Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Add API for reconstructing descriptors and generic signatures #293

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 49 additions & 1 deletion core/src/main/java/org/jboss/jandex/ClassInfo.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;

/**
* Represents a class entry in an index. A ClassInfo is only a partial view of a
Expand All @@ -52,7 +53,7 @@
* @author Jason T. Greene
*
*/
public final class ClassInfo implements AnnotationTarget {
public final class ClassInfo implements AnnotationTarget, Descriptor, GenericSignature {

private static final int MAX_POSITIONS = 256;
private static final byte[] EMPTY_POSITIONS = new byte[0];
Expand Down Expand Up @@ -1096,6 +1097,53 @@ public ModuleInfo module() {
return nestingInfo != null ? nestingInfo.module : null;
}

/**
* Returns whether this class must have a generic signature. That is, whether the Java compiler
* when compiling this class had to emit the {@code Signature} bytecode attribute.
*
* @return whether this class must have a generic signature
*/
@Override
public boolean requiresGenericSignature() {
return GenericSignatureReconstruction.requiresGenericSignature(this);
}

/**
* Returns a generic signature of this class, possibly without any generic-related information.
* That is, produces a correct generic signature even if this class is not generic
* and does not use any type variables.
* <p>
* Signatures of type variables are substituted for signatures of types provided by the substitution
* function {@code typeVariableSubstitution}. If the substitution function returns {@code null}
* for some type variable identifier, no substitution happens and the type variable signature is used
* unmodified.
* <p>
* Note that the return value does not come directly from bytecode. Jandex does not store the signature
* strings. Instead, the return value is reconstructed from the Jandex object model.
*
* @param typeVariableSubstitution a substitution function from type variable identifiers to types
* @return a generic signature of this class with type variables substituted, never {@code null}
*/
@Override
public String genericSignature(Function<String, Type> typeVariableSubstitution) {
return GenericSignatureReconstruction.reconstructGenericSignature(this, typeVariableSubstitution);
}

/**
* Returns a bytecode descriptor of the type introduced by this class.
* <p>
* Note that the return value does not come directly from bytecode. Jandex does not store the descriptor
* strings. Instead, the return value is reconstructed from the Jandex object model.
*
* @return the bytecode descriptor of this class's type
*/
@Override
public String descriptor(Function<String, Type> typeVariableSubstitution) {
StringBuilder result = new StringBuilder();
DescriptorReconstruction.objectTypeDescriptor(name, result);
return result.toString();
}

@Override
public ClassInfo asClass() {
return this;
Expand Down
57 changes: 57 additions & 0 deletions core/src/main/java/org/jboss/jandex/Descriptor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package org.jboss.jandex;

import java.util.function.Function;

/**
* Implementations of this interface have a bytecode descriptor, as defined in JVMS 17, chapter 4.3.
*/
public interface Descriptor {
Function<String, Type> NO_SUBSTITUTION = ignored -> null;

/**
* Returns a bytecode descriptor of this element.
* <p>
* Note that the return value does not come directly from bytecode. Jandex does not store the descriptor
* strings. Instead, the return value is reconstructed from the Jandex object model.
*
* @return a bytecode descriptor of this declaration, never {@code null}
*/
default String descriptor() {
return descriptor(NO_SUBSTITUTION);
}

/**
* Returns a bytecode descriptor of this element.
* <p>
* Descriptors of type variables are substituted for descriptors of types provided by the substitution
* function {@code typeVariableSubstitution}. If the substitution function returns {@code null}
* for some type variable identifier, or if it returns the type variable itself, no substitution happens
* and the type variable descriptor is used unmodified.
* <p>
* Note that the return value does not come directly from bytecode. Jandex does not store the descriptor
* strings. Instead, the return value is reconstructed from the Jandex object model.
*
* @param typeVariableSubstitution a substitution function from type variable identifiers to types
* @return a bytecode descriptor of this declaration, never {@code null}
*/
String descriptor(Function<String, Type> typeVariableSubstitution);

// ---
// utilities for advanced use cases

/**
* Appends a bytecode descriptor of a single type to given {@code StringBuilder}.
* <p>
* Descriptors of type variables are substituted for descriptors of types provided by the substitution
* function {@code typeVariableSubstitution}. If the substitution function returns {@code null}
* for some type variable identifier, or if it returns the type variable itself, no substitution happens
* and the type variable descriptor is used unmodified.
*
* @param type a type whose bytecode descriptor is appended to {@code result}
* @param typeVariableSubstitution a substitution function from type variable identifiers to types
* @param result the {@code StringBuilder} to which the bytecode descriptor is appended
*/
static void forType(Type type, Function<String, Type> typeVariableSubstitution, StringBuilder result) {
DescriptorReconstruction.typeDescriptor(type, typeVariableSubstitution, result);
}
}
112 changes: 112 additions & 0 deletions core/src/main/java/org/jboss/jandex/DescriptorReconstruction.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package org.jboss.jandex;

import java.util.function.Function;

final class DescriptorReconstruction {
static String fieldDescriptor(FieldInfo field, Function<String, Type> typeVariableSubstitution) {
FieldInternal internal = field.fieldInternal();

StringBuilder result = new StringBuilder();
typeDescriptor(internal.type(), typeVariableSubstitution, result);
return result.toString();
}

static String methodDescriptor(MethodInfo method, Function<String, Type> typeVariableSubstitution) {
MethodInternal internal = method.methodInternal();

StringBuilder result = new StringBuilder();
result.append('(');
for (Type parameterType : internal.parameterTypesArray()) {
typeDescriptor(parameterType, typeVariableSubstitution, result);
}
result.append(')');
typeDescriptor(internal.returnType(), typeVariableSubstitution, result);
return result.toString();
}

static String recordComponentDescriptor(RecordComponentInfo recordComponent,
Function<String, Type> typeVariableSubstitution) {
RecordComponentInternal internal = recordComponent.recordComponentInternal();

StringBuilder result = new StringBuilder();
typeDescriptor(internal.type(), typeVariableSubstitution, result);
return result.toString();
}

static void typeDescriptor(Type type, Function<String, Type> substitution, StringBuilder result) {
switch (type.kind()) {
case VOID:
result.append('V');
break;
case PRIMITIVE:
PrimitiveType.Primitive primitive = type.asPrimitiveType().primitive();
switch (primitive) {
case BOOLEAN:
result.append('Z');
return;
case BYTE:
result.append('B');
return;
case SHORT:
result.append('S');
return;
case INT:
result.append('I');
return;
case LONG:
result.append('J');
return;
case FLOAT:
result.append('F');
return;
case DOUBLE:
result.append('D');
return;
case CHAR:
result.append('C');
return;
default:
throw new IllegalArgumentException("unkown primitive type " + primitive);
}
case ARRAY:
ArrayType arrayType = type.asArrayType();
for (int i = 0; i < arrayType.dimensions(); i++) {
result.append('[');
}
typeDescriptor(arrayType.component(), substitution, result);
break;
case CLASS:
case PARAMETERIZED_TYPE:
case WILDCARD_TYPE:
objectTypeDescriptor(type.name(), result);
break;
case TYPE_VARIABLE:
typeVariableDescriptor(type, type.asTypeVariable().identifier(), substitution, result);
break;
case UNRESOLVED_TYPE_VARIABLE:
typeVariableDescriptor(type, type.asUnresolvedTypeVariable().identifier(), substitution, result);
break;
case TYPE_VARIABLE_REFERENCE:
typeVariableDescriptor(type, type.asTypeVariableReference().identifier(), substitution, result);
break;
default:
throw new IllegalArgumentException("unknown type " + type);
}
}

static void objectTypeDescriptor(DotName name, StringBuilder result) {
result.append('L').append(name.toString('/')).append(';');
}

// `typeVariable` is always the type variable whose identifier is `typeVariableIdentifier`; its purpose is
// to prevent possible cycle when `substitution.apply(typeVariableIdentifier)` returns `typeVariable`
private static void typeVariableDescriptor(Type typeVariable, String typeVariableIdentifier,
Function<String, Type> substitution, StringBuilder result) {
Type type = substitution == null ? null : substitution.apply(typeVariableIdentifier);
if (type == null || type == typeVariable) {
objectTypeDescriptor(typeVariable.name(), result);
} else {
typeDescriptor(type, substitution, result);
}
}
}
20 changes: 13 additions & 7 deletions core/src/main/java/org/jboss/jandex/DotName.java
Original file line number Diff line number Diff line change
Expand Up @@ -275,23 +275,29 @@ public boolean isInner() {
}

/**
* Returns the regular fully qualifier class name.
* Returns the regular binary class name.
*
* @return The fully qualified class name
* @return the binary class name
*/
public String toString() {
return toString('.');
}

/**
* Returns the regular binary class name where {@code delim} is used as a package separator.
*
* @param delim the package separator; typically {@code .}, but may be e.g. {@code /}
* to construct a bytecode descriptor
* @return the binary class name with given character used as a package separator
*/
public String toString(char delim) {
String string = local;
if (prefix != null) {
if (componentized) {
StringBuilder builder = new StringBuilder();
buildString(delim, builder);
string = builder.toString();
return builder.toString();
} else {
return delim == '.' ? local : local.replace('.', delim);
}

return string;
}

private void buildString(char delim, StringBuilder builder) {
Expand Down
51 changes: 50 additions & 1 deletion core/src/main/java/org/jboss/jandex/FieldInfo.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;

/**
* Represents a field.
Expand All @@ -34,7 +35,7 @@
* @author Jason T. Greene
*
*/
public final class FieldInfo implements AnnotationTarget {
public final class FieldInfo implements AnnotationTarget, Descriptor, GenericSignature {

private ClassInfo clazz;
private FieldInternal internal;
Expand Down Expand Up @@ -383,6 +384,54 @@ public int enumConstantOrdinal() {
return clazz.enumConstantOrdinal(internal);
}

/**
* Returns whether this field must have a generic signature. That is, whether the Java compiler
* when compiling this field had to emit the {@code Signature} bytecode attribute.
*
* @return whether this field must have a generic signature
*/
@Override
public boolean requiresGenericSignature() {
return GenericSignatureReconstruction.requiresGenericSignature(this);
}

/**
* Returns a generic signature of this field, possibly without any generic-related information.
* That is, produces a correct generic signature even if this field does not use any type variables.
* <p>
* Signatures of type variables are substituted for signatures of types provided by the substitution
* function {@code typeVariableSubstitution}. If the substitution function returns {@code null}
* for some type variable identifier, no substitution happens and the type variable signature is used
* unmodified.
* <p>
* Note that the return value does not come directly from bytecode. Jandex does not store the signature
* strings. Instead, the return value is reconstructed from the Jandex object model.
*
* @param typeVariableSubstitution a substitution function from type variable identifiers to types
* @return a generic signature of this field with type variables substituted, never {@code null}
*/
@Override
public String genericSignature(Function<String, Type> typeVariableSubstitution) {
return GenericSignatureReconstruction.reconstructGenericSignature(this, typeVariableSubstitution);
}

/**
* Returns a bytecode descriptor of this field.
* <p>
* Descriptors of type variables are substituted for descriptors of types provided by the substitution
* function {@code typeVariableSubstitution}. If the substitution function returns {@code null}
* for some type variable identifier, or if it returns the type variable itself, no substitution happens
* and the type variable descriptor is used unmodified.
* <p>
* Note that the return value does not come directly from bytecode. Jandex does not store the descriptor
* strings. Instead, the return value is reconstructed from the Jandex object model.
*
* @return the bytecode descriptor of this field
*/
public String descriptor(Function<String, Type> typeVariableSubstitution) {
return DescriptorReconstruction.fieldDescriptor(this, typeVariableSubstitution);
}

/**
* Returns a string representation describing this field. It is similar although not
* necessarily identical to a Java source code declaration of this field.
Expand Down
Loading