From fd51c25615f9387d1413b6ca5807b263fb1e4476 Mon Sep 17 00:00:00 2001 From: Ladislav Thon Date: Tue, 10 Jan 2023 16:29:41 +0100 Subject: [PATCH] Add API for reconstructing descriptors and generic signatures This API includes type variable substitution, which is used on several places in Quarkus. If the substitution function returns a type for some type variable identifier, the reconstructed descriptor/signature contains a descriptor/signature of the provided type instead of the original type variable. --- .../main/java/org/jboss/jandex/ClassInfo.java | 50 +- .../java/org/jboss/jandex/Descriptor.java | 57 +++ .../jandex/DescriptorReconstruction.java | 112 +++++ .../main/java/org/jboss/jandex/DotName.java | 20 +- .../main/java/org/jboss/jandex/FieldInfo.java | 51 +- .../org/jboss/jandex/GenericSignature.java | 110 +++++ .../GenericSignatureReconstruction.java | 312 +++++++++++++ .../java/org/jboss/jandex/MethodInfo.java | 52 ++- .../org/jboss/jandex/ParameterizedType.java | 2 +- .../org/jboss/jandex/RecordComponentInfo.java | 52 ++- core/src/main/java/org/jboss/jandex/Type.java | 88 +++- .../test/DescriptorReconstructionTest.java | 131 ++++++ .../jboss/jandex/test/DotNameTestCase.java | 14 + .../GenericSignatureReconstructionTest.java | 437 ++++++++++++++++++ .../jandex/test/data/ClassInheritance.java | 203 ++++++++ .../jboss/jandex/test/data/OuterParam.java | 72 +++ .../jandex/test/data/OuterParamBound.java | 73 +++ .../org/jboss/jandex/test/data/OuterRaw.java | 78 ++++ 18 files changed, 1882 insertions(+), 32 deletions(-) create mode 100644 core/src/main/java/org/jboss/jandex/Descriptor.java create mode 100644 core/src/main/java/org/jboss/jandex/DescriptorReconstruction.java create mode 100644 core/src/main/java/org/jboss/jandex/GenericSignature.java create mode 100644 core/src/main/java/org/jboss/jandex/GenericSignatureReconstruction.java create mode 100644 core/src/test/java/org/jboss/jandex/test/DescriptorReconstructionTest.java create mode 100644 core/src/test/java/org/jboss/jandex/test/GenericSignatureReconstructionTest.java create mode 100644 core/src/test/java/org/jboss/jandex/test/data/ClassInheritance.java create mode 100644 core/src/test/java/org/jboss/jandex/test/data/OuterParam.java create mode 100644 core/src/test/java/org/jboss/jandex/test/data/OuterParamBound.java create mode 100644 core/src/test/java/org/jboss/jandex/test/data/OuterRaw.java diff --git a/core/src/main/java/org/jboss/jandex/ClassInfo.java b/core/src/main/java/org/jboss/jandex/ClassInfo.java index 55657a5b..3cbf64e8 100644 --- a/core/src/main/java/org/jboss/jandex/ClassInfo.java +++ b/core/src/main/java/org/jboss/jandex/ClassInfo.java @@ -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 @@ -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]; @@ -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. + *

+ * 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. + *

