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