diff --git a/CHANGELOG.md b/CHANGELOG.md index 249d70107..c7a7e4966 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed +- Removed direct dependency on ASM by re-writing the annotation processor to Byte-Buddy. ([Issue 208](https://github.com/jqno/equalsverifier/issues/208)) diff --git a/pom.xml b/pom.xml index a3db0d195..4877d090b 100644 --- a/pom.xml +++ b/pom.xml @@ -87,11 +87,6 @@ byte-buddy 1.9.4 - - org.ow2.asm - asm - 7.0 - com.github.spotbugs spotbugs-annotations @@ -248,16 +243,11 @@ true - org.ow2.asm:asm net.bytebuddy:byte-buddy org.objenesis:objenesis - - org.objectweb.asm - nl.jqno.equalsverifier.internal.lib.asm - net.bytebuddy nl.jqno.equalsverifier.internal.lib.bytebuddy @@ -268,12 +258,6 @@ - - org.objectweb.asm:* - - META-INF/** - - net.bytebuddy:* diff --git a/src/main/java/nl/jqno/equalsverifier/EqualsVerifierApi.java b/src/main/java/nl/jqno/equalsverifier/EqualsVerifierApi.java index 7541cb04d..14ba168b2 100644 --- a/src/main/java/nl/jqno/equalsverifier/EqualsVerifierApi.java +++ b/src/main/java/nl/jqno/equalsverifier/EqualsVerifierApi.java @@ -7,7 +7,6 @@ import nl.jqno.equalsverifier.internal.prefabvalues.FactoryCache; import nl.jqno.equalsverifier.internal.util.*; import nl.jqno.equalsverifier.internal.util.Formatter; -import org.objectweb.asm.Type; import java.util.*; @@ -29,7 +28,7 @@ public class EqualsVerifierApi { private Set allExcludedFields = new HashSet<>(); private Set allIncludedFields = new HashSet<>(); private Set nonnullFields = new HashSet<>(); - private Set ignoredAnnotationDescriptors = new HashSet<>(); + private Set ignoredAnnotationClassNames = new HashSet<>(); private List equalExamples = new ArrayList<>(); private List unequalExamples = new ArrayList<>(); @@ -214,7 +213,7 @@ public EqualsVerifierApi withNonnullFields(String... fields) { public EqualsVerifierApi withIgnoredAnnotations(Class... annotations) { validateAnnotationsAreValid(annotations); for (Class ignoredAnnotation : annotations) { - ignoredAnnotationDescriptors.add(Type.getDescriptor(ignoredAnnotation)); + ignoredAnnotationClassNames.add(ignoredAnnotation.getCanonicalName()); } return this; } @@ -389,7 +388,7 @@ private void performVerification() { private Configuration buildConfig() { return Configuration.build(type, allExcludedFields, allIncludedFields, nonnullFields, cachedHashCodeInitializer, hasRedefinedSuperclass, redefinedSubclass, usingGetClass, warningsToSuppress, factoryCache, - ignoredAnnotationDescriptors, actualFields, equalExamples, unequalExamples); + ignoredAnnotationClassNames, actualFields, equalExamples, unequalExamples); } private void verifyWithoutExamples(Configuration config) { diff --git a/src/main/java/nl/jqno/equalsverifier/internal/reflection/Util.java b/src/main/java/nl/jqno/equalsverifier/internal/reflection/Util.java index 4c6f5e8d8..e790993c7 100644 --- a/src/main/java/nl/jqno/equalsverifier/internal/reflection/Util.java +++ b/src/main/java/nl/jqno/equalsverifier/internal/reflection/Util.java @@ -1,5 +1,9 @@ package nl.jqno.equalsverifier.internal.reflection; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + public final class Util { private Util() { // Do not instantiate @@ -74,4 +78,16 @@ public static Object[] objects(Object first, Object second) { public static Object[] objects(Object first, Object second, Object third) { return new Object[] { first, second, third }; } + + /** + * Helper method to create a set of object. + * + * @param ts The objects to add to the set. + * @param The type of objects to add to the set. + * @return A set with the given objets. + */ + @SafeVarargs + public static Set setOf(T... ts) { + return new HashSet<>(Arrays.asList(ts)); + } } diff --git a/src/main/java/nl/jqno/equalsverifier/internal/reflection/annotations/Annotation.java b/src/main/java/nl/jqno/equalsverifier/internal/reflection/annotations/Annotation.java index 6154b960f..7eaca9498 100644 --- a/src/main/java/nl/jqno/equalsverifier/internal/reflection/annotations/Annotation.java +++ b/src/main/java/nl/jqno/equalsverifier/internal/reflection/annotations/Annotation.java @@ -12,22 +12,27 @@ */ public interface Annotation { /** - * One or more strings that contain the annotation's class name. A - * descriptor can be the annotation's fully qualified canonical name, or a + * One or more strings that contain the annotation's (partial) class name. + * This can be the annotation's fully qualified canonical name, or a * substring thereof. * - * An annotation can be described by more than one descriptor. For + * An annotation can be described by more than one partial class name. For * instance, @Nonnull, @NonNull and @NotNull have the same semantics; their - * descriptors can be grouped together in one {@link Annotation} instance. + * partialClassNames can be grouped together in one {@link Annotation} + * instance. * - * @return An Iterable of annotation descriptor strings. + * @return A Set of potentially partial annotation class names. */ - public Iterable descriptors(); + public Set partialClassNames(); /** * Whether the annotation applies to the class in which is appears only, or * whether it applies to that class and all its subclasses. * + * Note: this encompasses more than {@see java.lang.annotation.Inherited} + * does: this flag also applies, for example, to annotations on fields that + * are declared in a superclass. + * * @return True if the annotation is inherited by subclasses of the class * in which the annotation appears. */ @@ -38,7 +43,7 @@ public interface Annotation { * * @param properties An object that contains information about the annotation. * @param annotationCache A cache containing all annotations for known types. - * @param ignoredAnnotations A collection of type descriptors for annotations + * @param ignoredAnnotations A collection of type partialClassNames for annotations * to ignore. * @return True if the annotation is valid and can be used as intended. */ diff --git a/src/main/java/nl/jqno/equalsverifier/internal/reflection/annotations/AnnotationCacheBuilder.java b/src/main/java/nl/jqno/equalsverifier/internal/reflection/annotations/AnnotationCacheBuilder.java index 95e3da2d9..ff87eca81 100644 --- a/src/main/java/nl/jqno/equalsverifier/internal/reflection/annotations/AnnotationCacheBuilder.java +++ b/src/main/java/nl/jqno/equalsverifier/internal/reflection/annotations/AnnotationCacheBuilder.java @@ -1,17 +1,19 @@ package nl.jqno.equalsverifier.internal.reflection.annotations; -import nl.jqno.equalsverifier.internal.reflection.FieldIterable; +import net.bytebuddy.description.annotation.AnnotationDescription; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.pool.TypePool; import nl.jqno.equalsverifier.internal.reflection.SuperclassIterable; -import org.objectweb.asm.*; -import java.io.IOException; -import java.io.InputStream; -import java.lang.reflect.Field; -import java.util.*; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.function.Consumer; -public class AnnotationCacheBuilder { +import static nl.jqno.equalsverifier.internal.reflection.Util.setOf; - private static final int OPCODES = Opcodes.ASM7; +public class AnnotationCacheBuilder { private final List supportedAnnotations; private final Set ignoredAnnotations; @@ -26,196 +28,120 @@ public void build(Class type, AnnotationCache cache) { return; } - visitClass(type, cache); - visitFields(type, cache); - } + try { + TypePool pool = TypePool.Default.of(type.getClassLoader()); + TypeDescription typeDescription = pool.describe(type.getName()).resolve(); - private void visitFields(Class type, AnnotationCache cache) { - for (Field f : FieldIterable.of(type)) { - build(f.getDeclaringClass(), cache); + visitType(setOf(type), cache, typeDescription, false); + visitSuperclasses(type, cache, pool); + visitOuterClasses(type, cache, pool); + visitPackage(type, cache, pool); + } + catch (IllegalStateException ignored) { + // Just ignore this class if it can't be processed. } } - private void visitClass(Class type, AnnotationCache cache) { - visitType(type, type, cache, false); - visitSuperclasses(type, cache); - visitOuterClasses(type, cache); - visitPackage(type, cache); + private void visitType(Set> types, AnnotationCache cache, TypeDescription typeDescription, boolean inheriting) { + visitClass(types, cache, typeDescription, inheriting); + visitFields(types, cache, typeDescription, inheriting); } - private void visitSuperclasses(Class type, AnnotationCache cache) { - for (Class c : SuperclassIterable.of(type)) { - visitType(c, type, cache, true); - } + private void visitSuperclasses(Class type, AnnotationCache cache, TypePool pool) { + SuperclassIterable.of(type).forEach(c -> { + TypeDescription typeDescription = pool.describe(c.getName()).resolve(); + visitType(setOf(type, c), cache, typeDescription, true); + }); } - private void visitOuterClasses(Class type, AnnotationCache cache) { + private void visitOuterClasses(Class type, AnnotationCache cache, TypePool pool) { Class outer = type.getDeclaringClass(); while (outer != null) { - visitType(outer, type, cache, false); + TypeDescription typeDescription = pool.describe(outer.getName()).resolve(); + visitType(setOf(type, outer), cache, typeDescription, false); + outer = outer.getDeclaringClass(); } } - private void visitPackage(Class type, AnnotationCache cache) { - try { - Package pkg = type.getPackage(); - if (pkg == null) { - return; - } - - String className = pkg.getName() + ".package-info"; - Class packageType = Class.forName(className); - visitType(packageType, type, cache, false); - } - catch (ClassNotFoundException e) { - // No package object; do nothing. + private void visitPackage(Class type, AnnotationCache cache, TypePool pool) { + Package pkg = type.getPackage(); + if (pkg == null) { + return; } - } - private void visitType(Class type, Class cacheInto, AnnotationCache cache, boolean inheriting) { - ClassLoader classLoader = getClassLoaderFor(type); - Type asmType = Type.getType(type); - String url = asmType.getInternalName() + ".class"; + String className = pkg.getName() + ".package-info"; - try (InputStream is = classLoader.getResourceAsStream(url)) { - Visitor v = new Visitor(cacheInto, cache, inheriting); - ClassReader cr = new ClassReader(is); - cr.accept(v, 0); + try { + TypeDescription typeDescription = pool.describe(className).resolve(); + visitType(setOf(type), cache, typeDescription, false); } - catch (IOException e) { - // Just ignore this class if it can't be processed. + catch (IllegalStateException e) { + // No package object; do nothing. } } - private ClassLoader getClassLoaderFor(Class c) { - ClassLoader result = c.getClassLoader(); - if (result == null) { - result = ClassLoader.getSystemClassLoader(); - } - return result; + private void visitClass(Set> types, AnnotationCache cache, TypeDescription typeDescription, boolean inheriting) { + Consumer addToCache = a -> types.forEach(t -> cache.addClassAnnotation(t, a)); + typeDescription.getDeclaredAnnotations() + .forEach(a -> cacheSupportedAnnotations(a, cache, addToCache, inheriting)); } - private class Visitor extends ClassVisitor { - private final Class type; - private final AnnotationCache cache; - private final boolean inheriting; + private void visitFields(Set> types, AnnotationCache cache, TypeDescription typeDescription, boolean inheriting) { + typeDescription.getDeclaredFields().forEach(f -> { + Consumer addToCache = a -> types.forEach(t -> cache.addFieldAnnotation(t, f.getName(), a)); - public Visitor(Class type, AnnotationCache cache, boolean inheriting) { - super(OPCODES); - this.type = type; - this.cache = cache; - this.inheriting = inheriting; - } - - @Override - public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) { - return new MyAnnotationVisitor(type, descriptor, cache, Optional.empty(), inheriting); - } + // Regular field annotations + f.getDeclaredAnnotations() + .forEach(a -> cacheSupportedAnnotations(a, cache, addToCache, inheriting)); - @Override - public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { - cache.addField(type, name); - return new MyFieldVisitor(type, cache, name, inheriting); - } + // Type-use annotations + f.getType().getDeclaredAnnotations() + .forEach(a -> cacheSupportedAnnotations(a, cache, addToCache, inheriting)); + }); } - private class MyFieldVisitor extends FieldVisitor { - private final Class type; - private final AnnotationCache cache; - private final String fieldName; - private final boolean inheriting; - - public MyFieldVisitor(Class type, AnnotationCache cache, String fieldName, boolean inheriting) { - super(OPCODES); - this.type = type; - this.cache = cache; - this.fieldName = fieldName; - this.inheriting = inheriting; - } + private void cacheSupportedAnnotations( + AnnotationDescription annotation, AnnotationCache cache, Consumer addToCache, boolean inheriting) { - @Override - public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String descriptor, boolean visible) { - return new MyAnnotationVisitor(type, descriptor, cache, Optional.of(fieldName), inheriting); + if (ignoredAnnotations.contains(annotation.getAnnotationType().getCanonicalName())) { + return; } - @Override - public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) { - return new MyAnnotationVisitor(type, descriptor, cache, Optional.of(fieldName), inheriting); - } + AnnotationProperties props = buildAnnotationProperties(annotation); + supportedAnnotations + .stream() + .filter(sa -> matches(annotation, sa)) + .filter(sa -> !inheriting || sa.inherits()) + .filter(sa -> sa.validate(props, cache, ignoredAnnotations)) + .forEach(addToCache); } - private class MyAnnotationVisitor extends AnnotationVisitor { - private final Class type; - private final String annotationDescriptor; - private final AnnotationCache cache; - private final Optional fieldName; - private final boolean inheriting; - - private final AnnotationProperties properties; - - public MyAnnotationVisitor(Class type, String annotationDescriptor, AnnotationCache cache, - Optional fieldName, boolean inheriting) { - super(OPCODES); - this.type = type; - this.annotationDescriptor = annotationDescriptor; - this.cache = cache; - this.fieldName = fieldName; - this.inheriting = inheriting; - properties = new AnnotationProperties(annotationDescriptor); - } - - @Override - public AnnotationVisitor visitArray(String name) { - Set foundAnnotations = new HashSet<>(); - properties.putArrayValues(name, foundAnnotations); - return new AnnotationArrayValueVisitor(foundAnnotations); - } - - @Override - public void visitEnd() { - if (ignoredAnnotations.contains(annotationDescriptor)) { - return; - } - - for (Annotation annotation : supportedAnnotations) { - if (!inheriting || annotation.inherits()) { - matchAnnotation(annotation); - } - } - } - - private void matchAnnotation(Annotation annotation) { - for (String descriptor : annotation.descriptors()) { - String asBytecodeIdentifier = descriptor.replaceAll("\\.", "/") + ";"; - if (annotationDescriptor.endsWith(asBytecodeIdentifier) && annotation.validate(properties, cache, ignoredAnnotations)) { - if (fieldName.isPresent()) { - cache.addFieldAnnotation(type, fieldName.get(), annotation); + private AnnotationProperties buildAnnotationProperties(AnnotationDescription annotation) { + AnnotationProperties props = new AnnotationProperties(annotation.getAnnotationType().getCanonicalName()); + annotation.getAnnotationType().getDeclaredMethods().forEach(m -> { + Object val = annotation.getValue(m).resolve(); + if (val.getClass().isArray()) { + Object[] array = (Object[])val; + Set values = new HashSet<>(); + for (Object obj : array) { + if (obj instanceof TypeDescription) { + values.add(((TypeDescription)obj).getName()); } else { - cache.addClassAnnotation(type, annotation); + values.add(obj.toString()); } } + props.putArrayValues(m.getName(), values); } - } + }); + return props; } - private static class AnnotationArrayValueVisitor extends AnnotationVisitor { - private final Set foundAnnotations; - - public AnnotationArrayValueVisitor(Set foundAnnotations) { - super(OPCODES); - this.foundAnnotations = foundAnnotations; - } - - @Override - public void visit(String name, Object value) { - foundAnnotations.add(value); - } - - @Override - public void visitEnum(String name, String desc, String value) { - foundAnnotations.add(value); - } + private boolean matches(AnnotationDescription foundAnnotation, Annotation supportedAnnotation) { + String canonicalName = foundAnnotation.getAnnotationType().getCanonicalName(); + return supportedAnnotation.partialClassNames() + .stream() + .anyMatch(canonicalName::endsWith); } } diff --git a/src/main/java/nl/jqno/equalsverifier/internal/reflection/annotations/AnnotationProperties.java b/src/main/java/nl/jqno/equalsverifier/internal/reflection/annotations/AnnotationProperties.java index e6dd63165..884de6fed 100644 --- a/src/main/java/nl/jqno/equalsverifier/internal/reflection/annotations/AnnotationProperties.java +++ b/src/main/java/nl/jqno/equalsverifier/internal/reflection/annotations/AnnotationProperties.java @@ -12,23 +12,23 @@ * that are actually used by EqualsVerifier. */ public class AnnotationProperties { - private final String descriptor; - private Map> arrayValues = new HashMap<>(); + private final String className; + private Map> arrayValues = new HashMap<>(); /** * Constructor. * - * @param descriptor The annotation's descriptor string. + * @param className The annotation's className string. */ - public AnnotationProperties(String descriptor) { - this.descriptor = descriptor; + public AnnotationProperties(String className) { + this.className = className; } /** - * @return the annotation's descriptor string. + * @return the annotation's className string. */ - public String getDescriptor() { - return descriptor; + public String getClassName() { + return className; } /** @@ -37,7 +37,7 @@ public String getDescriptor() { * @param name The name of the array value property. * @param values The content of the array value property. */ - public void putArrayValues(String name, Set values) { + public void putArrayValues(String name, Set values) { arrayValues.put(name, values); } @@ -47,7 +47,7 @@ public void putArrayValues(String name, Set values) { * @param name The name of the array value property. * @return The content of the array value property. */ - public Set getArrayValues(String name) { + public Set getArrayValues(String name) { return arrayValues.get(name); } } diff --git a/src/main/java/nl/jqno/equalsverifier/internal/reflection/annotations/SupportedAnnotations.java b/src/main/java/nl/jqno/equalsverifier/internal/reflection/annotations/SupportedAnnotations.java index d392b9e41..bc6ae5898 100644 --- a/src/main/java/nl/jqno/equalsverifier/internal/reflection/annotations/SupportedAnnotations.java +++ b/src/main/java/nl/jqno/equalsverifier/internal/reflection/annotations/SupportedAnnotations.java @@ -1,9 +1,7 @@ package nl.jqno.equalsverifier.internal.reflection.annotations; -import org.objectweb.asm.Type; - import java.util.Arrays; -import java.util.List; +import java.util.HashSet; import java.util.Set; import static nl.jqno.equalsverifier.internal.reflection.Util.classForName; @@ -56,12 +54,10 @@ public enum SupportedAnnotations implements Annotation { "edu.umd.cs.findbugs.annotations.DefaultAnnotation", "edu.umd.cs.findbugs.annotations.DefaultAnnotationForFields") { @Override public boolean validate(AnnotationProperties properties, AnnotationCache annotationCache, Set ignoredAnnotations) { - Set values = properties.getArrayValues("value"); - for (Object value : values) { - for (String descriptor : NONNULL.descriptors()) { - Type type = (Type)value; - String typeDescriptor = type.getDescriptor(); - if (typeDescriptor.contains(descriptor) && !ignoredAnnotations.contains(typeDescriptor)) { + Set values = properties.getArrayValues("value"); + for (String value : values) { + for (String className : NONNULL.partialClassNames()) { + if (value.contains(className) && !ignoredAnnotations.contains(value)) { return true; } } @@ -74,8 +70,7 @@ public boolean validate(AnnotationProperties properties, AnnotationCache annotat @Override public boolean validate(AnnotationProperties properties, AnnotationCache annotationCache, Set ignoredAnnotations) { try { - Type t = Type.getType(properties.getDescriptor()); - Class annotationType = classForName(t.getClassName()); + Class annotationType = classForName(properties.getClassName()); if (annotationType == null) { return false; } @@ -103,7 +98,7 @@ public boolean validate(AnnotationProperties properties, AnnotationCache annotat ECLIPSE_DEFAULT_ANNOTATION_NONNULL(false, "org.eclipse.jdt.annotation.NonNullByDefault") { @Override public boolean validate(AnnotationProperties properties, AnnotationCache annotationCache, Set ignoredAnnotations) { - Set values = properties.getArrayValues("value"); + Set values = properties.getArrayValues("value"); if (values == null) { return true; } @@ -119,16 +114,17 @@ public boolean validate(AnnotationProperties properties, AnnotationCache annotat NULLABLE(false, "Nullable", "CheckForNull"); private final boolean inherits; - private final List descriptors; + private final Set partialClassNames; - private SupportedAnnotations(boolean inherits, String... descriptors) { + private SupportedAnnotations(boolean inherits, String... partialClassNames) { this.inherits = inherits; - this.descriptors = Arrays.asList(descriptors); + this.partialClassNames = new HashSet<>(); + this.partialClassNames.addAll(Arrays.asList(partialClassNames)); } @Override - public Iterable descriptors() { - return descriptors; + public Set partialClassNames() { + return partialClassNames; } @Override diff --git a/src/main/java/nl/jqno/equalsverifier/internal/util/Configuration.java b/src/main/java/nl/jqno/equalsverifier/internal/util/Configuration.java index 1e04d6517..d71052c2e 100644 --- a/src/main/java/nl/jqno/equalsverifier/internal/util/Configuration.java +++ b/src/main/java/nl/jqno/equalsverifier/internal/util/Configuration.java @@ -56,14 +56,14 @@ public Configuration(Class type, TypeTag typeTag, ClassAccessor classAcces public static Configuration build(Class type, Set excludedFields, Set includedFields, Set nonnullFields, CachedHashCodeInitializer cachedHashCodeInitializer, boolean hasRedefinedSuperclass, Class redefinedSubclass, boolean usingGetClass, EnumSet warningsToSuppress, - FactoryCache factoryCache, Set ignoredAnnotationDescriptors, Set actualFields, + FactoryCache factoryCache, Set ignoredAnnotationClassNames, Set actualFields, List equalExamples, List unequalExamples) { TypeTag typeTag = new TypeTag(type); FactoryCache cache = JavaApiPrefabValues.build().merge(factoryCache); PrefabValues prefabValues = new PrefabValues(cache); ClassAccessor classAccessor = ClassAccessor.of(type, prefabValues); - AnnotationCache annotationCache = buildAnnotationCache(type, ignoredAnnotationDescriptors); + AnnotationCache annotationCache = buildAnnotationCache(type, ignoredAnnotationClassNames); Set ignoredFields = includedFields.isEmpty() ? excludedFields : invertIncludedFields(actualFields, includedFields); List unequals = ensureUnequalExamples(typeTag, classAccessor, unequalExamples); @@ -71,8 +71,8 @@ public static Configuration build(Class type, Set excludedFiel cachedHashCodeInitializer, hasRedefinedSuperclass, redefinedSubclass, usingGetClass, warningsToSuppress, equalExamples, unequals); } - private static AnnotationCache buildAnnotationCache(Class type, Set ignoredAnnotationDescriptors) { - AnnotationCacheBuilder acb = new AnnotationCacheBuilder(SupportedAnnotations.values(), ignoredAnnotationDescriptors); + private static AnnotationCache buildAnnotationCache(Class type, Set ignoredAnnotationClassNames) { + AnnotationCacheBuilder acb = new AnnotationCacheBuilder(SupportedAnnotations.values(), ignoredAnnotationClassNames); AnnotationCache cache = new AnnotationCache(); acb.build(type, cache); return cache; diff --git a/src/test/java/nl/jqno/equalsverifier/internal/reflection/UtilTest.java b/src/test/java/nl/jqno/equalsverifier/internal/reflection/UtilTest.java index 06292ee3f..f552d3cdf 100644 --- a/src/test/java/nl/jqno/equalsverifier/internal/reflection/UtilTest.java +++ b/src/test/java/nl/jqno/equalsverifier/internal/reflection/UtilTest.java @@ -4,7 +4,10 @@ import org.junit.Test; import java.util.GregorianCalendar; +import java.util.HashSet; +import java.util.Set; +import static nl.jqno.equalsverifier.internal.reflection.Util.setOf; import static nl.jqno.equalsverifier.testhelpers.Util.coverThePrivateConstructor; import static org.junit.Assert.*; @@ -39,4 +42,14 @@ public void objectsReturnsItsArguments() { Object[] actual = Util.objects("x", new Point(1, 2)); assertArrayEquals(expected, actual); } + + @Test + public void setOfReturnsItsArguments() { + Set expected = new HashSet<>(); + expected.add("one"); + expected.add("two"); + + Set actual = setOf("one", "two"); + assertEquals(expected, actual); + } } diff --git a/src/test/java/nl/jqno/equalsverifier/internal/reflection/annotations/AnnotationCacheBuilderTest.java b/src/test/java/nl/jqno/equalsverifier/internal/reflection/annotations/AnnotationCacheBuilderTest.java index 095a456dc..2e2cb32fa 100644 --- a/src/test/java/nl/jqno/equalsverifier/internal/reflection/annotations/AnnotationCacheBuilderTest.java +++ b/src/test/java/nl/jqno/equalsverifier/internal/reflection/annotations/AnnotationCacheBuilderTest.java @@ -6,7 +6,9 @@ import net.bytebuddy.dynamic.scaffold.TypeValidation; import nl.jqno.equalsverifier.internal.packageannotation.AnnotatedPackage; import nl.jqno.equalsverifier.internal.reflection.Instantiator; +import nl.jqno.equalsverifier.internal.reflection.Util; import nl.jqno.equalsverifier.testhelpers.annotations.AnnotationWithClassValues; +import nl.jqno.equalsverifier.testhelpers.annotations.FieldAnnotationRuntimeRetention; import nl.jqno.equalsverifier.testhelpers.annotations.NotNull; import nl.jqno.equalsverifier.testhelpers.annotations.TestSupportedAnnotations; import nl.jqno.equalsverifier.testhelpers.types.TypeHelper.*; @@ -14,10 +16,8 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; -import org.objectweb.asm.Type; import javax.annotation.Nonnull; -import java.util.Collections; import java.util.HashSet; import java.util.Set; @@ -109,18 +109,18 @@ public void findTypeUseClassAnnotationInField() { public void findPartialAnnotationName() { build(AnnotatedWithRuntime.class, AnnotatedFields.class, AnnotatedTypes.class); - assertTypeHasAnnotation(AnnotatedWithRuntime.class, TYPE_RUNTIME_RETENTION_PARTIAL_DESCRIPTOR); - assertFieldHasAnnotation(AnnotatedFields.class, RUNTIME_RETENTION, FIELD_RUNTIME_RETENTION_PARTIAL_DESCRIPTOR); - assertFieldHasAnnotation(AnnotatedTypes.class, RUNTIME_RETENTION, TYPEUSE_RUNTIME_RETENTION_PARTIAL_DESCRIPTOR); + assertTypeHasAnnotation(AnnotatedWithRuntime.class, TYPE_RUNTIME_RETENTION_PARTIAL_CLASSNAME); + assertFieldHasAnnotation(AnnotatedFields.class, RUNTIME_RETENTION, FIELD_RUNTIME_RETENTION_PARTIAL_CLASSNAME); + assertFieldHasAnnotation(AnnotatedTypes.class, RUNTIME_RETENTION, TYPEUSE_RUNTIME_RETENTION_PARTIAL_CLASSNAME); } @Test public void findFullyQualifiedAnnotationName() { build(AnnotatedWithRuntime.class, AnnotatedFields.class, AnnotatedTypes.class); - assertTypeHasAnnotation(AnnotatedWithRuntime.class, TYPE_RUNTIME_RETENTION_CANONICAL_DESCRIPTOR); - assertFieldHasAnnotation(AnnotatedFields.class, RUNTIME_RETENTION, FIELD_RUNTIME_RETENTION_CANONICAL_DESCRIPTOR); - assertFieldHasAnnotation(AnnotatedTypes.class, RUNTIME_RETENTION, TYPEUSE_RUNTIME_RETENTION_CANONICAL_DESCRIPTOR); + assertTypeHasAnnotation(AnnotatedWithRuntime.class, TYPE_RUNTIME_RETENTION_CANONICAL_CLASSNAME); + assertFieldHasAnnotation(AnnotatedFields.class, RUNTIME_RETENTION, FIELD_RUNTIME_RETENTION_CANONICAL_CLASSNAME); + assertFieldHasAnnotation(AnnotatedTypes.class, RUNTIME_RETENTION, TYPEUSE_RUNTIME_RETENTION_CANONICAL_CLASSNAME); } @Test @@ -128,6 +128,7 @@ public void typeAnnotationInheritance() { build(SubclassWithAnnotations.class); assertTypeHasAnnotation(SubclassWithAnnotations.class, TYPE_INHERITS); + assertTypeHasAnnotation(SuperclassWithAnnotations.class, TYPE_INHERITS); assertTypeDoesNotHaveAnnotation(SubclassWithAnnotations.class, TYPE_DOESNT_INHERIT); } @@ -136,6 +137,7 @@ public void fieldAnnotationInheritance() { build(SubclassWithAnnotations.class); assertFieldHasAnnotation(SubclassWithAnnotations.class, "inherits", FIELD_INHERITS); + assertFieldHasAnnotation(SuperclassWithAnnotations.class, "inherits", FIELD_INHERITS); assertFieldDoesNotHaveAnnotation(SubclassWithAnnotations.class, "doesntInherit", FIELD_DOESNT_INHERIT); } @@ -144,6 +146,7 @@ public void typeUseAnnotationInheritance() { build(SubclassWithAnnotations.class); assertFieldHasAnnotation(SubclassWithAnnotations.class, "inherits", TYPEUSE_INHERITS); + assertFieldHasAnnotation(SuperclassWithAnnotations.class, "inherits", TYPEUSE_INHERITS); assertFieldDoesNotHaveAnnotation(SubclassWithAnnotations.class, "doesntInherit", TYPEUSE_DOESNT_INHERIT); } @@ -171,6 +174,16 @@ public void typeAnnotationInPackage() { assertTypeDoesNotHaveAnnotation(AnnotatedPackage.class, INAPPLICABLE); } + @Test + public void searchIgnoredField() { + cacheBuilder = new AnnotationCacheBuilder( + TestSupportedAnnotations.values(), + Util.setOf(FieldAnnotationRuntimeRetention.class.getCanonicalName())); + build(AnnotatedFields.class); + + assertFieldDoesNotHaveAnnotation(AnnotatedFields.class, "runtimeRetention", FIELD_RUNTIME_RETENTION); + } + @Test public void searchNonExistingField() { build(AnnotatedFields.class); @@ -188,25 +201,16 @@ public void inapplicableAnnotationsAreNotFound() { @Test public void annotationsArrayParametersAreFoundOnClass() { - AnnotationWithClassValuesDescriptor annotation = new AnnotationWithClassValuesDescriptor(); + AnnotationWithClassValuesAnnotation annotation = new AnnotationWithClassValuesAnnotation(); Annotation[] supportedAnnotations = { annotation }; AnnotationCacheBuilder acb = new AnnotationCacheBuilder(supportedAnnotations, NO_INGORED_ANNOTATIONS); acb.build(AnnotationWithClassValuesContainer.class, cache); assertTypeHasAnnotation(AnnotationWithClassValuesContainer.class, annotation); - Set annotations = mapGetDescriptor(annotation); - assertTrue(annotations.contains("Ljavax/annotation/Nonnull;")); - assertTrue(annotations.contains("Lnl/jqno/equalsverifier/testhelpers/annotations/NotNull;")); - } - - private Set mapGetDescriptor(AnnotationWithClassValuesDescriptor annotation) { - Set result = new HashSet<>(); - for (Object o : annotation.properties.getArrayValues("annotations")) { - Type type = (Type)o; - result.add(type.getDescriptor()); - } - return result; + Set annotations = new HashSet<>(annotation.properties.getArrayValues("annotations")); + assertTrue(annotations.contains("javax.annotation.Nonnull")); + assertTrue(annotations.contains("nl.jqno.equalsverifier.testhelpers.annotations.NotNull")); } @Test @@ -257,12 +261,14 @@ private void assertFieldDoesNotHaveAnnotation(Class type, String fieldName, A assertFalse(cache.hasFieldAnnotation(type, fieldName, annotation)); } - private static class AnnotationWithClassValuesDescriptor implements Annotation { + private static class AnnotationWithClassValuesAnnotation implements Annotation { private AnnotationProperties properties; @Override - public Iterable descriptors() { - return Collections.singletonList(AnnotationWithClassValues.class.getSimpleName()); + public Set partialClassNames() { + Set result = new HashSet<>(); + result.add(AnnotationWithClassValues.class.getSimpleName()); + return result; } @Override @@ -271,8 +277,8 @@ public boolean inherits() { } @Override - public boolean validate(AnnotationProperties descriptor, AnnotationCache annotationCache, Set ignoredAnnotations) { - this.properties = descriptor; + public boolean validate(AnnotationProperties props, AnnotationCache annotationCache, Set ignoredAnnotations) { + this.properties = props; return true; } } diff --git a/src/test/java/nl/jqno/equalsverifier/internal/reflection/annotations/SupportedAnnotationsTest.java b/src/test/java/nl/jqno/equalsverifier/internal/reflection/annotations/SupportedAnnotationsTest.java index 0bfa72016..6bdd267b8 100644 --- a/src/test/java/nl/jqno/equalsverifier/internal/reflection/annotations/SupportedAnnotationsTest.java +++ b/src/test/java/nl/jqno/equalsverifier/internal/reflection/annotations/SupportedAnnotationsTest.java @@ -14,21 +14,21 @@ public class SupportedAnnotationsTest { @Test public void jsr305DefaultReturnsTrue_whenAnnotationHasNonnullAnnotation() { - AnnotationProperties props = new AnnotationProperties("Lnl/jqno/equalsverifier/testhelpers/annotations/DefaultNonnullJavax;"); + AnnotationProperties props = new AnnotationProperties("nl.jqno.equalsverifier.testhelpers.annotations.DefaultNonnullJavax"); boolean actual = SupportedAnnotations.JSR305_DEFAULT_ANNOTATION_NONNULL.validate(props, EMPTY_ANNOTATION_CACHE, NO_IGNORED_ANNOTATIONS); assertTrue(actual); } @Test public void jsr305DefaultReturnsFalse_whenAnnotationDoesntHaveNonnullAnnotation() { - AnnotationProperties props = new AnnotationProperties("Ljavax/annotation/Nonnull;"); + AnnotationProperties props = new AnnotationProperties("javax.annotation.Nonnull"); boolean actual = SupportedAnnotations.JSR305_DEFAULT_ANNOTATION_NONNULL.validate(props, EMPTY_ANNOTATION_CACHE, NO_IGNORED_ANNOTATIONS); assertFalse(actual); } @Test public void jsr305DefaultReturnsFalse_whenTypeDoesNotExist() { - AnnotationProperties props = new AnnotationProperties("Lnl/jqno/equalsverifier/TypeDoesNotExist;"); + AnnotationProperties props = new AnnotationProperties("nl.jqno.equalsverifier.TypeDoesNotExist"); boolean actual = SupportedAnnotations.JSR305_DEFAULT_ANNOTATION_NONNULL.validate(props, EMPTY_ANNOTATION_CACHE, NO_IGNORED_ANNOTATIONS); assertFalse(actual); } diff --git a/src/test/java/nl/jqno/equalsverifier/testhelpers/annotations/TestSupportedAnnotations.java b/src/test/java/nl/jqno/equalsverifier/testhelpers/annotations/TestSupportedAnnotations.java index 7e616d6d2..6efe9fa79 100644 --- a/src/test/java/nl/jqno/equalsverifier/testhelpers/annotations/TestSupportedAnnotations.java +++ b/src/test/java/nl/jqno/equalsverifier/testhelpers/annotations/TestSupportedAnnotations.java @@ -5,24 +5,23 @@ import nl.jqno.equalsverifier.internal.reflection.annotations.AnnotationProperties; import java.util.Arrays; -import java.util.List; +import java.util.HashSet; import java.util.Set; public enum TestSupportedAnnotations implements Annotation { - // Type's closing ; is added by AnnotationAccessor. - TYPE_RUNTIME_RETENTION(false, "Lnl/jqno/equalsverifier/testhelpers/annotations/TypeAnnotationRuntimeRetention"), - TYPE_CLASS_RETENTION(false, "Lnl/jqno/equalsverifier/testhelpers/annotations/TypeAnnotationClassRetention"), - FIELD_RUNTIME_RETENTION(false, "Lnl/jqno/equalsverifier/testhelpers/annotations/FieldAnnotationRuntimeRetention"), - FIELD_CLASS_RETENTION(false, "Lnl/jqno/equalsverifier/testhelpers/annotations/FieldAnnotationClassRetention"), - TYPEUSE_RUNTIME_RETENTION(false, "Lnl/jqno/equalsverifier/testhelpers/annotations/TypeUseAnnotationRuntimeRetention"), - TYPEUSE_CLASS_RETENTION(false, "Lnl/jqno/equalsverifier/testhelpers/annotations/TypeUseAnnotationClassRetention"), + TYPE_RUNTIME_RETENTION(false, "nl.jqno.equalsverifier.testhelpers.annotations.TypeAnnotationRuntimeRetention"), + TYPE_CLASS_RETENTION(false, "nl.jqno.equalsverifier.testhelpers.annotations.TypeAnnotationClassRetention"), + FIELD_RUNTIME_RETENTION(false, "nl.jqno.equalsverifier.testhelpers.annotations.FieldAnnotationRuntimeRetention"), + FIELD_CLASS_RETENTION(false, "nl.jqno.equalsverifier.testhelpers.annotations.FieldAnnotationClassRetention"), + TYPEUSE_RUNTIME_RETENTION(false, "nl.jqno.equalsverifier.testhelpers.annotations.TypeUseAnnotationRuntimeRetention"), + TYPEUSE_CLASS_RETENTION(false, "nl.jqno.equalsverifier.testhelpers.annotations.TypeUseAnnotationClassRetention"), - TYPE_RUNTIME_RETENTION_PARTIAL_DESCRIPTOR(false, "TypeAnnotationRuntimeRetention"), - TYPE_RUNTIME_RETENTION_CANONICAL_DESCRIPTOR(false, TypeAnnotationRuntimeRetention.class.getCanonicalName()), - FIELD_RUNTIME_RETENTION_PARTIAL_DESCRIPTOR(false, "FieldAnnotationRuntimeRetention"), - FIELD_RUNTIME_RETENTION_CANONICAL_DESCRIPTOR(false, FieldAnnotationRuntimeRetention.class.getCanonicalName()), - TYPEUSE_RUNTIME_RETENTION_PARTIAL_DESCRIPTOR(false, "TypeUseAnnotationRuntimeRetention"), - TYPEUSE_RUNTIME_RETENTION_CANONICAL_DESCRIPTOR(false, TypeUseAnnotationRuntimeRetention.class.getCanonicalName()), + TYPE_RUNTIME_RETENTION_PARTIAL_CLASSNAME(false, "TypeAnnotationRuntimeRetention"), + TYPE_RUNTIME_RETENTION_CANONICAL_CLASSNAME(false, TypeAnnotationRuntimeRetention.class.getCanonicalName()), + FIELD_RUNTIME_RETENTION_PARTIAL_CLASSNAME(false, "FieldAnnotationRuntimeRetention"), + FIELD_RUNTIME_RETENTION_CANONICAL_CLASSNAME(false, FieldAnnotationRuntimeRetention.class.getCanonicalName()), + TYPEUSE_RUNTIME_RETENTION_PARTIAL_CLASSNAME(false, "TypeUseAnnotationRuntimeRetention"), + TYPEUSE_RUNTIME_RETENTION_CANONICAL_CLASSNAME(false, TypeUseAnnotationRuntimeRetention.class.getCanonicalName()), TYPE_INHERITS(true, "TypeAnnotationInherits"), TYPE_DOESNT_INHERIT(false, "TypeAnnotationDoesntInherit"), @@ -40,16 +39,17 @@ public boolean validate(AnnotationProperties properties, AnnotationCache annotat }; private final boolean inherits; - private final List descriptors; + private final Set partialClassNames; - private TestSupportedAnnotations(boolean inherits, String... descriptors) { + private TestSupportedAnnotations(boolean inherits, String... partialClassNames) { this.inherits = inherits; - this.descriptors = Arrays.asList(descriptors); + this.partialClassNames = new HashSet<>(); + this.partialClassNames.addAll(Arrays.asList(partialClassNames)); } @Override - public Iterable descriptors() { - return descriptors; + public Set partialClassNames() { + return partialClassNames; } @Override