+ * 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 typeVariableSubstitution) { + return GenericSignatureReconstruction.reconstructGenericSignature(this, typeVariableSubstitution); + } + + /** + * Returns a bytecode descriptor of the type introduced by this class. + *

+ * 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 typeVariableSubstitution) { + StringBuilder result = new StringBuilder(); + DescriptorReconstruction.objectTypeDescriptor(name, result); + return result.toString(); + } + @Override public ClassInfo asClass() { return this; diff --git a/core/src/main/java/org/jboss/jandex/Descriptor.java b/core/src/main/java/org/jboss/jandex/Descriptor.java new file mode 100644 index 00000000..f55fed9d --- /dev/null +++ b/core/src/main/java/org/jboss/jandex/Descriptor.java @@ -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 NO_SUBSTITUTION = ignored -> null; + + /** + * Returns a bytecode descriptor of this element. + *

+ * 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. + *

+ * 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. + *

+ * 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 typeVariableSubstitution); + + // --- + // utilities for advanced use cases + + /** + * Appends a bytecode descriptor of a single type to given {@code StringBuilder}. + *

+ * 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 typeVariableSubstitution, StringBuilder result) { + DescriptorReconstruction.typeDescriptor(type, typeVariableSubstitution, result); + } +} diff --git a/core/src/main/java/org/jboss/jandex/DescriptorReconstruction.java b/core/src/main/java/org/jboss/jandex/DescriptorReconstruction.java new file mode 100644 index 00000000..d30c8720 --- /dev/null +++ b/core/src/main/java/org/jboss/jandex/DescriptorReconstruction.java @@ -0,0 +1,112 @@ +package org.jboss.jandex; + +import java.util.function.Function; + +final class DescriptorReconstruction { + static String fieldDescriptor(FieldInfo field, Function typeVariableSubstitution) { + FieldInternal internal = field.fieldInternal(); + + StringBuilder result = new StringBuilder(); + typeDescriptor(internal.type(), typeVariableSubstitution, result); + return result.toString(); + } + + static String methodDescriptor(MethodInfo method, Function 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 typeVariableSubstitution) { + RecordComponentInternal internal = recordComponent.recordComponentInternal(); + + StringBuilder result = new StringBuilder(); + typeDescriptor(internal.type(), typeVariableSubstitution, result); + return result.toString(); + } + + static void typeDescriptor(Type type, Function 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 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); + } + } +} diff --git a/core/src/main/java/org/jboss/jandex/DotName.java b/core/src/main/java/org/jboss/jandex/DotName.java index 9d53d969..a3f00db0 100644 --- a/core/src/main/java/org/jboss/jandex/DotName.java +++ b/core/src/main/java/org/jboss/jandex/DotName.java @@ -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) { diff --git a/core/src/main/java/org/jboss/jandex/FieldInfo.java b/core/src/main/java/org/jboss/jandex/FieldInfo.java index 51c9620b..cc7bf617 100644 --- a/core/src/main/java/org/jboss/jandex/FieldInfo.java +++ b/core/src/main/java/org/jboss/jandex/FieldInfo.java @@ -22,6 +22,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.function.Function; /** * Represents a field. @@ -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; @@ -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. + *

+ * 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. + *

+ * 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 typeVariableSubstitution) { + return GenericSignatureReconstruction.reconstructGenericSignature(this, typeVariableSubstitution); + } + + /** + * Returns a bytecode descriptor of this field. + *

+ * 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. + *

+ * 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 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. diff --git a/core/src/main/java/org/jboss/jandex/GenericSignature.java b/core/src/main/java/org/jboss/jandex/GenericSignature.java new file mode 100644 index 00000000..c5ceb03c --- /dev/null +++ b/core/src/main/java/org/jboss/jandex/GenericSignature.java @@ -0,0 +1,110 @@ +package org.jboss.jandex; + +import java.util.List; +import java.util.function.Function; + +/** + * Implementations of this interface have a generic signature, as defined in JVMS 17, chapter 4.7.9.1. + */ +public interface GenericSignature { + Function NO_SUBSTITUTION = ignored -> null; + + /** + * Returns whether this declaration must have a generic signature. That is, whether the Java compiler + * when compiling this declaration had to emit the {@code Signature} bytecode attribute. + * + * @return whether this declaration must have a generic signature + */ + boolean requiresGenericSignature(); + + /** + * Returns a generic signature of this declaration, possibly without any generic-related information. + * That is, produces a correct generic signature even if this declaration is not generic + * and does not use any type variables. + *

+ * 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. + * + * @return a generic signature of this declaration, never {@code null} + */ + default String genericSignature() { + return genericSignature(NO_SUBSTITUTION); + } + + /** + * Returns a generic signature of this declaration, possibly without any generic-related information. + * That is, produces a correct generic signature even if this declaration is not generic + * and does not use any type variables. + *

+ * 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, or if it returns the type variable itself, no substitution happens + * and the type variable signature is used unmodified. + *

+ * 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 declaration with type variables substituted, never {@code null} + */ + String genericSignature(Function typeVariableSubstitution); + + /** + * Returns a {@linkplain #genericSignature() generic signature} of this declaration + * if {@linkplain #requiresGenericSignature() required}. + * + * @return a generic signature of this declaration, or {@code null} if this declaration doesn't have to have one + */ + default String genericSignatureIfRequired() { + return genericSignatureIfRequired(NO_SUBSTITUTION); + } + + /** + * Returns a {@linkplain #genericSignature(Function) generic signature} of this declaration + * if {@linkplain #requiresGenericSignature() required}. Type variable signatures are substituted. + * + * @param typeVariableSubstitution a substitution function from type variable identifiers to types + * @return a generic signature of this declaration with type variables substituted, or {@code null} + * if this declaration does not have to have one + */ + default String genericSignatureIfRequired(Function typeVariableSubstitution) { + return requiresGenericSignature() ? genericSignature(typeVariableSubstitution) : null; + } + + // --- + // utilities for advanced use cases + + /** + * Appends a generic signature of a type parameter list, including the {@code <} at the beginning and + * {@code >} at the end, to given {@code StringBuilder}. + *

+ * 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, or if it returns the type variable itself, no substitution happens + * and the type variable signature is used unmodified. + * + * @param typeParameters a list of type parameters whose generic signature is appended to {@code result} + * @param typeVariableSubstitution a substitution function from type variable identifiers to types + * @param result the {@code StringBuilder} to which the generic signature is appended + */ + static void forTypeParameters(List typeParameters, Function typeVariableSubstitution, + StringBuilder result) { + GenericSignatureReconstruction.typeParametersSignature(typeParameters, typeVariableSubstitution, result); + } + + /** + * Appends a generic signature of a single type to given {@code StringBuilder}. + *

+ * 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, or if it returns the type variable itself, no substitution happens + * and the type variable signature is used unmodified. + * + * @param type a type parameters whose generic signature is appended to {@code result} + * @param typeVariableSubstitution a substitution function from type variable identifiers to types + * @param result the {@code StringBuilder} to which the generic signature is appended + */ + static void forType(Type type, Function typeVariableSubstitution, StringBuilder result) { + GenericSignatureReconstruction.typeSignature(type, typeVariableSubstitution, result); + } +} diff --git a/core/src/main/java/org/jboss/jandex/GenericSignatureReconstruction.java b/core/src/main/java/org/jboss/jandex/GenericSignatureReconstruction.java new file mode 100644 index 00000000..a6bd9ccd --- /dev/null +++ b/core/src/main/java/org/jboss/jandex/GenericSignatureReconstruction.java @@ -0,0 +1,312 @@ +package org.jboss.jandex; + +import java.util.List; +import java.util.function.Function; + +final class GenericSignatureReconstruction { + static boolean requiresGenericSignature(ClassInfo clazz) { + // JVMS 17, chapter 4.7.9.1. Signatures: + // + // Java compiler must emit ... + // + // A class signature for any class or interface declaration which is either generic, + // or has a parameterized type as a superclass or superinterface, or both. + + if (!clazz.typeParameters().isEmpty()) { + return true; + } + + { + Type superType = clazz.superClassType(); + if (superType.kind() == Type.Kind.PARAMETERIZED_TYPE) { + return true; + } + } + + for (Type superType : clazz.interfaceTypes()) { + if (superType.kind() == Type.Kind.PARAMETERIZED_TYPE) { + return true; + } + } + + return false; + } + + static boolean requiresGenericSignature(MethodInfo method) { + // JVMS 17, chapter 4.7.9.1. Signatures: + // + // Java compiler must emit ... + // + // A method signature for any method or constructor declaration which is either generic, + // or has a type variable or parameterized type as the return type or a formal parameter type, + // or has a type variable in a throws clause, or any combination thereof. + // + // If the throws clause of a method or constructor declaration does not involve type variables, + // then a compiler may treat the declaration as having no throws clause for the purpose of + // emitting a method signature. + + if (!method.typeParameters().isEmpty()) { + return true; + } + + { + if (requiresGenericSignature(method.returnType())) { + return true; + } + } + + for (Type parameterType : method.parameterTypes()) { + if (requiresGenericSignature(parameterType)) { + return true; + } + } + + if (hasThrowsSignature(method)) { + return true; + } + + return false; + } + + static boolean requiresGenericSignature(FieldInfo field) { + // JVMS 17, chapter 4.7.9.1. Signatures: + // + // Java compiler must emit ... + // + // A field signature for any field, formal parameter, local variable, or record component + // declaration whose type uses a type variable or a parameterized type. + + return requiresGenericSignature(field.type()); + } + + static boolean requiresGenericSignature(RecordComponentInfo recordComponent) { + // JVMS 17, chapter 4.7.9.1. Signatures: + // + // Java compiler must emit ... + // + // A field signature for any field, formal parameter, local variable, or record component + // declaration whose type uses a type variable or a parameterized type. + + return requiresGenericSignature(recordComponent.type()); + } + + private static boolean requiresGenericSignature(Type type) { + return type.kind() == Type.Kind.TYPE_VARIABLE + || type.kind() == Type.Kind.UNRESOLVED_TYPE_VARIABLE + || type.kind() == Type.Kind.PARAMETERIZED_TYPE; + } + + // --- + // for grammar, see JVMS 17, chapter 4.7.9.1. Signatures + + static String reconstructGenericSignature(ClassInfo clazz, Function typeVariableSubstitution) { + StringBuilder result = new StringBuilder(); + + if (!clazz.typeParameters().isEmpty()) { + typeParametersSignature(clazz.typeParameters(), typeVariableSubstitution, result); + } + + typeSignature(clazz.superClassType(), typeVariableSubstitution, result); + + for (Type interfaceType : clazz.interfaceTypes()) { + typeSignature(interfaceType, typeVariableSubstitution, result); + } + + return result.toString(); + } + + static String reconstructGenericSignature(MethodInfo method, Function typeVariableSubstitution) { + StringBuilder result = new StringBuilder(); + + if (!method.typeParameters().isEmpty()) { + typeParametersSignature(method.typeParameters(), typeVariableSubstitution, result); + } + + result.append('('); + for (Type parameter : method.parameterTypes()) { + typeSignature(parameter, typeVariableSubstitution, result); + } + result.append(')'); + + typeSignature(method.returnType(), typeVariableSubstitution, result); + + if (hasThrowsSignature(method)) { + for (Type exception : method.exceptions()) { + result.append('^'); + typeSignature(exception, typeVariableSubstitution, result); + } + } + + return result.toString(); + } + + static String reconstructGenericSignature(FieldInfo field, Function typeVariableSubstitution) { + StringBuilder result = new StringBuilder(); + + typeSignature(field.type(), typeVariableSubstitution, result); + + return result.toString(); + } + + static String reconstructGenericSignature(RecordComponentInfo recordComponent, + Function typeVariableSubstitution) { + StringBuilder result = new StringBuilder(); + + typeSignature(recordComponent.type(), typeVariableSubstitution, result); + + return result.toString(); + } + + private static boolean hasThrowsSignature(MethodInfo method) { + // JVMS 17, chapter 4.7.9.1. Signatures: + // + // If the throws clause of a method or constructor declaration does not involve type variables, + // then a compiler may treat the declaration as having no throws clause for the purpose of + // emitting a method signature. + + // also, no need to check if an exception type is of kind PARAMETERIZED_TYPE, because + // + // JLS 17, chapter 8.1.2. Generic Classes and Type Parameters: + // + // It is a compile-time error if a generic class is a direct or indirect subclass of Throwable. + + for (Type type : method.exceptions()) { + if (type.kind() == Type.Kind.TYPE_VARIABLE + || type.kind() == Type.Kind.UNRESOLVED_TYPE_VARIABLE) { + return true; + } + } + return false; + } + + static void typeParametersSignature(List typeParameters, + Function substitution, StringBuilder result) { + + if (typeParameters.isEmpty()) { + return; + } + + // it is not clear whether we should substitute type variables in type parameter bounds; + // we currently do, because the Quarkus original (where this is adapted from) also did + + result.append('<'); + for (TypeVariable typeParameter : typeParameters) { + result.append(typeParameter.identifier()); + + if (typeParameter.hasImplicitObjectBound()) { + result.append(':'); + } + for (Type bound : typeParameter.bounds()) { + result.append(':'); + typeSignature(bound, substitution, result); + } + } + result.append('>'); + } + + static void typeSignature(Type type, Function 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 CHAR: + result.append('C'); + 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; + default: + throw new IllegalArgumentException("unkown primitive type " + primitive); + } + case CLASS: + ClassType classType = type.asClassType(); + result.append('L').append(classType.name().toString('/')).append(';'); + break; + case ARRAY: + ArrayType arrayType = type.asArrayType(); + for (int i = 0; i < arrayType.dimensions(); i++) { + result.append('['); + } + typeSignature(arrayType.component(), substitution, result); + break; + case PARAMETERIZED_TYPE: + ParameterizedType parameterizedType = type.asParameterizedType(); + Type owner = parameterizedType.owner(); + if (owner != null && owner.kind() == Type.Kind.PARAMETERIZED_TYPE) { + typeSignature(owner, substitution, result); + // the typeSignature call on previous line always takes the PARAMETERIZED_TYPE branch, + // so at this point, result ends with a ';', which we just replace with '.' + assert result.charAt(result.length() - 1) == ';'; + result.setCharAt(result.length() - 1, '.'); + result.append(parameterizedType.name().local()); + } else { + result.append('L').append(parameterizedType.name().toString('/')); + } + if (!parameterizedType.arguments().isEmpty()) { + result.append('<'); + for (Type argument : parameterizedType.arguments()) { + typeSignature(argument, substitution, result); + } + result.append('>'); + } + result.append(';'); + break; + case TYPE_VARIABLE: + typeVariableSignature(type, type.asTypeVariable().identifier(), substitution, result); + break; + case UNRESOLVED_TYPE_VARIABLE: + typeVariableSignature(type, type.asUnresolvedTypeVariable().identifier(), substitution, result); + break; + case TYPE_VARIABLE_REFERENCE: + typeVariableSignature(type, type.asTypeVariableReference().identifier(), substitution, result); + break; + case WILDCARD_TYPE: + WildcardType wildcardType = type.asWildcardType(); + if (wildcardType.superBound() != null) { + result.append('-'); + typeSignature(wildcardType.superBound(), substitution, result); + } else if (ClassType.OBJECT_TYPE.equals(wildcardType.extendsBound())) { + result.append('*'); + } else { + result.append('+'); + typeSignature(wildcardType.extendsBound(), substitution, result); + } + break; + default: + throw new IllegalArgumentException("unknown type " + type); + } + } + + // `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 typeVariableSignature(Type typeVariable, String typeVariableIdentifier, + Function substitution, StringBuilder result) { + Type type = substitution == null ? null : substitution.apply(typeVariableIdentifier); + if (type == null || type == typeVariable) { + result.append('T').append(typeVariableIdentifier).append(';'); + } else { + typeSignature(type, substitution, result); + } + } +} diff --git a/core/src/main/java/org/jboss/jandex/MethodInfo.java b/core/src/main/java/org/jboss/jandex/MethodInfo.java index e5003eed..5a677c6c 100644 --- a/core/src/main/java/org/jboss/jandex/MethodInfo.java +++ b/core/src/main/java/org/jboss/jandex/MethodInfo.java @@ -24,6 +24,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.function.Function; /** * Represents a Java method, constructor, or static initializer. @@ -41,7 +42,7 @@ * * @author Jason T. Greene */ -public final class MethodInfo implements AnnotationTarget { +public final class MethodInfo implements AnnotationTarget, Descriptor, GenericSignature { static final String[] EMPTY_PARAMETER_NAMES = new String[0]; private MethodInternal methodInternal; @@ -586,6 +587,55 @@ public boolean isConstructor() { return Arrays.equals(Utils.INIT_METHOD_NAME, methodInternal.nameBytes()); } + /** + * Returns whether this method must have a generic signature. That is, whether the Java compiler + * when compiling this method had to emit the {@code Signature} bytecode attribute. + * + * @return whether this method must have a generic signature + */ + @Override + public boolean requiresGenericSignature() { + return GenericSignatureReconstruction.requiresGenericSignature(this); + } + + /** + * Returns a generic signature of this method, possibly without any generic-related information. + * That is, produces a correct generic signature even if this method is not generic + * and does not use any type variables. + *

+ * 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. + *

+ * 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 method with type variables substituted, never {@code null} + */ + @Override + public String genericSignature(Function typeVariableSubstitution) { + return GenericSignatureReconstruction.reconstructGenericSignature(this, typeVariableSubstitution); + } + + /** + * Returns a bytecode descriptor of this method. + *

+ * 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. + *

+ * 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 method + */ + public String descriptor(Function typeVariableSubstitution) { + return DescriptorReconstruction.methodDescriptor(this, typeVariableSubstitution); + } + /** * Returns a string representation describing this method. It is similar although not * necessarily identical to a Java source code declaration of this method. diff --git a/core/src/main/java/org/jboss/jandex/ParameterizedType.java b/core/src/main/java/org/jboss/jandex/ParameterizedType.java index 905d8551..ec2b2292 100644 --- a/core/src/main/java/org/jboss/jandex/ParameterizedType.java +++ b/core/src/main/java/org/jboss/jandex/ParameterizedType.java @@ -23,7 +23,7 @@ /** * Represents a parameterized type. The {@code name()} corresponds to the raw type, and the - * {@code arguments()} list corresponds to the type arguments passed to the generic type + * {@code arguments()} list corresponds to the type arguments applied to the generic class * in order to instantiate this parameterized type. *

* For example, the parameterized type {@code Map} would have a name of diff --git a/core/src/main/java/org/jboss/jandex/RecordComponentInfo.java b/core/src/main/java/org/jboss/jandex/RecordComponentInfo.java index 04ff02b7..7afa513e 100644 --- a/core/src/main/java/org/jboss/jandex/RecordComponentInfo.java +++ b/core/src/main/java/org/jboss/jandex/RecordComponentInfo.java @@ -21,6 +21,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.function.Function; /** * Represents an individual Java record component that was annotated. @@ -31,7 +32,7 @@ * This class is immutable and can be shared between threads without safe publication. * */ -public final class RecordComponentInfo implements AnnotationTarget { +public final class RecordComponentInfo implements AnnotationTarget, Descriptor, GenericSignature { private ClassInfo clazz; private RecordComponentInternal internal; @@ -318,6 +319,55 @@ public final List declaredAnnotations() { return Collections.unmodifiableList(instances); } + /** + * Returns whether this record component must have a generic signature. That is, whether the Java compiler + * when compiling this record component had to emit the {@code Signature} bytecode attribute. + * + * @return whether this record component must have a generic signature + */ + @Override + public boolean requiresGenericSignature() { + return GenericSignatureReconstruction.requiresGenericSignature(this); + } + + /** + * Returns a generic signature of this record component, possibly without any generic-related information. + * That is, produces a correct generic signature even if this record component does not use any type variables. + *

+ * 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. + *

+ * 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 record component with type variables substituted, never {@code null} + */ + @Override + public String genericSignature(Function typeVariableSubstitution) { + return GenericSignatureReconstruction.reconstructGenericSignature(this, typeVariableSubstitution); + } + + /** + * Returns a bytecode descriptor of this record component. + *

+ * 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. + *

+ * 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 record component + */ + @Override + public String descriptor(Function typeVariableSubstitution) { + return DescriptorReconstruction.recordComponentDescriptor(this, typeVariableSubstitution); + } + /** * Returns a string representation describing this record component. It is similar although not * necessarily identical to a Java source code declaration of this record component. diff --git a/core/src/main/java/org/jboss/jandex/Type.java b/core/src/main/java/org/jboss/jandex/Type.java index 7d079cef..f6b7c1aa 100644 --- a/core/src/main/java/org/jboss/jandex/Type.java +++ b/core/src/main/java/org/jboss/jandex/Type.java @@ -22,6 +22,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.function.Function; /** * Represents a Java type usage that is specified on methods, fields, classes, @@ -36,7 +37,7 @@ * * @author Jason T. Greene */ -public abstract class Type implements Interned { +public abstract class Type implements Descriptor, Interned { public static final Type[] EMPTY_ARRAY = new Type[0]; private static final AnnotationInstance[] EMPTY_ANNOTATIONS = new AnnotationInstance[0]; private final DotName name; @@ -120,26 +121,36 @@ public static Kind fromOrdinal(int ordinal) { } /** - * Creates a type instance of the specified kind. If {@code kind} is {@code CLASS}, - * the {@code name} is used as is. If {@code kind} is {@code ARRAY}, the {@code name} - * must be in the Java reflection format (Java descriptor format changing {@code /} - * to {@code .}, e.g. {@code [[[[Ljava.lang.String;"} or {@code [[[I}). If {@code kind} - * is {@code PRIMITIVE}, the name must be in the Java reflection format (which is equal - * to the Java keyword denoting the primitive type, e.g. {@code int}). If kind is - * {@code VOID}, the {@code name} is ignored. All other kinds cause an exception. + * Creates a type of the specified kind and {@code name} in the {@link Class#getName()} + * format. Specifically: + *

* - * @param name the name of type to use or parse - * @param kind the kind of type to create + * @param name the name of type to use or parse; must not be {@code null} + * @param kind the kind of type to create; must not be {@code null} * @return the type - * @throws java.lang.IllegalArgumentException if the kind is no supported + * @throws java.lang.IllegalArgumentException if the {@code kind} is not supported * */ public static Type create(DotName name, Kind kind) { - if (name == null) + if (name == null) { throw new IllegalArgumentException("name can not be null!"); - - if (kind == null) + } + if (kind == null) { throw new IllegalArgumentException("kind can not be null!"); + } switch (kind) { case ARRAY: @@ -190,11 +201,11 @@ public static Type create(DotName name, Kind kind) { * Creates an instance of specified type with given type {@code annotations}. * To create the type instance, this method delegates to {@link #create(DotName, Kind)}. * - * @param name the name of type to use or parse - * @param kind the kind of type to create - * @param annotations the type annotations that should be present on the type instance + * @param name the name of type to use or parse; must not be {@code null} + * @param kind the kind of type to create; must not be {@code null} + * @param annotations the type annotations that should be present on the type instance; may be {@code null} * @return the annotated type - * @throws java.lang.IllegalArgumentException if the kind is no supported + * @throws java.lang.IllegalArgumentException if the {@code kind} is not supported */ public static Type createWithAnnotations(DotName name, Kind kind, AnnotationInstance[] annotations) { Type type = create(name, kind); @@ -212,8 +223,8 @@ public static Type createWithAnnotations(DotName name, Kind kind, AnnotationInst *
  • for array types, a string is returned that consists of one or more {@code [} * characters corresponding to the number of dimensions of the array type, * followed by the element type as a single-character code for primitive types - * or {@code L;} for class types (for example, {@code [I} for {@code int[]} - * or {@code [[Ljava.lang.String;} for {@code String[][]});
  • + * or {@code Lbinary.name.of.TheClass;} for class types (for example, {@code [I} + * for {@code int[]} or {@code [[Ljava.lang.String;} for {@code String[][]}); *
  • for parameterized types, the binary name of the generic class is returned * (for example, {@code java.util.List} for {@code List});
  • *
  • for type variables, the name of the first bound of the type variable is returned, @@ -476,6 +487,43 @@ void appendAnnotations(StringBuilder builder) { } } + /** + * Returns the bytecode descriptor of this type (or its erasure in case of generic types). + * Specifically: + * + * 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. + *

    + * 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 type (or its erasure in case of generic types) + */ + public String descriptor(Function typeVariableSubstitution) { + StringBuilder result = new StringBuilder(); + DescriptorReconstruction.typeDescriptor(this, typeVariableSubstitution, result); + return result.toString(); + } + /** * Compares this {@code Type} with another type. A type is equal to another type * if it is of the same kind, and all of their fields are equal. This includes diff --git a/core/src/test/java/org/jboss/jandex/test/DescriptorReconstructionTest.java b/core/src/test/java/org/jboss/jandex/test/DescriptorReconstructionTest.java new file mode 100644 index 00000000..0e1da2d4 --- /dev/null +++ b/core/src/test/java/org/jboss/jandex/test/DescriptorReconstructionTest.java @@ -0,0 +1,131 @@ +package org.jboss.jandex.test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +import org.jboss.jandex.ClassInfo; +import org.jboss.jandex.ClassType; +import org.jboss.jandex.Descriptor; +import org.jboss.jandex.DotName; +import org.jboss.jandex.FieldInfo; +import org.jboss.jandex.Index; +import org.jboss.jandex.IndexView; +import org.jboss.jandex.MethodInfo; +import org.jboss.jandex.Type; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +public class DescriptorReconstructionTest { + static class TestData> { + boolean booleanField; + int intField; + double doubleField; + String stringField; + List listField; + Map> mapField; + A typeVarWithoutBoundField; + B typeVarWithBoundField; + D typeVarWithTransitiveBoundField; + E recursiveTypeVarField; + + void voidMethod(boolean booleanParam, List listParam) { + } + + String stringMethod(int intParam, Map> mapParam, A typeVarParam) { + return null; + } + + B typeVarMethod(double doubleParam, C typeVarParam, List wildcardParam) { + return null; + } + + X genericMethodWithoutBound(X typeParamParam, D typeVarParam, List wildcardParam) { + return null; + } + + X genericMethodWithBound(X typeParamParam, List wildcardParam) { + return null; + } + } + + private static IndexView index; + + @BeforeAll + public static void setUp() throws IOException { + index = Index.of(TestData.class); + } + + @Test + public void test() { + assertEquals("Lorg/jboss/jandex/test/DescriptorReconstructionTest$TestData;", + index.getClassByName(TestData.class).descriptor()); + + assertFieldDescriptor("booleanField", "Z"); + assertFieldDescriptor("intField", "I"); + assertFieldDescriptor("doubleField", "D"); + assertFieldDescriptor("stringField", "Ljava/lang/String;"); + assertFieldDescriptor("listField", "Ljava/util/List;"); + assertFieldDescriptor("mapField", "Ljava/util/Map;"); + assertFieldDescriptor("typeVarWithoutBoundField", "Ljava/lang/Object;"); + assertFieldDescriptor("typeVarWithBoundField", "Ljava/lang/Number;"); + assertFieldDescriptor("typeVarWithTransitiveBoundField", "Ljava/lang/Number;"); + assertFieldDescriptor("recursiveTypeVarField", "Ljava/lang/Comparable;"); + + assertMethodDescriptor("voidMethod", + "(ZLjava/util/List;)V"); + assertMethodDescriptor("stringMethod", + "(ILjava/util/Map;Ljava/lang/Object;)Ljava/lang/String;"); + assertMethodDescriptor("typeVarMethod", + "(DLjava/lang/Number;Ljava/util/List;)Ljava/lang/Number;"); + assertMethodDescriptor("genericMethodWithoutBound", + "(Ljava/lang/Object;Ljava/lang/Number;Ljava/util/List;)Ljava/lang/Object;"); + assertMethodDescriptor("genericMethodWithBound", + "(Ljava/lang/Number;Ljava/util/List;)Ljava/lang/Number;"); + } + + @Test + public void withSubstitution() { + assertFieldDescriptor("typeVarWithoutBoundField", "Ljava/lang/String;", + id -> "A".equals(id) ? ClassType.create(DotName.STRING_NAME) : null); + assertFieldDescriptor("typeVarWithBoundField", "Ljava/lang/String;", + id -> "B".equals(id) ? ClassType.create(DotName.STRING_NAME) : null); + assertFieldDescriptor("typeVarWithTransitiveBoundField", "Ljava/lang/Number;", + id -> null); + assertFieldDescriptor("recursiveTypeVarField", "Ljava/lang/Comparable;", + id -> null); + + assertMethodDescriptor("typeVarMethod", + "(DLjava/lang/String;Ljava/util/List;)Ljava/lang/String;", + id -> "B".equals(id) || "C".equals(id) ? ClassType.create(DotName.STRING_NAME) : null); + assertMethodDescriptor("genericMethodWithoutBound", + "(Ljava/lang/String;Ljava/lang/Number;Ljava/util/List;)Ljava/lang/String;", + id -> "X".equals(id) ? ClassType.create(DotName.STRING_NAME) : null); + assertMethodDescriptor("genericMethodWithBound", + "(Ljava/lang/String;Ljava/util/List;)Ljava/lang/String;", + id -> "X".equals(id) ? ClassType.create(DotName.STRING_NAME) : null); + } + + private static void assertFieldDescriptor(String name, String expectedDescriptor) { + assertFieldDescriptor(name, expectedDescriptor, Descriptor.NO_SUBSTITUTION); + } + + private static void assertFieldDescriptor(String name, String expectedDescriptor, Function subst) { + ClassInfo clazz = index.getClassByName(TestData.class); + FieldInfo field = clazz.field(name); + assertEquals(expectedDescriptor, field.descriptor(subst)); + } + + private static void assertMethodDescriptor(String name, String expectedDescriptor) { + assertMethodDescriptor(name, expectedDescriptor, Descriptor.NO_SUBSTITUTION); + } + + private static void assertMethodDescriptor(String name, String expectedDescriptor, Function subst) { + ClassInfo clazz = index.getClassByName(TestData.class); + MethodInfo method = clazz.firstMethod(name); + assertEquals(expectedDescriptor, method.descriptor(subst)); + } +} diff --git a/core/src/test/java/org/jboss/jandex/test/DotNameTestCase.java b/core/src/test/java/org/jboss/jandex/test/DotNameTestCase.java index f09f43cc..8fd5fb80 100644 --- a/core/src/test/java/org/jboss/jandex/test/DotNameTestCase.java +++ b/core/src/test/java/org/jboss/jandex/test/DotNameTestCase.java @@ -209,6 +209,19 @@ public void testPackagePrefixName() { assertNull(DotName.createSimple("Foo").packagePrefixName()); } + @Test + public void testToStringDelim() { + DotName foo = DotName.createComponentized(DotName.createComponentized(null, "root"), "thefoo"); + foo = DotName.createComponentized(foo, "Foo"); + assertEquals("root/thefoo/Foo", foo.toString('/')); + DotName inner = DotName.createComponentized(foo, "Inner", true); + DotName inner2 = DotName.createComponentized(inner, "Inner2", true); + assertEquals("root/thefoo/Foo$Inner$Inner2", inner2.toString('/')); + + assertEquals("foo/bar/baz/Foo", DotName.createSimple("foo.bar.baz.Foo").toString('/')); + assertEquals("foo/bar/baz/Foo$Inner$Inner2", DotName.createSimple("foo.bar.baz.Foo$Inner$Inner2").toString('/')); + } + @Test public void testForNaturalComparator() { DotsContainer c = new DotsContainer(); @@ -328,6 +341,7 @@ public void testClassNameWithDelimitersFirstAndLast() throws IOException { assertNotNull(index.getClassByName(testNameSimple)); assertNotNull(index.getClassByName(testName)); assertEquals("$delimiters$.test.$SurroundedByDelimiters$", indexedName.toString()); + assertEquals("$delimiters$/test/$SurroundedByDelimiters$", indexedName.toString('/')); } @Test diff --git a/core/src/test/java/org/jboss/jandex/test/GenericSignatureReconstructionTest.java b/core/src/test/java/org/jboss/jandex/test/GenericSignatureReconstructionTest.java new file mode 100644 index 00000000..d82a7961 --- /dev/null +++ b/core/src/test/java/org/jboss/jandex/test/GenericSignatureReconstructionTest.java @@ -0,0 +1,437 @@ +package org.jboss.jandex.test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +import java.io.IOException; +import java.util.function.Function; + +import org.jboss.jandex.ClassInfo; +import org.jboss.jandex.ClassType; +import org.jboss.jandex.DotName; +import org.jboss.jandex.FieldInfo; +import org.jboss.jandex.GenericSignature; +import org.jboss.jandex.Index; +import org.jboss.jandex.IndexView; +import org.jboss.jandex.MethodInfo; +import org.jboss.jandex.Type; +import org.jboss.jandex.test.data.ClassInheritance; +import org.jboss.jandex.test.data.OuterParam; +import org.jboss.jandex.test.data.OuterParamBound; +import org.jboss.jandex.test.data.OuterRaw; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +public class GenericSignatureReconstructionTest { + private static IndexView index; + + @BeforeAll + public static void setUp() { + try { + index = Index.of( + OuterRaw.class, + OuterRaw.InnerRaw.class, OuterRaw.InnerParam.class, OuterRaw.InnerParamBound.class, + OuterRaw.NestedRaw.class, OuterRaw.NestedParam.class, OuterRaw.NestedParamBound.class, + OuterParam.class, + OuterParam.InnerRaw.class, OuterParam.InnerParam.class, OuterParam.InnerParamBound.class, + OuterParam.NestedRaw.class, OuterParam.NestedParam.class, OuterParam.NestedParamBound.class, + OuterParamBound.class, + OuterParamBound.InnerRaw.class, OuterParamBound.InnerParam.class, OuterParamBound.InnerParamBound.class, + OuterParamBound.NestedRaw.class, OuterParamBound.NestedParam.class, OuterParamBound.NestedParamBound.class, + ClassInheritance.class, + ClassInheritance.PlainClass.class, + ClassInheritance.ParamClass.class, + ClassInheritance.ParamBoundClass.class, + ClassInheritance.PlainInterface.class, + ClassInheritance.ParamInterface.class, + ClassInheritance.ParamBoundInterface.class, + ClassInheritance.PlainClassExtendsPlainClass.class, + ClassInheritance.PlainClassExtendsParamClass.class, + ClassInheritance.PlainClassExtendsParamBoundClass.class, + ClassInheritance.ParamClassExtendsPlainClass.class, + ClassInheritance.ParamClassExtendsParamClass1.class, + ClassInheritance.ParamClassExtendsParamClass2.class, + ClassInheritance.ParamClassExtendsParamBoundClass.class, + ClassInheritance.ParamBoundClassExtendsPlainClass.class, + ClassInheritance.ParamBoundClassExtendsParamClass1.class, + ClassInheritance.ParamBoundClassExtendsParamClass2.class, + ClassInheritance.ParamBoundClassExtendsParamBoundClass1.class, + ClassInheritance.ParamBoundClassExtendsParamBoundClass2.class, + ClassInheritance.PlainClassImplementsPlainInterface.class, + ClassInheritance.PlainClassImplementsParamInterface.class, + ClassInheritance.PlainClassImplementsParamBoundInterface.class, + ClassInheritance.ParamClassImplementsPlainInterface.class, + ClassInheritance.ParamClassImplementsParamInterface1.class, + ClassInheritance.ParamClassImplementsParamInterface2.class, + ClassInheritance.ParamClassImplementsParamBoundInterface.class, + ClassInheritance.ParamBoundClassImplementsPlainInterface.class, + ClassInheritance.ParamBoundClassImplementsParamInterface1.class, + ClassInheritance.ParamBoundClassImplementsParamInterface2.class, + ClassInheritance.ParamBoundClassImplementsParamBoundInterface1.class, + ClassInheritance.ParamBoundClassImplementsParamBoundInterface2.class, + ClassInheritance.PlainClassExtendsPlainClassImplementsPlainInterface.class, + ClassInheritance.PlainClassExtendsParamClassImplementsParamInterface.class, + ClassInheritance.PlainClassExtendsParamBoundClassImplementsParamBoundInterface.class, + ClassInheritance.ParamClassExtendsPlainClassImplementsPlainInterface.class, + ClassInheritance.ParamClassExtendsParamClassImplementsParamInterface1.class, + ClassInheritance.ParamClassExtendsParamClassImplementsParamInterface2.class, + ClassInheritance.ParamClassExtendsParamBoundClassImplementsParamBoundInterface.class, + ClassInheritance.ParamBoundClassExtendsPlainClassImplementsPlainInterface.class, + ClassInheritance.ParamBoundClassExtendsParamClassImplementsParamInterface1.class, + ClassInheritance.ParamBoundClassExtendsParamClassImplementsParamInterface2.class, + ClassInheritance.ParamBoundClassExtendsParamBoundClassImplementsParamBoundInterface1.class, + ClassInheritance.ParamBoundClassExtendsParamBoundClassImplementsParamBoundInterface2.class, + ClassInheritance.OuterParam.class, + ClassInheritance.OuterParam.NestedParam.class, + ClassInheritance.OuterParam.InnerParam.class, + ClassInheritance.OuterParam.InnerParam.InnerInnerRaw.class, + ClassInheritance.OuterParam.InnerParam.InnerInnerRaw.InnerInnerInnerParam.class, + ClassInheritance.OuterParam.InnerParam.InnerInnerRaw.InnerInnerInnerParam.Test.class); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + // expected signatures here were obtained from classes compiled with OpenJDK 11.0.17 + // and then dumped using `javap -v` + + @Test + public void outerRaw() { + assertClassSignature(OuterRaw.class, + null); + assertClassSignature(OuterRaw.NestedRaw.class, + null); + assertClassSignature(OuterRaw.NestedParam.class, + "Ljava/lang/Object;"); + assertClassSignature(OuterRaw.NestedParamBound.class, + ";>Ljava/lang/Object;"); + assertClassSignature(OuterRaw.InnerRaw.class, + null); + assertClassSignature(OuterRaw.InnerParam.class, + "Ljava/lang/Object;"); + assertClassSignature(OuterRaw.InnerParamBound.class, + ";>Ljava/lang/Object;"); + + assertMethodSignature(OuterRaw.class, "methodA", + null); + assertMethodSignature(OuterRaw.class, "methodB", + ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(TU;Lorg/jboss/jandex/test/data/OuterRaw;)TT;"); + assertMethodSignature(OuterRaw.NestedRaw.class, "methodC", + ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(Ljava/util/List<+TU;>;Lorg/jboss/jandex/test/data/OuterRaw$NestedRaw;)TT;^Ljava/lang/IllegalArgumentException;^TV;"); + assertMethodSignature(OuterRaw.NestedParam.class, "methodD", + ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(Ljava/util/List<-TU;>;TX;Lorg/jboss/jandex/test/data/OuterRaw$NestedParam;)TT;"); + assertMethodSignature(OuterRaw.NestedParamBound.class, "methodE", + ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(Ljava/util/List<*>;TX;Lorg/jboss/jandex/test/data/OuterRaw$NestedParamBound;)TT;^TV;"); + assertMethodSignature(OuterRaw.InnerRaw.class, "methodF", + ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(Ljava/util/List<+TU;>;Lorg/jboss/jandex/test/data/OuterRaw$InnerRaw;)TT;^Ljava/lang/IllegalArgumentException;^TV;"); + assertMethodSignature(OuterRaw.InnerParam.class, "methodG", + ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(Ljava/util/List<-TU;>;TX;Lorg/jboss/jandex/test/data/OuterRaw$InnerParam;)TT;"); + assertMethodSignature(OuterRaw.InnerParamBound.class, "methodH", + ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(Ljava/util/List<*>;TX;Lorg/jboss/jandex/test/data/OuterRaw$InnerParamBound;)TT;^TV;"); + + assertFieldSignature(OuterRaw.class, "fieldA", + null); + assertFieldSignature(OuterRaw.class, "fieldB", + "Ljava/util/List;"); + assertFieldSignature(OuterRaw.class, "fieldC", + "Ljava/util/List<*>;"); + assertFieldSignature(OuterRaw.class, "fieldD", + "Ljava/util/List<+Ljava/lang/CharSequence;>;"); + assertFieldSignature(OuterRaw.class, "fieldE", + "Ljava/util/List<-Ljava/lang/String;>;"); + assertFieldSignature(OuterRaw.NestedParam.class, "fieldF", + "TX;"); + assertFieldSignature(OuterRaw.NestedParam.class, "fieldG", + "Lorg/jboss/jandex/test/data/OuterRaw$NestedParam;"); + assertFieldSignature(OuterRaw.NestedParamBound.class, "fieldH", + "Ljava/util/List;"); + assertFieldSignature(OuterRaw.NestedParamBound.class, "fieldI", + "Lorg/jboss/jandex/test/data/OuterRaw$NestedParamBound;"); + assertFieldSignature(OuterRaw.InnerParam.class, "fieldJ", + "TX;"); + assertFieldSignature(OuterRaw.InnerParam.class, "fieldK", + "Lorg/jboss/jandex/test/data/OuterRaw$InnerParam;"); + assertFieldSignature(OuterRaw.InnerParamBound.class, "fieldL", + "Ljava/util/List;"); + assertFieldSignature(OuterRaw.InnerParamBound.class, "fieldM", + "Lorg/jboss/jandex/test/data/OuterRaw$InnerParamBound;"); + } + + @Test + public void outerParam() { + assertClassSignature(OuterParam.class, + "Ljava/lang/Object;"); + assertClassSignature(OuterParam.NestedRaw.class, + null); + assertClassSignature(OuterParam.NestedParam.class, + "Ljava/lang/Object;"); + assertClassSignature(OuterParam.NestedParamBound.class, + ";>Ljava/lang/Object;"); + assertClassSignature(OuterParam.InnerRaw.class, + null); + assertClassSignature(OuterParam.InnerParam.class, + "Ljava/lang/Object;"); + assertClassSignature(OuterParam.InnerParamBound.class, + ";>Ljava/lang/Object;"); + + assertMethodSignature(OuterParam.class, "methodA", + "(Ljava/lang/String;TW;Lorg/jboss/jandex/test/data/OuterParam;)Ljava/lang/String;"); + assertMethodSignature(OuterParam.class, "methodB", + ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(TU;TW;Lorg/jboss/jandex/test/data/OuterParam;)TT;"); + assertMethodSignature(OuterParam.NestedRaw.class, "methodC", + ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(Ljava/util/List<+TU;>;Lorg/jboss/jandex/test/data/OuterParam$NestedRaw;)TT;^Ljava/lang/IllegalArgumentException;^TV;"); + assertMethodSignature(OuterParam.NestedParam.class, "methodD", + ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(Ljava/util/List<-TU;>;TX;Lorg/jboss/jandex/test/data/OuterParam$NestedParam;)TT;"); + assertMethodSignature(OuterParam.NestedParamBound.class, "methodE", + ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(Ljava/util/List<*>;TX;Lorg/jboss/jandex/test/data/OuterParam$NestedParamBound;)TT;^TV;"); + assertMethodSignature(OuterParam.InnerRaw.class, "methodF", + ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(Ljava/util/List<+TU;>;TW;Lorg/jboss/jandex/test/data/OuterParam.InnerRaw;)TT;^Ljava/lang/IllegalArgumentException;^TV;"); + assertMethodSignature(OuterParam.InnerParam.class, "methodG", + ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(Ljava/util/List<-TU;>;TX;TW;Lorg/jboss/jandex/test/data/OuterParam.InnerParam;)TT;"); + assertMethodSignature(OuterParam.InnerParamBound.class, "methodH", + ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(Ljava/util/List<*>;TX;TW;Lorg/jboss/jandex/test/data/OuterParam.InnerParamBound;)TT;^TV;"); + + assertFieldSignature(OuterParam.class, "fieldA", + "TW;"); + assertFieldSignature(OuterParam.class, "fieldB", + "Ljava/util/List;"); + assertFieldSignature(OuterParam.class, "fieldC", + "Ljava/util/List<+TW;>;"); + assertFieldSignature(OuterParam.class, "fieldD", + "Ljava/util/List<-TW;>;"); + assertFieldSignature(OuterParam.InnerRaw.class, "fieldE", + "Ljava/util/List.InnerRaw;>;"); + assertFieldSignature(OuterParam.InnerParam.class, "fieldF", + "Ljava/util/List.InnerParam<*>;>;"); + assertFieldSignature(OuterParam.InnerParamBound.class, "fieldG", + "Ljava/util/Map<+TX;TW;>;"); + assertFieldSignature(OuterParam.InnerParamBound.class, "fieldH", + "Ljava/util/Map<-TX;Lorg/jboss/jandex/test/data/OuterParam.InnerParamBound;>;"); + } + + @Test + public void outerParamBound() { + assertClassSignature(OuterParamBound.class, + ";>Ljava/lang/Object;"); + assertClassSignature(OuterParamBound.NestedRaw.class, + null); + assertClassSignature(OuterParamBound.NestedParam.class, + "Ljava/lang/Object;"); + assertClassSignature(OuterParamBound.NestedParamBound.class, + ";>Ljava/lang/Object;"); + assertClassSignature(OuterParamBound.InnerRaw.class, + null); + assertClassSignature(OuterParamBound.InnerParam.class, + "Ljava/lang/Object;"); + assertClassSignature(OuterParamBound.InnerParamBound.class, + ";>Ljava/lang/Object;"); + + assertMethodSignature(OuterParamBound.class, "methodA", + "(Ljava/lang/String;TW;Lorg/jboss/jandex/test/data/OuterParamBound;)Ljava/lang/String;"); + assertMethodSignature(OuterParamBound.class, "methodB", + ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(TU;TW;Lorg/jboss/jandex/test/data/OuterParamBound;)TT;"); + assertMethodSignature(OuterParamBound.NestedRaw.class, "methodC", + ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(Ljava/util/List<+TU;>;Lorg/jboss/jandex/test/data/OuterParamBound$NestedRaw;)TT;^Ljava/lang/IllegalArgumentException;^TV;"); + assertMethodSignature(OuterParamBound.NestedParam.class, "methodD", + ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(Ljava/util/List<-TU;>;TX;Lorg/jboss/jandex/test/data/OuterParamBound$NestedParam;)TT;"); + assertMethodSignature(OuterParamBound.NestedParamBound.class, "methodE", + ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(Ljava/util/List<*>;TX;Lorg/jboss/jandex/test/data/OuterParamBound$NestedParamBound;)TT;^TV;"); + assertMethodSignature(OuterParamBound.InnerRaw.class, "methodF", + ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(Ljava/util/List<+TU;>;TW;Lorg/jboss/jandex/test/data/OuterParamBound.InnerRaw;)TT;^Ljava/lang/IllegalArgumentException;^TV;"); + assertMethodSignature(OuterParamBound.InnerParam.class, "methodG", + ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(Ljava/util/List<-TU;>;TX;TW;Lorg/jboss/jandex/test/data/OuterParamBound.InnerParam;)TT;"); + assertMethodSignature(OuterParamBound.InnerParamBound.class, "methodH", + ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(Ljava/util/List<*>;TX;TW;Lorg/jboss/jandex/test/data/OuterParamBound.InnerParamBound;)TT;^TV;"); + + assertFieldSignature(OuterParamBound.class, "fieldA", + "TW;"); + assertFieldSignature(OuterParamBound.class, "fieldB", + "Ljava/util/List;"); + assertFieldSignature(OuterParamBound.class, "fieldC", + "Ljava/util/List<+TW;>;"); + assertFieldSignature(OuterParamBound.class, "fieldD", + "Ljava/util/List<-TW;>;"); + assertFieldSignature(OuterParamBound.InnerRaw.class, "fieldE", + "Ljava/util/List.InnerParam<-Ljava/lang/String;>;>;"); + assertFieldSignature(OuterParamBound.InnerParam.class, "fieldF", + "Ljava/util/List.InnerParam<+TW;>;>;"); + assertFieldSignature(OuterParamBound.InnerParam.class, "fieldG", + "Ljava/util/Map;"); + assertFieldSignature(OuterParamBound.InnerParamBound.class, "fieldH", + "Ljava/util/List.InnerParamBound;>;"); + assertFieldSignature(OuterParamBound.InnerParamBound.class, "fieldI", + "Ljava/util/Map<+TX;TW;>;"); + } + + @Test + public void classInheritance() { + assertClassSignature(ClassInheritance.PlainClass.class, + null); + assertClassSignature(ClassInheritance.ParamClass.class, + "Ljava/lang/Object;"); + assertClassSignature(ClassInheritance.ParamBoundClass.class, + ";>Ljava/lang/Object;"); + assertClassSignature(ClassInheritance.PlainInterface.class, + null); + assertClassSignature(ClassInheritance.ParamInterface.class, + "Ljava/lang/Object;"); + assertClassSignature(ClassInheritance.ParamBoundInterface.class, + ";:Ljava/io/Serializable;>Ljava/lang/Object;"); + assertClassSignature(ClassInheritance.PlainClassExtendsPlainClass.class, + null); + assertClassSignature(ClassInheritance.PlainClassExtendsParamClass.class, + "Lorg/jboss/jandex/test/data/ClassInheritance$ParamClass;"); + assertClassSignature(ClassInheritance.PlainClassExtendsParamBoundClass.class, + "Lorg/jboss/jandex/test/data/ClassInheritance$ParamBoundClass;"); + assertClassSignature(ClassInheritance.ParamClassExtendsPlainClass.class, + "Lorg/jboss/jandex/test/data/ClassInheritance$PlainClass;"); + assertClassSignature(ClassInheritance.ParamClassExtendsParamClass1.class, + "Lorg/jboss/jandex/test/data/ClassInheritance$ParamClass;"); + assertClassSignature(ClassInheritance.ParamClassExtendsParamClass2.class, + "Lorg/jboss/jandex/test/data/ClassInheritance$ParamClass;"); + assertClassSignature(ClassInheritance.ParamClassExtendsParamBoundClass.class, + "Lorg/jboss/jandex/test/data/ClassInheritance$ParamBoundClass;"); + assertClassSignature(ClassInheritance.ParamBoundClassExtendsPlainClass.class, + ";>Lorg/jboss/jandex/test/data/ClassInheritance$PlainClass;"); + assertClassSignature(ClassInheritance.ParamBoundClassExtendsParamClass1.class, + ";>Lorg/jboss/jandex/test/data/ClassInheritance$ParamClass;"); + assertClassSignature(ClassInheritance.ParamBoundClassExtendsParamClass2.class, + ";>Lorg/jboss/jandex/test/data/ClassInheritance$ParamClass;"); + assertClassSignature(ClassInheritance.ParamBoundClassExtendsParamBoundClass1.class, + ";>Lorg/jboss/jandex/test/data/ClassInheritance$ParamBoundClass;"); + assertClassSignature(ClassInheritance.ParamBoundClassExtendsParamBoundClass2.class, + ";>Lorg/jboss/jandex/test/data/ClassInheritance$ParamBoundClass;"); + assertClassSignature(ClassInheritance.PlainClassImplementsPlainInterface.class, + null); + assertClassSignature(ClassInheritance.PlainClassImplementsParamInterface.class, + "Ljava/lang/Object;Lorg/jboss/jandex/test/data/ClassInheritance$ParamInterface;"); + assertClassSignature(ClassInheritance.PlainClassImplementsParamBoundInterface.class, + "Ljava/lang/Object;Lorg/jboss/jandex/test/data/ClassInheritance$ParamBoundInterface;"); + assertClassSignature(ClassInheritance.ParamClassImplementsPlainInterface.class, + "Ljava/lang/Object;Lorg/jboss/jandex/test/data/ClassInheritance$PlainInterface;"); + assertClassSignature(ClassInheritance.ParamClassImplementsParamInterface1.class, + "Ljava/lang/Object;Lorg/jboss/jandex/test/data/ClassInheritance$ParamInterface;"); + assertClassSignature(ClassInheritance.ParamClassImplementsParamInterface2.class, + "Ljava/lang/Object;Lorg/jboss/jandex/test/data/ClassInheritance$ParamInterface;"); + assertClassSignature(ClassInheritance.ParamClassImplementsParamBoundInterface.class, + "Ljava/lang/Object;Lorg/jboss/jandex/test/data/ClassInheritance$ParamBoundInterface;"); + assertClassSignature(ClassInheritance.ParamBoundClassImplementsPlainInterface.class, + "Ljava/lang/Object;Lorg/jboss/jandex/test/data/ClassInheritance$PlainInterface;"); + assertClassSignature(ClassInheritance.ParamBoundClassImplementsParamInterface1.class, + "Ljava/lang/Object;Lorg/jboss/jandex/test/data/ClassInheritance$ParamInterface;"); + assertClassSignature(ClassInheritance.ParamBoundClassImplementsParamInterface2.class, + "Ljava/lang/Object;Lorg/jboss/jandex/test/data/ClassInheritance$ParamInterface;"); + assertClassSignature(ClassInheritance.ParamBoundClassImplementsParamBoundInterface1.class, + "Ljava/lang/Object;Lorg/jboss/jandex/test/data/ClassInheritance$ParamBoundInterface;"); + assertClassSignature(ClassInheritance.ParamBoundClassImplementsParamBoundInterface2.class, + "Ljava/lang/Object;Lorg/jboss/jandex/test/data/ClassInheritance$ParamBoundInterface;"); + assertClassSignature(ClassInheritance.PlainClassExtendsPlainClassImplementsPlainInterface.class, + null); + assertClassSignature(ClassInheritance.PlainClassExtendsParamClassImplementsParamInterface.class, + "Lorg/jboss/jandex/test/data/ClassInheritance$ParamClass;Lorg/jboss/jandex/test/data/ClassInheritance$ParamInterface;"); + assertClassSignature(ClassInheritance.PlainClassExtendsParamBoundClassImplementsParamBoundInterface.class, + "Lorg/jboss/jandex/test/data/ClassInheritance$ParamBoundClass;Lorg/jboss/jandex/test/data/ClassInheritance$ParamBoundInterface;"); + assertClassSignature(ClassInheritance.ParamClassExtendsPlainClassImplementsPlainInterface.class, + "Lorg/jboss/jandex/test/data/ClassInheritance$PlainClass;Lorg/jboss/jandex/test/data/ClassInheritance$PlainInterface;"); + assertClassSignature(ClassInheritance.ParamClassExtendsParamClassImplementsParamInterface1.class, + "Lorg/jboss/jandex/test/data/ClassInheritance$ParamClass;Lorg/jboss/jandex/test/data/ClassInheritance$ParamInterface;"); + assertClassSignature(ClassInheritance.ParamClassExtendsParamClassImplementsParamInterface2.class, + "Lorg/jboss/jandex/test/data/ClassInheritance$ParamClass;Lorg/jboss/jandex/test/data/ClassInheritance$ParamInterface;"); + assertClassSignature(ClassInheritance.ParamClassExtendsParamBoundClassImplementsParamBoundInterface.class, + "Lorg/jboss/jandex/test/data/ClassInheritance$ParamBoundClass;Lorg/jboss/jandex/test/data/ClassInheritance$ParamBoundInterface;"); + assertClassSignature(ClassInheritance.ParamBoundClassExtendsPlainClassImplementsPlainInterface.class, + ";>Lorg/jboss/jandex/test/data/ClassInheritance$PlainClass;Lorg/jboss/jandex/test/data/ClassInheritance$PlainInterface;"); + assertClassSignature(ClassInheritance.ParamBoundClassExtendsParamClassImplementsParamInterface1.class, + ";>Lorg/jboss/jandex/test/data/ClassInheritance$ParamClass;Lorg/jboss/jandex/test/data/ClassInheritance$ParamInterface;"); + assertClassSignature(ClassInheritance.ParamBoundClassExtendsParamClassImplementsParamInterface2.class, + ";>Lorg/jboss/jandex/test/data/ClassInheritance$ParamClass;Lorg/jboss/jandex/test/data/ClassInheritance$ParamInterface;"); + assertClassSignature(ClassInheritance.ParamBoundClassExtendsParamBoundClassImplementsParamBoundInterface1.class, + ";>Lorg/jboss/jandex/test/data/ClassInheritance$ParamBoundClass;Lorg/jboss/jandex/test/data/ClassInheritance$ParamBoundInterface;"); + assertClassSignature(ClassInheritance.ParamBoundClassExtendsParamBoundClassImplementsParamBoundInterface2.class, + ";>Lorg/jboss/jandex/test/data/ClassInheritance$ParamBoundClass;Lorg/jboss/jandex/test/data/ClassInheritance$ParamBoundInterface;"); + + assertClassSignature(ClassInheritance.OuterParam.InnerParam.InnerInnerRaw.InnerInnerInnerParam.Test.class, + "Lorg/jboss/jandex/test/data/ClassInheritance$OuterParam.InnerParam.InnerInnerRaw.InnerInnerInnerParam;Lorg/jboss/jandex/test/data/ClassInheritance$OuterParam$NestedParam;"); + } + + @Test + public void withSubstitution() { + assertClassSignature(OuterParamBound.class, + ";>Ljava/lang/Object;", + id -> "W".equals(id) ? ClassType.create(DotName.STRING_NAME) : null); + assertClassSignature(ClassInheritance.ParamBoundInterface.class, + ";:Ljava/io/Serializable;>Ljava/lang/Object;", + id -> "T".equals(id) ? ClassType.create(DotName.STRING_NAME) : null); + assertClassSignature(ClassInheritance.ParamBoundClassExtendsParamClassImplementsParamInterface2.class, + ";>Lorg/jboss/jandex/test/data/ClassInheritance$ParamClass;Lorg/jboss/jandex/test/data/ClassInheritance$ParamInterface;", + id -> "T".equals(id) ? ClassType.create(DotName.STRING_NAME) : null); + + assertMethodSignature(OuterRaw.class, "methodB", + ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(Ljava/lang/String;Lorg/jboss/jandex/test/data/OuterRaw;)TT;", + id -> "U".equals(id) ? ClassType.create(DotName.STRING_NAME) : null); + assertMethodSignature(OuterRaw.class, "methodB", + ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(Ljava/lang/String;Lorg/jboss/jandex/test/data/OuterRaw;)Ljava/lang/String;", + id -> "T".equals(id) || "U".equals(id) ? ClassType.create(DotName.STRING_NAME) : null); + + assertFieldSignature(OuterRaw.InnerParamBound.class, "fieldL", + "Ljava/util/List;", + id -> "X".equals(id) ? ClassType.create(DotName.STRING_NAME) : null); + assertFieldSignature(OuterRaw.InnerParamBound.class, "fieldM", + "Lorg/jboss/jandex/test/data/OuterRaw$InnerParamBound;", + id -> "X".equals(id) ? ClassType.create(DotName.STRING_NAME) : null); + } + + private static void assertClassSignature(Class clazz, String expectedSignature) { + assertClassSignature(clazz, expectedSignature, GenericSignature.NO_SUBSTITUTION); + } + + private static void assertClassSignature(Class clazz, String expectedSignature, + Function subst) { + ClassInfo classInfo = index.getClassByName(clazz); + if (classInfo != null) { + String actualSignature = classInfo.genericSignatureIfRequired(subst); + assertEquals(expectedSignature, actualSignature); + return; + } + + fail("Couldn't find class " + clazz.getName() + " in test index"); + } + + private static void assertMethodSignature(Class clazz, String method, String expectedSignature) { + assertMethodSignature(clazz, method, expectedSignature, GenericSignature.NO_SUBSTITUTION); + } + + private static void assertMethodSignature(Class clazz, String method, String expectedSignature, + Function subst) { + ClassInfo classInfo = index.getClassByName(clazz); + if (classInfo != null) { + MethodInfo methodInfo = classInfo.firstMethod(method); + if (methodInfo != null) { + String actualSignature = methodInfo.genericSignatureIfRequired(subst); + assertEquals(expectedSignature, actualSignature); + return; + } + } + + fail("Couldn't find method " + clazz.getName() + "#" + method + " in test index"); + } + + private static void assertFieldSignature(Class clazz, String field, String expectedSignature) { + assertFieldSignature(clazz, field, expectedSignature, GenericSignature.NO_SUBSTITUTION); + } + + private static void assertFieldSignature(Class clazz, String field, String expectedSignature, + Function subst) { + ClassInfo classInfo = index.getClassByName(clazz); + if (classInfo != null) { + FieldInfo fieldInfo = classInfo.field(field); + if (fieldInfo != null) { + String actualSignature = fieldInfo.genericSignatureIfRequired(subst); + assertEquals(expectedSignature, actualSignature); + return; + } + } + + fail("Couldn't find field " + clazz.getName() + "#" + field + " in test index"); + } +} diff --git a/core/src/test/java/org/jboss/jandex/test/data/ClassInheritance.java b/core/src/test/java/org/jboss/jandex/test/data/ClassInheritance.java new file mode 100644 index 00000000..3e845556 --- /dev/null +++ b/core/src/test/java/org/jboss/jandex/test/data/ClassInheritance.java @@ -0,0 +1,203 @@ +package org.jboss.jandex.test.data; + +import java.io.Serializable; + +public class ClassInheritance { + public static class PlainClass { + } + + public static class ParamClass { + } + + public static class ParamBoundClass> { + } + + public interface PlainInterface { + } + + public interface ParamInterface { + } + + public interface ParamBoundInterface & Serializable> { + } + + // --- + + public static class PlainClassExtendsPlainClass + extends PlainClass { + } + + public static class PlainClassExtendsParamClass + extends ParamClass { + } + + public static class PlainClassExtendsParamBoundClass + extends ParamBoundClass { + } + + public static class ParamClassExtendsPlainClass + extends PlainClass { + } + + public static class ParamClassExtendsParamClass1 + extends ParamClass { + } + + public static class ParamClassExtendsParamClass2 + extends ParamClass { + } + + public static class ParamClassExtendsParamBoundClass + extends ParamBoundClass { + } + + public static class ParamBoundClassExtendsPlainClass> + extends PlainClass { + } + + public static class ParamBoundClassExtendsParamClass1> + extends ParamClass { + } + + public static class ParamBoundClassExtendsParamClass2> + extends ParamClass { + } + + public static class ParamBoundClassExtendsParamBoundClass1> + extends ParamBoundClass { + } + + public static class ParamBoundClassExtendsParamBoundClass2> + extends ParamBoundClass { + } + + // --- + + public static class PlainClassImplementsPlainInterface + implements PlainInterface { + } + + public static class PlainClassImplementsParamInterface + implements ParamInterface { + } + + public static class PlainClassImplementsParamBoundInterface + implements ParamBoundInterface { + } + + public static class ParamClassImplementsPlainInterface + implements PlainInterface { + } + + public static class ParamClassImplementsParamInterface1 + implements ParamInterface { + } + + public static class ParamClassImplementsParamInterface2 + implements ParamInterface { + } + + public static class ParamClassImplementsParamBoundInterface + implements ParamBoundInterface { + } + + public static class ParamBoundClassImplementsPlainInterface + implements PlainInterface { + } + + public static class ParamBoundClassImplementsParamInterface1 + implements ParamInterface { + } + + public static class ParamBoundClassImplementsParamInterface2 + implements ParamInterface { + } + + public static class ParamBoundClassImplementsParamBoundInterface1 + implements ParamBoundInterface { + } + + public static class ParamBoundClassImplementsParamBoundInterface2 + implements ParamBoundInterface { + } + + // --- + + public static class PlainClassExtendsPlainClassImplementsPlainInterface + extends PlainClass + implements PlainInterface { + } + + public static class PlainClassExtendsParamClassImplementsParamInterface + extends ParamClass + implements ParamInterface { + } + + public static class PlainClassExtendsParamBoundClassImplementsParamBoundInterface + extends ParamBoundClass + implements ParamBoundInterface { + } + + public static class ParamClassExtendsPlainClassImplementsPlainInterface + extends PlainClass + implements PlainInterface { + } + + public static class ParamClassExtendsParamClassImplementsParamInterface1 + extends ParamClass + implements ParamInterface { + } + + public static class ParamClassExtendsParamClassImplementsParamInterface2 + extends ParamClass + implements ParamInterface { + } + + public static class ParamClassExtendsParamBoundClassImplementsParamBoundInterface + extends ParamBoundClass + implements ParamBoundInterface { + } + + public static class ParamBoundClassExtendsPlainClassImplementsPlainInterface> + extends PlainClass + implements PlainInterface { + } + + public static class ParamBoundClassExtendsParamClassImplementsParamInterface1> + extends ParamClass + implements ParamInterface { + } + + public static class ParamBoundClassExtendsParamClassImplementsParamInterface2> + extends ParamClass + implements ParamInterface { + } + + public static class ParamBoundClassExtendsParamBoundClassImplementsParamBoundInterface1> + extends ParamBoundClass + implements ParamBoundInterface { + } + + public static class ParamBoundClassExtendsParamBoundClassImplementsParamBoundInterface2> + extends ParamBoundClass + implements ParamBoundInterface { + } + + // --- + + public static class OuterParam { + public interface NestedParam { + } + + public class InnerParam { + public class InnerInnerRaw { + public class InnerInnerInnerParam { + public class Test + extends OuterParam.InnerParam.InnerInnerRaw.InnerInnerInnerParam + implements NestedParam { + } + } + } + } + } +} diff --git a/core/src/test/java/org/jboss/jandex/test/data/OuterParam.java b/core/src/test/java/org/jboss/jandex/test/data/OuterParam.java new file mode 100644 index 00000000..99d5d163 --- /dev/null +++ b/core/src/test/java/org/jboss/jandex/test/data/OuterParam.java @@ -0,0 +1,72 @@ +package org.jboss.jandex.test.data; + +import java.util.List; +import java.util.Map; + +public class OuterParam { + public W fieldA; + + public List fieldB; + + public List fieldC; + + public List fieldD; + + public String methodA(String arg, W arg2, OuterParam self) throws IllegalArgumentException { + return null; + } + + public , U extends Comparable, V extends Exception> T methodB( + U arg, W arg2, OuterParam self) { + return null; + } + + public static class NestedRaw { + public , U extends Comparable, V extends Exception> T methodC( + List arg, NestedRaw self) throws IllegalArgumentException, V { + return null; + } + } + + public static class NestedParam { + public , U extends Comparable, V extends Exception> T methodD( + List arg, X arg2, NestedParam self) throws IllegalArgumentException { + return null; + } + } + + public static class NestedParamBound> { + public , U extends Comparable, V extends Exception> T methodE( + List arg, X arg2, NestedParamBound self) throws V { + return null; + } + } + + public class InnerRaw { + public List.InnerRaw> fieldE; + + public , U extends Comparable, V extends Exception> T methodF( + List arg, W arg2, InnerRaw self) throws IllegalArgumentException, V { + return null; + } + } + + public class InnerParam { + public List.InnerParam> fieldF; + + public , U extends Comparable, V extends Exception> T methodG( + List arg, X arg2, W arg3, InnerParam self) throws IllegalArgumentException { + return null; + } + } + + public class InnerParamBound> { + public Map fieldG; + public Map.InnerParamBound> fieldH; + + public , U extends Comparable, V extends Exception> T methodH( + List arg, X arg2, W arg3, InnerParamBound self) throws V { + return null; + } + } +} diff --git a/core/src/test/java/org/jboss/jandex/test/data/OuterParamBound.java b/core/src/test/java/org/jboss/jandex/test/data/OuterParamBound.java new file mode 100644 index 00000000..36bcbc8d --- /dev/null +++ b/core/src/test/java/org/jboss/jandex/test/data/OuterParamBound.java @@ -0,0 +1,73 @@ +package org.jboss.jandex.test.data; + +import java.util.List; +import java.util.Map; + +public class OuterParamBound> { + public W fieldA; + + public List fieldB; + + public List fieldC; + + public List fieldD; + + public String methodA(String arg, W arg2, OuterParamBound self) throws IllegalArgumentException { + return null; + } + + public , U extends Comparable, V extends Exception> T methodB( + U arg, W arg2, OuterParamBound self) { + return null; + } + + public static class NestedRaw { + public , U extends Comparable, V extends Exception> T methodC( + List arg, NestedRaw self) throws IllegalArgumentException, V { + return null; + } + } + + public static class NestedParam { + public , U extends Comparable, V extends Exception> T methodD( + List arg, X arg2, NestedParam self) throws IllegalArgumentException { + return null; + } + } + + public static class NestedParamBound> { + public , U extends Comparable, V extends Exception> T methodE( + List arg, X arg2, NestedParamBound self) throws V { + return null; + } + } + + public class InnerRaw { + public List.InnerParam> fieldE; + + public , U extends Comparable, V extends Exception> T methodF( + List arg, W arg2, InnerRaw self) throws IllegalArgumentException, V { + return null; + } + } + + public class InnerParam { + public List.InnerParam> fieldF; + public Map fieldG; + + public , U extends Comparable, V extends Exception> T methodG( + List arg, X arg2, W arg3, InnerParam self) throws IllegalArgumentException { + return null; + } + } + + public class InnerParamBound> { + public List.InnerParamBound> fieldH; + public Map fieldI; + + public , U extends Comparable, V extends Exception> T methodH( + List arg, X arg2, W arg3, InnerParamBound self) throws V { + return null; + } + } +} diff --git a/core/src/test/java/org/jboss/jandex/test/data/OuterRaw.java b/core/src/test/java/org/jboss/jandex/test/data/OuterRaw.java new file mode 100644 index 00000000..d2356ec4 --- /dev/null +++ b/core/src/test/java/org/jboss/jandex/test/data/OuterRaw.java @@ -0,0 +1,78 @@ +package org.jboss.jandex.test.data; + +import java.util.List; + +public class OuterRaw { + public String fieldA; + + public List fieldB; + + public List fieldC; + + public List fieldD; + + public List fieldE; + + public String methodA(String arg, OuterRaw self) throws IllegalArgumentException { + return null; + } + + public , U extends Comparable, V extends Exception> T methodB( + U arg, OuterRaw self) { + return null; + } + + public static class NestedRaw { + public , U extends Comparable, V extends Exception> T methodC( + List arg, NestedRaw self) throws IllegalArgumentException, V { + return null; + } + } + + public static class NestedParam { + public X fieldF; + public NestedParam fieldG; + + public , U extends Comparable, V extends Exception> T methodD( + List arg, X arg2, NestedParam self) throws IllegalArgumentException { + return null; + } + } + + public static class NestedParamBound> { + public List fieldH; + public NestedParamBound fieldI; + + public , U extends Comparable, V extends Exception> T methodE( + List arg, X arg2, NestedParamBound self) throws V { + return null; + } + } + + public class InnerRaw { + public , U extends Comparable, V extends Exception> T methodF( + List arg, InnerRaw self) throws IllegalArgumentException, V { + return null; + } + } + + public class InnerParam { + public X fieldJ; + public InnerParam fieldK; + + public , U extends Comparable, V extends Exception> T methodG( + List arg, X arg2, InnerParam self) throws IllegalArgumentException { + return null; + } + } + + public class InnerParamBound> { + public List fieldL; + public InnerParamBound fieldM; + + public , U extends Comparable, V extends Exception> T methodH( + List arg, X arg2, InnerParamBound self) throws V { + return null; + } + } +}