diff --git a/core/src/main/java/org/jboss/jandex/DotName.java b/core/src/main/java/org/jboss/jandex/DotName.java index e0231688..5fb129b4 100644 --- a/core/src/main/java/org/jboss/jandex/DotName.java +++ b/core/src/main/java/org/jboss/jandex/DotName.java @@ -46,9 +46,9 @@ public final class DotName implements Comparable { static final DotName JAVA_NAME; static final DotName JAVA_LANG_NAME; - static final DotName OBJECT_NAME; - static final DotName ENUM_NAME; - static final DotName RECORD_NAME; + public static final DotName OBJECT_NAME; + public static final DotName ENUM_NAME; + public static final DotName RECORD_NAME; private final DotName prefix; private final String local; diff --git a/core/src/main/java/org/jboss/jandex/EquivalenceKey.java b/core/src/main/java/org/jboss/jandex/EquivalenceKey.java new file mode 100644 index 00000000..7193a99a --- /dev/null +++ b/core/src/main/java/org/jboss/jandex/EquivalenceKey.java @@ -0,0 +1,637 @@ +package org.jboss.jandex; + +import java.util.Arrays; +import java.util.Locale; +import java.util.Objects; +import java.util.stream.Collectors; + +/** + * Establishes a notion of equivalence of Jandex objects. Two Jandex objects are equivalent if and only if + * they denote the same Java declaration or type, without taking into account any annotations. The prime use case + * is to assist with building annotation overlays on top of Jandex, where it is common to have multiple Jandex + * objects with different annotations, but otherwise equivalent. + *

+ * In contrast, the common Jandex classes such as {@link ClassInfo}, {@link MethodInfo} or {@link FieldInfo} + * either don't provide equality at all (and hence can only be compared by identity) or provide strict equality, + * which includes presence or absence of annotations and comparison of their members, as well as other details. + *

+ * An instance of this class, also called an equivalence key, provides 3 fundamental operations, + * corresponding to the 3 common methods all Java classes have: + *

+ * In addition, equivalence keys are structured in an inheritance hierarchy that roughly corresponds + * to the inheritance hierarchy of Jandex objects. Therefore, the kind of the "source" Jandex object may be found + * by inspecting the class of the equivalence key: + * + */ +public abstract class EquivalenceKey { + /** + * Returns an equivalence key for given {@link AnnotationTarget annotation target}. + * + * @param annotationTarget the annotation target, may be {@code null} + * @return equvalence key for given annotation target, only {@code null} if {@code annotationTarget == null} + */ + public static EquivalenceKey of(AnnotationTarget annotationTarget) { + if (annotationTarget == null) { + return null; + } + + switch (annotationTarget.kind()) { + case CLASS: + return of(annotationTarget.asClass()); + case METHOD: + return of(annotationTarget.asMethod()); + case METHOD_PARAMETER: + return of(annotationTarget.asMethodParameter()); + case FIELD: + return of(annotationTarget.asField()); + case RECORD_COMPONENT: + return of(annotationTarget.asRecordComponent()); + case TYPE: + return of(annotationTarget.asType()); + default: + throw new IllegalArgumentException("Unknown annotation target: " + annotationTarget); + } + } + + /** + * Returns an equivalence key for given class. + * + * @param clazz the class, may be {@code null} + * @return equvalence key for given class, only {@code null} if {@code clazz == null} + */ + public static ClassEquivalenceKey of(ClassInfo clazz) { + if (clazz == null) { + return null; + } + return new ClassEquivalenceKey(clazz.name()); + } + + /** + * Returns an equivalence key for given method. + * + * @param method the method, may be {@code null} + * @return equvalence key for given method, only {@code null} if {@code method == null} + */ + public static MethodEquivalenceKey of(MethodInfo method) { + if (method == null) { + return null; + } + return new MethodEquivalenceKey(method.declaringClass().name(), method.methodInternal().nameBytes(), + of(method.methodInternal().parameterArray()), of(method.returnType())); + } + + /** + * Returns an equivalence key for given method parameter. + * + * @param parameter the method parameter, may be {@code null} + * @return equvalence key for given method parameter, only {@code null} if {@code parameter == null} + */ + public static MethodParameterEquivalenceKey of(MethodParameterInfo parameter) { + if (parameter == null) { + return null; + } + return new MethodParameterEquivalenceKey(of(parameter.method()), parameter.position()); + } + + /** + * Returns an equivalence key for given field. + * + * @param field the field, may be {@code null} + * @return equvalence key for given field, only {@code null} if {@code field == null} + */ + public static FieldEquivalenceKey of(FieldInfo field) { + if (field == null) { + return null; + } + return new FieldEquivalenceKey(field.declaringClass().name(), field.fieldInternal().nameBytes(), of(field.type())); + } + + /** + * Returns an equivalence key for given record component. + * + * @param recordComponent the record component, may be {@code null} + * @return equvalence key for given record component, only {@code null} if {@code recordComponent == null} + */ + public static RecordComponentEquivalenceKey of(RecordComponentInfo recordComponent) { + if (recordComponent == null) { + return null; + } + return new RecordComponentEquivalenceKey(recordComponent.declaringClass().name(), + recordComponent.recordComponentInternal().nameBytes(), of(recordComponent.type())); + } + + /** + * Returns an equivalence key for given type annotation target. + * It is the equivalence key of the annotated type. + * + * @param typeTarget the type target, may be {@code null} + * @return equvalence key for given type target, only {@code null} if {@code typeTarget == null} + */ + public static TypeEquivalenceKey of(TypeTarget typeTarget) { + if (typeTarget == null) { + return null; + } + return of(typeTarget.target()); + } + + /** + * Returns an equivalence key for given type. + * + * @param type the type, may be {@code null} + * @return equvalence key for given type, only {@code null} if {@code type == null} + */ + public static TypeEquivalenceKey of(Type type) { + if (type == null) { + return null; + } + + switch (type.kind()) { + case ARRAY: + return new ArrayTypeEquivalenceKey(of(type.asArrayType().component()), + type.asArrayType().dimensions()); + case CLASS: + return new ClassTypeEquivalenceKey(type.asClassType().name()); + case PARAMETERIZED_TYPE: + return new ParameterizedTypeEquivalenceKey(type.asParameterizedType().name(), + of(type.asParameterizedType().argumentsArray())); + case PRIMITIVE: + return new PrimitiveTypeEquivalenceKey(type.asPrimitiveType().primitive()); + case TYPE_VARIABLE: + return new TypeVariableEquivalenceKey(type.asTypeVariable().identifier(), + of(type.asTypeVariable().boundArray())); + case UNRESOLVED_TYPE_VARIABLE: + return new UnresolvedTypeVariableEquivalenceKey(type.asUnresolvedTypeVariable().identifier()); + case VOID: + return VoidTypeEquivalenceKey.SINGLETON; + case WILDCARD_TYPE: + return new WildcardTypeEquivalenceKey(of(type.asWildcardType().bound()), type.asWildcardType().isExtends(), + type.asWildcardType().hasImplicitObjectBound()); + default: + throw new IllegalArgumentException("Unknown type: " + type); + } + } + + private static TypeEquivalenceKey[] of(Type[] types) { + TypeEquivalenceKey[] result = new TypeEquivalenceKey[types.length]; + for (int i = 0; i < types.length; i++) { + result[i] = of(types[i]); + } + return result; + } + + // --- + + private EquivalenceKey() { + } + + public static abstract class DeclarationEquivalenceKey extends EquivalenceKey { + private DeclarationEquivalenceKey() { + } + } + + public static final class ClassEquivalenceKey extends DeclarationEquivalenceKey { + private final DotName className; + + private ClassEquivalenceKey(DotName className) { + this.className = className; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (!(o instanceof ClassEquivalenceKey)) + return false; + ClassEquivalenceKey that = (ClassEquivalenceKey) o; + return Objects.equals(className, that.className); + } + + @Override + public int hashCode() { + return Objects.hash(className); + } + + @Override + public String toString() { + return "class " + className.toString(); + } + } + + public static final class MethodEquivalenceKey extends DeclarationEquivalenceKey { + private final DotName className; + private final byte[] methodName; + private final TypeEquivalenceKey[] parameterTypes; + private final TypeEquivalenceKey returnType; // needed e.g. to distinguish bridge methods from "real" methods + + private MethodEquivalenceKey(DotName className, byte[] methodName, TypeEquivalenceKey[] parameterTypes, + TypeEquivalenceKey returnType) { + this.className = className; + this.methodName = methodName; + this.parameterTypes = parameterTypes; + this.returnType = returnType; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (!(o instanceof MethodEquivalenceKey)) + return false; + MethodEquivalenceKey that = (MethodEquivalenceKey) o; + return Objects.equals(className, that.className) + && Arrays.equals(methodName, that.methodName) + && Arrays.equals(parameterTypes, that.parameterTypes) + && Objects.equals(returnType, that.returnType); + } + + @Override + public int hashCode() { + int result = Objects.hash(className, returnType); + result = 31 * result + Arrays.hashCode(methodName); + result = 31 * result + Arrays.hashCode(parameterTypes); + return result; + } + + @Override + public String toString() { + return "method " + className + "#" + Utils.fromUTF8(methodName) + "(" + + Arrays.stream(parameterTypes).map(TypeEquivalenceKey::toString).collect(Collectors.joining(", ")) + + ") -> " + returnType; + } + } + + public static final class MethodParameterEquivalenceKey extends DeclarationEquivalenceKey { + private final MethodEquivalenceKey method; + private final short position; + + private MethodParameterEquivalenceKey(MethodEquivalenceKey method, short position) { + this.method = method; + this.position = position; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (!(o instanceof MethodParameterEquivalenceKey)) + return false; + MethodParameterEquivalenceKey that = (MethodParameterEquivalenceKey) o; + return position == that.position && Objects.equals(method, that.method); + } + + @Override + public int hashCode() { + return Objects.hash(method, position); + } + + @Override + public String toString() { + return "parameter " + position + " of " + method; + } + } + + public static final class FieldEquivalenceKey extends DeclarationEquivalenceKey { + private final DotName className; + private final byte[] fieldName; + private final TypeEquivalenceKey type; + + private FieldEquivalenceKey(DotName className, byte[] fieldName, TypeEquivalenceKey type) { + this.className = className; + this.fieldName = fieldName; + this.type = type; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (!(o instanceof FieldEquivalenceKey)) + return false; + FieldEquivalenceKey that = (FieldEquivalenceKey) o; + return Objects.equals(className, that.className) && Arrays.equals(fieldName, that.fieldName) + && Objects.equals(type, that.type); + } + + @Override + public int hashCode() { + int result = Objects.hash(className, type); + result = 31 * result + Arrays.hashCode(fieldName); + return result; + } + + @Override + public String toString() { + return "field " + className + "#" + Utils.fromUTF8(fieldName) + " of type " + type; + } + } + + public static final class RecordComponentEquivalenceKey extends DeclarationEquivalenceKey { + private final DotName className; + private final byte[] recordComponentName; + private final TypeEquivalenceKey type; + + private RecordComponentEquivalenceKey(DotName className, byte[] recordComponentName, TypeEquivalenceKey type) { + this.className = className; + this.recordComponentName = recordComponentName; + this.type = type; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (!(o instanceof RecordComponentEquivalenceKey)) + return false; + RecordComponentEquivalenceKey that = (RecordComponentEquivalenceKey) o; + return Objects.equals(className, that.className) && Arrays.equals(recordComponentName, that.recordComponentName) + && Objects.equals(type, that.type); + } + + @Override + public int hashCode() { + int result = Objects.hash(className, type); + result = 31 * result + Arrays.hashCode(recordComponentName); + return result; + } + + @Override + public String toString() { + return "record component " + className + "#" + Utils.fromUTF8(recordComponentName) + " of type " + type; + } + } + + public static abstract class TypeEquivalenceKey extends EquivalenceKey { + private TypeEquivalenceKey() { + } + } + + public static final class ArrayTypeEquivalenceKey extends TypeEquivalenceKey { + private final TypeEquivalenceKey component; + private final int dimensions; + + private ArrayTypeEquivalenceKey(TypeEquivalenceKey component, int dimensions) { + this.component = component; + this.dimensions = dimensions; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (!(o instanceof ArrayTypeEquivalenceKey)) + return false; + ArrayTypeEquivalenceKey that = (ArrayTypeEquivalenceKey) o; + return dimensions == that.dimensions && Objects.equals(component, that.component); + } + + @Override + public int hashCode() { + return Objects.hash(component, dimensions); + } + + @Override + public String toString() { + StringBuilder result = new StringBuilder(); + result.append(component); + for (int i = 0; i < dimensions; i++) { + result.append("[]"); + } + return result.toString(); + } + } + + public static final class ClassTypeEquivalenceKey extends TypeEquivalenceKey { + private final DotName name; + + private ClassTypeEquivalenceKey(DotName name) { + this.name = name; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (!(o instanceof ClassTypeEquivalenceKey)) + return false; + ClassTypeEquivalenceKey that = (ClassTypeEquivalenceKey) o; + return Objects.equals(name, that.name); + } + + @Override + public int hashCode() { + return Objects.hash(name); + } + + @Override + public String toString() { + return name.toString(); + } + } + + public static final class ParameterizedTypeEquivalenceKey extends TypeEquivalenceKey { + private final DotName genericClass; + private final TypeEquivalenceKey[] typeArguments; + + private ParameterizedTypeEquivalenceKey(DotName genericClass, TypeEquivalenceKey[] typeArguments) { + this.genericClass = genericClass; + this.typeArguments = typeArguments; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (!(o instanceof ParameterizedTypeEquivalenceKey)) + return false; + ParameterizedTypeEquivalenceKey that = (ParameterizedTypeEquivalenceKey) o; + return Objects.equals(genericClass, that.genericClass) && Arrays.equals(typeArguments, that.typeArguments); + } + + @Override + public int hashCode() { + int result = Objects.hash(genericClass); + result = 31 * result + Arrays.hashCode(typeArguments); + return result; + } + + @Override + public String toString() { + return genericClass + "<" + + Arrays.stream(typeArguments).map(TypeEquivalenceKey::toString).collect(Collectors.joining(", ")) + ">"; + } + } + + public static final class PrimitiveTypeEquivalenceKey extends TypeEquivalenceKey { + private final PrimitiveType.Primitive kind; + + private PrimitiveTypeEquivalenceKey(PrimitiveType.Primitive kind) { + this.kind = kind; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (!(o instanceof PrimitiveTypeEquivalenceKey)) + return false; + PrimitiveTypeEquivalenceKey that = (PrimitiveTypeEquivalenceKey) o; + return kind == that.kind; + } + + @Override + public int hashCode() { + return Objects.hash(kind); + } + + @Override + public String toString() { + return kind.name().toLowerCase(Locale.ROOT); + } + } + + public static final class TypeVariableEquivalenceKey extends TypeEquivalenceKey { + private final String name; + private final TypeEquivalenceKey[] bounds; + + private TypeVariableEquivalenceKey(String name, TypeEquivalenceKey[] bounds) { + this.name = name; + this.bounds = bounds; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (!(o instanceof TypeVariableEquivalenceKey)) + return false; + TypeVariableEquivalenceKey that = (TypeVariableEquivalenceKey) o; + return Objects.equals(name, that.name) && Arrays.equals(bounds, that.bounds); + } + + @Override + public int hashCode() { + int result = Objects.hash(name); + result = 31 * result + Arrays.hashCode(bounds); + return result; + } + + @Override + public String toString() { + if (bounds.length == 0) { + return name; + } + return name + " extends " + + Arrays.stream(bounds).map(TypeEquivalenceKey::toString).collect(Collectors.joining(" & ")); + } + } + + public static final class UnresolvedTypeVariableEquivalenceKey extends TypeEquivalenceKey { + private final String name; + + private UnresolvedTypeVariableEquivalenceKey(String name) { + this.name = name; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (!(o instanceof UnresolvedTypeVariableEquivalenceKey)) + return false; + UnresolvedTypeVariableEquivalenceKey that = (UnresolvedTypeVariableEquivalenceKey) o; + return Objects.equals(name, that.name); + } + + @Override + public int hashCode() { + return Objects.hash(name); + } + + @Override + public String toString() { + return name; + } + } + + public static final class VoidTypeEquivalenceKey extends TypeEquivalenceKey { + private static final VoidTypeEquivalenceKey SINGLETON = new VoidTypeEquivalenceKey(); + + private VoidTypeEquivalenceKey() { + } + + @Override + public String toString() { + return "void"; + } + } + + public static final class WildcardTypeEquivalenceKey extends TypeEquivalenceKey { + private final TypeEquivalenceKey bound; + private final boolean isExtends; + private final boolean hasImplicitObjectBound; + + private WildcardTypeEquivalenceKey(TypeEquivalenceKey bound, boolean isExtends, boolean hasImplicitObjectBound) { + this.bound = bound; + this.isExtends = isExtends; + this.hasImplicitObjectBound = hasImplicitObjectBound; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (!(o instanceof WildcardTypeEquivalenceKey)) + return false; + WildcardTypeEquivalenceKey that = (WildcardTypeEquivalenceKey) o; + return isExtends == that.isExtends && hasImplicitObjectBound == that.hasImplicitObjectBound + && Objects.equals(bound, that.bound); + } + + @Override + public int hashCode() { + return Objects.hash(bound, isExtends, hasImplicitObjectBound); + } + + @Override + public String toString() { + if (bound == null || hasImplicitObjectBound) { + return "?"; + } + if (isExtends) { + return "?" + " extends " + bound; + } + return "?" + " super " + bound; + } + } +} diff --git a/core/src/main/java/org/jboss/jandex/Utils.java b/core/src/main/java/org/jboss/jandex/Utils.java index 147289eb..e5348bcd 100644 --- a/core/src/main/java/org/jboss/jandex/Utils.java +++ b/core/src/main/java/org/jboss/jandex/Utils.java @@ -18,7 +18,7 @@ package org.jboss.jandex; -import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -31,14 +31,12 @@ * @author Jason T. Greene */ class Utils { - private static Charset UTF8 = Charset.forName("UTF-8"); - static byte[] toUTF8(String string) { - return string.getBytes(UTF8); + return string.getBytes(StandardCharsets.UTF_8); } static String fromUTF8(byte[] bytes) { - return new String(bytes, UTF8); + return new String(bytes, StandardCharsets.UTF_8); } static List emptyOrWrap(List list) { @@ -56,5 +54,4 @@ static Map emptyOrWrap(Map map) { static List listOfCapacity(int capacity) { return capacity > 0 ? new ArrayList(capacity) : Collections. emptyList(); } - } diff --git a/core/src/main/java/org/jboss/jandex/WildcardType.java b/core/src/main/java/org/jboss/jandex/WildcardType.java index 4a2a4a28..e786b390 100644 --- a/core/src/main/java/org/jboss/jandex/WildcardType.java +++ b/core/src/main/java/org/jboss/jandex/WildcardType.java @@ -26,7 +26,7 @@ * @author Jason T. Greene */ public class WildcardType extends Type { - private static Type OBJECT = new ClassType(DotName.OBJECT_NAME); + private static final Type OBJECT = new ClassType(DotName.OBJECT_NAME); private final boolean isExtends; private final Type bound; @@ -84,6 +84,10 @@ boolean isExtends() { return isExtends; } + boolean hasImplicitObjectBound() { + return isExtends && bound == OBJECT; + } + @Override public Kind kind() { return Kind.WILDCARD_TYPE; diff --git a/core/src/test/java/org/jboss/jandex/test/EquivalenceTest.java b/core/src/test/java/org/jboss/jandex/test/EquivalenceTest.java new file mode 100644 index 00000000..aa5abe1e --- /dev/null +++ b/core/src/test/java/org/jboss/jandex/test/EquivalenceTest.java @@ -0,0 +1,303 @@ +package org.jboss.jandex.test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; + +import java.io.IOException; +import java.io.Serializable; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.jboss.jandex.AnnotationInstance; +import org.jboss.jandex.AnnotationValue; +import org.jboss.jandex.ClassInfo; +import org.jboss.jandex.DotName; +import org.jboss.jandex.EquivalenceKey; +import org.jboss.jandex.FieldInfo; +import org.jboss.jandex.Index; +import org.jboss.jandex.MethodInfo; +import org.jboss.jandex.MethodParameterInfo; +import org.jboss.jandex.Type; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class EquivalenceTest { + static class A { + int a; + + void a(String str, int[] i, List listUpperBound, List listLowerBound) { + } + + U b(U arg, U[] args, List> listUnbounded) { + return null; + } + + private T c() { + return null; + } + } + + static class B { + static class A { + int a; + + public void a(String str, int[] i, List listUpperBound, List listLowerBound) { + } + + U b(U arg, U[] args, List> listUnbounded) { + return null; + } + + T c() { + return null; + } + } + + double z; + + double z(short arg) { + return 0.0; + } + } + + private Index index; + + @BeforeEach + public void setUp() throws IOException { + index = Index.of(A.class, B.class, B.A.class); + } + + @Test + public void classes() { + ClassInfo a1 = index.getClassByName(DotName.createSimple(A.class.getName())); + ClassInfo a2 = index.getClassByName(DotName.createSimple(B.A.class.getName())); + ClassInfo b = index.getClassByName(DotName.createSimple(B.class.getName())); + + EquivalenceKey keyA1 = EquivalenceKey.of(a1); + EquivalenceKey keyA2 = EquivalenceKey.of(a2); + EquivalenceKey keyB = EquivalenceKey.of(b); + + assertEquals(keyA1, EquivalenceKey.of(a1)); + assertEquals(keyA1.hashCode(), EquivalenceKey.of(a1).hashCode()); + assertEquals("class org.jboss.jandex.test.EquivalenceTest$A", keyA1.toString()); + + assertNotEquals(keyA1, keyA2); + assertEquals("class org.jboss.jandex.test.EquivalenceTest$B$A", keyA2.toString()); + + assertNotEquals(keyA1, keyB); + assertEquals("class org.jboss.jandex.test.EquivalenceTest$B", keyB.toString()); + + Map> annotations = new HashMap<>(); + DotName annotationName = DotName.createSimple(MyAnnotation.class.getName()); + annotations.put(annotationName, + Collections.singletonList(AnnotationInstance.create(annotationName, null, new AnnotationValue[0]))); + ClassInfo bWithAnnotations = ClassInfo.create(b.name(), b.superName(), b.flags(), b.interfaces(), annotations, + b.hasNoArgsConstructor()); + EquivalenceKey keyBWithAnnotations = EquivalenceKey.of(bWithAnnotations); + + assertNotEquals(b, bWithAnnotations); + assertEquals(keyB, keyBWithAnnotations); + assertEquals(keyB.hashCode(), keyBWithAnnotations.hashCode()); + assertEquals(keyB.toString(), keyBWithAnnotations.toString()); + } + + @Test + public void methods() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { + ClassInfo class1 = index.getClassByName(DotName.createSimple(A.class.getName())); + ClassInfo class2 = index.getClassByName(DotName.createSimple(B.A.class.getName())); + + MethodInfo a1 = class1.firstMethod("a"); + MethodInfo a2 = class2.firstMethod("a"); + + EquivalenceKey keyA1 = EquivalenceKey.of(a1); + EquivalenceKey keyA2 = EquivalenceKey.of(a2); + + assertEquals(keyA1, EquivalenceKey.of(a1)); + assertEquals(keyA1.hashCode(), EquivalenceKey.of(a1).hashCode()); + assertEquals( + "method org.jboss.jandex.test.EquivalenceTest$A#a(java.lang.String, int[], java.util.List, java.util.List) -> void", + keyA1.toString()); + + assertNotEquals(keyA1, keyA2); + assertEquals( + "method org.jboss.jandex.test.EquivalenceTest$B$A#a(java.lang.String, int[], java.util.List, java.util.List) -> void", + keyA2.toString()); + + MethodInfo b1 = class1.firstMethod("b"); + MethodInfo b2 = class2.firstMethod("b"); + + EquivalenceKey keyB1 = EquivalenceKey.of(b1); + EquivalenceKey keyB2 = EquivalenceKey.of(b2); + + assertEquals(keyB1, EquivalenceKey.of(b1)); + assertEquals(keyB1.hashCode(), EquivalenceKey.of(b1).hashCode()); + assertEquals( + "method org.jboss.jandex.test.EquivalenceTest$A#b(U extends java.lang.Number & java.lang.CharSequence & java.io.Serializable, U extends java.lang.Number & java.lang.CharSequence & java.io.Serializable[], java.util.List>) -> U extends java.lang.Number & java.lang.CharSequence & java.io.Serializable", + keyB1.toString()); + + assertNotEquals(keyB1, keyB2); + assertEquals( + "method org.jboss.jandex.test.EquivalenceTest$B$A#b(U extends java.lang.Number & java.lang.CharSequence & java.io.Serializable, U extends java.lang.Number & java.lang.CharSequence & java.io.Serializable[], java.util.List>) -> U extends java.lang.Number & java.lang.CharSequence & java.io.Serializable", + keyB2.toString()); + + MethodInfo c1 = class1.firstMethod("c"); + MethodInfo c2 = class2.firstMethod("c"); + + EquivalenceKey keyC1 = EquivalenceKey.of(c1); + EquivalenceKey keyC2 = EquivalenceKey.of(c2); + + assertEquals(keyC1, EquivalenceKey.of(c1)); + assertEquals(keyC1.hashCode(), EquivalenceKey.of(c1).hashCode()); + assertEquals("method org.jboss.jandex.test.EquivalenceTest$A#c() -> T extends java.lang.Appendable", keyC1.toString()); + + assertNotEquals(keyC1, keyC2); + assertEquals("method org.jboss.jandex.test.EquivalenceTest$B$A#c() -> T extends java.lang.Appendable", + keyC2.toString()); + + MethodInfo z = index.getClassByName(DotName.createSimple(B.class.getName())).firstMethod("z"); + MethodInfo zWithAnnotations = MethodInfo.create(z.declaringClass(), z.name(), z.parameters().toArray(Type.EMPTY_ARRAY), + z.returnType(), z.flags()); + List annotations = Collections.singletonList( + AnnotationInstance.create(DotName.createSimple(MyAnnotation.class.getName()), null, new AnnotationValue[0])); + Method setAnnotations = zWithAnnotations.getClass().getDeclaredMethod("setAnnotations", List.class); + setAnnotations.setAccessible(true); + setAnnotations.invoke(zWithAnnotations, annotations); + + EquivalenceKey keyZ = EquivalenceKey.of(z); + EquivalenceKey keyZWithAnnotations = EquivalenceKey.of(zWithAnnotations); + + assertNotEquals(z, zWithAnnotations); + assertEquals(keyZ, keyZWithAnnotations); + assertEquals(keyZ.hashCode(), keyZWithAnnotations.hashCode()); + assertEquals(keyZ.toString(), keyZWithAnnotations.toString()); + } + + @Test + public void methodParameters() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { + ClassInfo class1 = index.getClassByName(DotName.createSimple(A.class.getName())); + ClassInfo class2 = index.getClassByName(DotName.createSimple(B.A.class.getName())); + + for (short i = 0; i < 4; i++) { + MethodParameterInfo a1 = MethodParameterInfo.create(class1.firstMethod("a"), i); + MethodParameterInfo a2 = MethodParameterInfo.create(class2.firstMethod("a"), i); + + EquivalenceKey keyA1 = EquivalenceKey.of(a1); + EquivalenceKey keyA2 = EquivalenceKey.of(a2); + + assertEquals(keyA1, EquivalenceKey.of(a1)); + assertEquals(keyA1.hashCode(), EquivalenceKey.of(a1).hashCode()); + assertEquals("parameter " + i + + " of method org.jboss.jandex.test.EquivalenceTest$A#a(java.lang.String, int[], java.util.List, java.util.List) -> void", + keyA1.toString()); + + assertNotEquals(keyA1, keyA2); + assertEquals("parameter " + i + + " of method org.jboss.jandex.test.EquivalenceTest$B$A#a(java.lang.String, int[], java.util.List, java.util.List) -> void", + keyA2.toString()); + } + + for (short i = 0; i < 3; i++) { + MethodParameterInfo b1 = MethodParameterInfo.create(class1.firstMethod("b"), i); + MethodParameterInfo b2 = MethodParameterInfo.create(class2.firstMethod("b"), i); + + EquivalenceKey keyB1 = EquivalenceKey.of(b1); + EquivalenceKey keyB2 = EquivalenceKey.of(b2); + + assertEquals(keyB1, EquivalenceKey.of(b1)); + assertEquals(keyB1.hashCode(), EquivalenceKey.of(b1).hashCode()); + assertEquals("parameter " + i + + " of method org.jboss.jandex.test.EquivalenceTest$A#b(U extends java.lang.Number & java.lang.CharSequence & java.io.Serializable, U extends java.lang.Number & java.lang.CharSequence & java.io.Serializable[], java.util.List>) -> U extends java.lang.Number & java.lang.CharSequence & java.io.Serializable", + keyB1.toString()); + + assertNotEquals(keyB1, keyB2); + assertEquals("parameter " + i + + " of method org.jboss.jandex.test.EquivalenceTest$B$A#b(U extends java.lang.Number & java.lang.CharSequence & java.io.Serializable, U extends java.lang.Number & java.lang.CharSequence & java.io.Serializable[], java.util.List>) -> U extends java.lang.Number & java.lang.CharSequence & java.io.Serializable", + keyB2.toString()); + } + + MethodInfo z = index.getClassByName(DotName.createSimple(B.class.getName())).firstMethod("z"); + MethodInfo zWithAnnotations = MethodInfo.create(z.declaringClass(), z.name(), z.parameters().toArray(Type.EMPTY_ARRAY), + z.returnType(), z.flags()); + List annotations = Collections.singletonList( + AnnotationInstance.create(DotName.createSimple(MyAnnotation.class.getName()), + MethodParameterInfo.create(z, (short) 0), new AnnotationValue[0])); + Method setAnnotations = zWithAnnotations.getClass().getDeclaredMethod("setAnnotations", List.class); + setAnnotations.setAccessible(true); + setAnnotations.invoke(zWithAnnotations, annotations); + + EquivalenceKey keyZ = EquivalenceKey.of(z); + EquivalenceKey keyZWithAnnotations = EquivalenceKey.of(zWithAnnotations); + + assertNotEquals(z, zWithAnnotations); + assertEquals(keyZ, keyZWithAnnotations); + assertEquals(keyZ.hashCode(), keyZWithAnnotations.hashCode()); + assertEquals(keyZ.toString(), keyZWithAnnotations.toString()); + + EquivalenceKey keyZP = EquivalenceKey.of(MethodParameterInfo.create(z, (short) 0)); + EquivalenceKey keyZPWithAnnotations = EquivalenceKey.of(MethodParameterInfo.create(zWithAnnotations, (short) 0)); + + assertEquals(keyZP, keyZPWithAnnotations); + assertEquals(keyZP.hashCode(), keyZPWithAnnotations.hashCode()); + assertEquals(keyZP.toString(), keyZPWithAnnotations.toString()); + } + + @Test + public void fields() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { + FieldInfo a1 = index.getClassByName(DotName.createSimple(A.class.getName())).field("a"); + FieldInfo a2 = index.getClassByName(DotName.createSimple(B.A.class.getName())).field("a"); + + EquivalenceKey keyA1 = EquivalenceKey.of(a1); + EquivalenceKey keyA2 = EquivalenceKey.of(a2); + + assertEquals(keyA1, EquivalenceKey.of(a1)); + assertEquals(keyA1.hashCode(), EquivalenceKey.of(a1).hashCode()); + assertEquals("field org.jboss.jandex.test.EquivalenceTest$A#a of type int", keyA1.toString()); + + assertNotEquals(keyA1, keyA2); + assertEquals("field org.jboss.jandex.test.EquivalenceTest$B$A#a of type int", keyA2.toString()); + + FieldInfo z = index.getClassByName(DotName.createSimple(B.class.getName())).field("z"); + FieldInfo zWithAnnotations = FieldInfo.create(z.declaringClass(), z.name(), z.type(), z.flags()); + List annotations = Collections.singletonList( + AnnotationInstance.create(DotName.createSimple(MyAnnotation.class.getName()), null, new AnnotationValue[0])); + Method setAnnotations = zWithAnnotations.getClass().getDeclaredMethod("setAnnotations", List.class); + setAnnotations.setAccessible(true); + setAnnotations.invoke(zWithAnnotations, annotations); + + EquivalenceKey keyZ = EquivalenceKey.of(z); + EquivalenceKey keyZWithAnnotations = EquivalenceKey.of(zWithAnnotations); + + assertNotEquals(z, zWithAnnotations); + assertEquals(keyZ, keyZWithAnnotations); + assertEquals(keyZ.hashCode(), keyZWithAnnotations.hashCode()); + assertEquals(keyZ.toString(), keyZWithAnnotations.toString()); + } + + @Test + public void mixed() { + ClassInfo a1 = index.getClassByName(DotName.createSimple(A.class.getName())); + ClassInfo a2 = index.getClassByName(DotName.createSimple(B.A.class.getName())); + + MethodInfo ma1 = a1.firstMethod("a"); + MethodInfo ma2 = a2.firstMethod("a"); + FieldInfo fa1 = a1.field("a"); + FieldInfo fa2 = a2.field("a"); + + assertNotEquals(a1, ma1); + assertNotEquals(a1, ma2); + assertNotEquals(a1, fa1); + assertNotEquals(a1, fa2); + assertNotEquals(a2, ma1); + assertNotEquals(a2, ma2); + assertNotEquals(a2, fa1); + assertNotEquals(a2, fa2); + assertNotEquals(ma1, fa1); + assertNotEquals(ma1, fa2); + assertNotEquals(ma2, fa1); + assertNotEquals(ma2, fa2); + } +} diff --git a/core/src/test/java/org/jboss/jandex/test/MyAnnotation.java b/core/src/test/java/org/jboss/jandex/test/MyAnnotation.java new file mode 100644 index 00000000..22b22e08 --- /dev/null +++ b/core/src/test/java/org/jboss/jandex/test/MyAnnotation.java @@ -0,0 +1,9 @@ +package org.jboss.jandex.test; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +@interface MyAnnotation { + String value(); +} diff --git a/core/src/test/java/org/jboss/jandex/test/TooManyMethodParametersTest.java b/core/src/test/java/org/jboss/jandex/test/TooManyMethodParametersTest.java index a45acdd6..4491cdc6 100644 --- a/core/src/test/java/org/jboss/jandex/test/TooManyMethodParametersTest.java +++ b/core/src/test/java/org/jboss/jandex/test/TooManyMethodParametersTest.java @@ -5,8 +5,6 @@ import java.io.ByteArrayInputStream; import java.io.IOException; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.List; @@ -26,10 +24,6 @@ import net.bytebuddy.dynamic.DynamicType; import net.bytebuddy.implementation.StubMethod; -@Retention(RetentionPolicy.RUNTIME) -@interface MyAnn { -} - public class TooManyMethodParametersTest { private static final String TEST_CLASS = "org.jboss.jandex.test.TestClass"; @@ -51,10 +45,11 @@ protected String name(TypeDescription superClass) { .subclass(Object.class) .defineMethod("hugeMethod", void.class) .withParameter(String.class, "p0") - .annotateParameter(AnnotationDescription.Builder.ofType(MyAnn.class).build()); + .annotateParameter(AnnotationDescription.Builder.ofType(MyAnnotation.class).define("value", "0").build()); for (int i = 1; i < 300; i++) { builder = builder.withParameter(String.class, "p" + i) - .annotateParameter(AnnotationDescription.Builder.ofType(MyAnn.class).build()); + .annotateParameter( + AnnotationDescription.Builder.ofType(MyAnnotation.class).define("value", "" + i).build()); } byte[] syntheticClass = builder .intercept(StubMethod.INSTANCE) @@ -79,7 +74,8 @@ protected String name(TypeDescription superClass) { } } assertEquals(1, paramAnnotations.size()); - assertEquals("MyAnn", paramAnnotations.get(0).name().withoutPackagePrefix()); + assertEquals("MyAnnotation", paramAnnotations.get(0).name().withoutPackagePrefix()); + assertEquals("" + i, paramAnnotations.get(0).value().asString()); } } } diff --git a/core/src/test/java/org/jboss/jandex/test/Utf8ConstantEncodingTest.java b/core/src/test/java/org/jboss/jandex/test/Utf8ConstantEncodingTest.java index f1678ed4..8d8effbc 100644 --- a/core/src/test/java/org/jboss/jandex/test/Utf8ConstantEncodingTest.java +++ b/core/src/test/java/org/jboss/jandex/test/Utf8ConstantEncodingTest.java @@ -4,8 +4,6 @@ import java.io.ByteArrayInputStream; import java.io.IOException; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; import org.jboss.jandex.ClassInfo; import org.jboss.jandex.DotName; @@ -21,11 +19,6 @@ import net.bytebuddy.description.type.TypeDescription; public class Utf8ConstantEncodingTest { - @Retention(RetentionPolicy.RUNTIME) - @interface MyAnnotation { - String value(); - } - private static final String CLASS_NAME = "org.jboss.jandex.test.MyTestClass"; private static final String LONG_STRING;