diff --git a/japicmp/src/test/java/japicmp/compat/CompatibilityChangesTest.java b/japicmp/src/test/java/japicmp/compat/CompatibilityChangesTest.java index 687df4dd..a2fa371e 100755 --- a/japicmp/src/test/java/japicmp/compat/CompatibilityChangesTest.java +++ b/japicmp/src/test/java/japicmp/compat/CompatibilityChangesTest.java @@ -2615,18 +2615,22 @@ public void testAnnotationOnClassModified() throws Exception { public List createOldClasses(ClassPool classPool) throws Exception { CtClass anAnnotation = CtAnnotationBuilder.create().name("japicmp.MyAnnotation").addToClassPool(classPool); CtMethodBuilder.create().name("foo").returnType(CtClass.intType).publicAccess().addToClass(anAnnotation); - return Collections.singletonList(anAnnotation); + CtClass aClass = CtClassBuilder.create().name("japicmp.Test").withAnnotation("japicmp.MyAnnotation", new CtElement("foo", 1000)).addToClassPool(classPool); + return Arrays.asList(aClass, anAnnotation); } @Override public List createNewClasses(ClassPool classPool) { CtClass anAnnotation = CtAnnotationBuilder.create().name("japicmp.MyAnnotation").addToClassPool(classPool); - return Arrays.asList(anAnnotation); + CtClass aClass = CtClassBuilder.create().name("japicmp.Test").withAnnotation("japicmp.MyAnnotation", new CtElement("foo", 999)).addToClassPool(classPool); + return Arrays.asList(aClass, anAnnotation); } }); JApiClass jApiClass = getJApiClass(jApiClasses, "japicmp.MyAnnotation"); JApiMethod jApiMethod = getJApiMethod(jApiClass.getMethods(), "foo"); assertThat(jApiMethod.getCompatibilityChanges(), hasItem(new JApiCompatibilityChange(JApiCompatibilityChangeType.METHOD_REMOVED))); + JApiClass jApiClassWithAnnotation = getJApiClass(jApiClasses, "japicmp.Test"); + assertThat(jApiClassWithAnnotation.getCompatibilityChanges(), hasItem(new JApiCompatibilityChange(JApiCompatibilityChangeType.ANNOTATION_MODIFIED))); } @Test @@ -2676,4 +2680,104 @@ public List createNewClasses(ClassPool classPool) throws Exception { JApiMethod jApiMethod = getJApiMethod(jApiClass.getMethods(), "method"); assertThat(jApiMethod.getCompatibilityChanges(), hasItem(new JApiCompatibilityChange(JApiCompatibilityChangeType.ANNOTATION_REMOVED))); } + + @Test + public void testAnnotationOnMethodModified() throws Exception { + JarArchiveComparatorOptions options = new JarArchiveComparatorOptions(); + List jApiClasses = ClassesHelper.compareClasses(options, new ClassesHelper.ClassesGenerator() { + @Override + public List createOldClasses(ClassPool classPool) throws Exception { + CtClass anAnnotation = CtAnnotationBuilder.create().name("japicmp.MyAnnotation").addToClassPool(classPool); + CtMethodBuilder.create().name("foo").returnType(CtClass.intType).publicAccess().addToClass(anAnnotation); + CtClass aClass = CtClassBuilder.create().name("japicmp.Test").addToClassPool(classPool); + CtMethodBuilder.create().publicAccess().name("method").withAnnotation("japicmp.MyAnnotation", new CtElement("foo", 1000)).addToClass(aClass); + return Arrays.asList(aClass, anAnnotation); + } + + @Override + public List createNewClasses(ClassPool classPool) throws Exception { + CtClass anAnnotation = CtAnnotationBuilder.create().name("japicmp.MyAnnotation").addToClassPool(classPool); + CtClass aClass = CtClassBuilder.create().name("japicmp.Test").addToClassPool(classPool); + CtMethodBuilder.create().publicAccess().name("method").withAnnotation("japicmp.MyAnnotation", new CtElement("foo", 999)).addToClass(aClass); + return Arrays.asList(aClass, anAnnotation); + } + }); + JApiClass jApiClass = getJApiClass(jApiClasses, "japicmp.Test"); + JApiMethod jApiMethod = getJApiMethod(jApiClass.getMethods(), "method"); + assertThat(jApiMethod.getCompatibilityChanges(), hasItem(new JApiCompatibilityChange(JApiCompatibilityChangeType.ANNOTATION_MODIFIED))); + } + + @Test + public void testAnnotationAddedToField() throws Exception { + JarArchiveComparatorOptions options = new JarArchiveComparatorOptions(); + List jApiClasses = ClassesHelper.compareClasses(options, new ClassesHelper.ClassesGenerator() { + @Override + public List createOldClasses(ClassPool classPool) throws Exception { + CtClass aClass = CtClassBuilder.create().name("japicmp.Test").addToClassPool(classPool); + CtFieldBuilder.create().name("field").addToClass(aClass); + return Collections.singletonList(aClass); + } + + @Override + public List createNewClasses(ClassPool classPool) throws Exception { + CtClass anAnnotation = CtAnnotationBuilder.create().name("japicmp.MyAnnotation").addToClassPool(classPool); + CtClass aClass = CtClassBuilder.create().name("japicmp.Test").addToClassPool(classPool); + CtFieldBuilder.create().withAnnotation("japicmp.MyAnnotation").name("field").addToClass(aClass); + return Arrays.asList(aClass, anAnnotation); + } + }); + JApiClass jApiClass = getJApiClass(jApiClasses, "japicmp.Test"); + JApiField jApiField = getJApiField(jApiClass.getFields(), "field"); + assertThat(jApiField.getCompatibilityChanges(), hasItem(new JApiCompatibilityChange(JApiCompatibilityChangeType.ANNOTATION_ADDED))); + } + + @Test + public void testAnnotationRemovedFromField() throws Exception { + JarArchiveComparatorOptions options = new JarArchiveComparatorOptions(); + List jApiClasses = ClassesHelper.compareClasses(options, new ClassesHelper.ClassesGenerator() { + @Override + public List createOldClasses(ClassPool classPool) throws Exception { + CtClass anAnnotation = CtAnnotationBuilder.create().name("japicmp.MyAnnotation").addToClassPool(classPool); + CtClass aClass = CtClassBuilder.create().name("japicmp.Test").addToClassPool(classPool); + CtFieldBuilder.create().name("field").withAnnotation("japicmp.MyAnnotation").addToClass(aClass); + return Arrays.asList(aClass, anAnnotation); + } + + @Override + public List createNewClasses(ClassPool classPool) throws Exception { + CtClass aClass = CtClassBuilder.create().name("japicmp.Test").addToClassPool(classPool); + CtFieldBuilder.create().name("field").addToClass(aClass); + return Collections.singletonList(aClass); + } + }); + JApiClass jApiClass = getJApiClass(jApiClasses, "japicmp.Test"); + JApiField jApiField = getJApiField(jApiClass.getFields(), "field"); + assertThat(jApiField.getCompatibilityChanges(), hasItem(new JApiCompatibilityChange(JApiCompatibilityChangeType.ANNOTATION_REMOVED))); + } + + @Test + public void testAnnotationOnFieldModified() throws Exception { + JarArchiveComparatorOptions options = new JarArchiveComparatorOptions(); + List jApiClasses = ClassesHelper.compareClasses(options, new ClassesHelper.ClassesGenerator() { + @Override + public List createOldClasses(ClassPool classPool) throws Exception { + CtClass anAnnotation = CtAnnotationBuilder.create().name("japicmp.MyAnnotation").addToClassPool(classPool); + CtMethodBuilder.create().name("foo").returnType(CtClass.intType).publicAccess().addToClass(anAnnotation); + CtClass aClass = CtClassBuilder.create().name("japicmp.Test").addToClassPool(classPool); + CtFieldBuilder.create().name("field").withAnnotation("japicmp.MyAnnotation", new CtElement("foo", 1000)).addToClass(aClass); + return Arrays.asList(aClass, anAnnotation); + } + + @Override + public List createNewClasses(ClassPool classPool) throws Exception { + CtClass anAnnotation = CtAnnotationBuilder.create().name("japicmp.MyAnnotation").addToClassPool(classPool); + CtClass aClass = CtClassBuilder.create().name("japicmp.Test").addToClassPool(classPool); + CtFieldBuilder.create().name("field").withAnnotation("japicmp.MyAnnotation", new CtElement("foo", 999)).addToClass(aClass); + return Arrays.asList(aClass, anAnnotation); + } + }); + JApiClass jApiClass = getJApiClass(jApiClasses, "japicmp.Test"); + JApiField jApiField = getJApiField(jApiClass.getFields(), "field"); + assertThat(jApiField.getCompatibilityChanges(), hasItem(new JApiCompatibilityChange(JApiCompatibilityChangeType.ANNOTATION_MODIFIED))); + } } diff --git a/japicmp/src/test/java/japicmp/util/CtClassBuilder.java b/japicmp/src/test/java/japicmp/util/CtClassBuilder.java index 7741f7d1..dd5f23ba 100644 --- a/japicmp/src/test/java/japicmp/util/CtClassBuilder.java +++ b/japicmp/src/test/java/japicmp/util/CtClassBuilder.java @@ -10,13 +10,15 @@ import javassist.bytecode.annotation.Annotation; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; public class CtClassBuilder { public static final String DEFAULT_CLASS_NAME = "japicmp.Test"; private String name = DEFAULT_CLASS_NAME; private int modifier = Modifier.PUBLIC; - private final List annotations = new ArrayList<>(); + private final Map annotations = new HashMap<>(); private Optional superclass = Optional.absent(); private final List interfaces = new ArrayList<>(); @@ -30,8 +32,8 @@ public CtClassBuilder syntheticModifier() { return this; } - public CtClassBuilder withAnnotation(String annotation) { - this.annotations.add(annotation); + public CtClassBuilder withAnnotation(String annotation, CtElement... elements) { + this.annotations.put(annotation, elements); return this; } @@ -69,11 +71,14 @@ public CtClass addToClassPool(ClassPool classPool) { ctClass = classPool.makeClass(this.name); } ctClass.setModifiers(this.modifier); - for (String annotation : annotations) { + for (String annotation : annotations.keySet()) { ClassFile classFile = ctClass.getClassFile(); ConstPool constPool = classFile.getConstPool(); AnnotationsAttribute attr = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag); Annotation annot = new Annotation(annotation, constPool); + for (CtElement element : annotations.get(annotation)) { + annot.addMemberValue(element.name, element.value.apply(constPool)); + } attr.setAnnotation(annot); ctClass.getClassFile2().addAttribute(attr); } diff --git a/japicmp/src/test/java/japicmp/util/CtElement.java b/japicmp/src/test/java/japicmp/util/CtElement.java new file mode 100644 index 00000000..e5897da1 --- /dev/null +++ b/japicmp/src/test/java/japicmp/util/CtElement.java @@ -0,0 +1,66 @@ +package japicmp.util; + +import java.util.function.Function; +import javassist.bytecode.ConstPool; +import javassist.bytecode.annotation.BooleanMemberValue; +import javassist.bytecode.annotation.ByteMemberValue; +import javassist.bytecode.annotation.CharMemberValue; +import javassist.bytecode.annotation.ClassMemberValue; +import javassist.bytecode.annotation.DoubleMemberValue; +import javassist.bytecode.annotation.FloatMemberValue; +import javassist.bytecode.annotation.IntegerMemberValue; +import javassist.bytecode.annotation.LongMemberValue; +import javassist.bytecode.annotation.MemberValue; +import javassist.bytecode.annotation.ShortMemberValue; +import javassist.bytecode.annotation.StringMemberValue; + +public class CtElement { + + public final String name; + public final Function value; + + private CtElement(String name, Function value) { + this.name = name; + this.value = value; + } + + public CtElement(String name, boolean value) { + this(name, cp -> new BooleanMemberValue(value, cp)); + } + + public CtElement(String name, byte value) { + this(name, cp -> new ByteMemberValue(value, cp)); + } + + public CtElement(String name, char value) { + this(name, cp -> new CharMemberValue(value, cp)); + } + + public CtElement(String name, short value) { + this(name, cp -> new ShortMemberValue(value, cp)); + } + + public CtElement(String name, int value) { + this(name, cp -> new IntegerMemberValue(cp, value)); + } + + public CtElement(String name, long value) { + this(name, cp -> new LongMemberValue(value, cp)); + } + + public CtElement(String name, float value) { + this(name, cp -> new FloatMemberValue(value, cp)); + } + + public CtElement(String name, double value) { + this(name, cp -> new DoubleMemberValue(value, cp)); + } + + public CtElement(String name, Class value) { + this(name, cp -> new ClassMemberValue(value.getName(), cp)); + } + + public CtElement(String name, String value) { + this(name, cp -> new StringMemberValue(value, cp)); + } +} diff --git a/japicmp/src/test/java/japicmp/util/CtFieldBuilder.java b/japicmp/src/test/java/japicmp/util/CtFieldBuilder.java index 1535e27b..ce0824cc 100644 --- a/japicmp/src/test/java/japicmp/util/CtFieldBuilder.java +++ b/japicmp/src/test/java/japicmp/util/CtFieldBuilder.java @@ -9,15 +9,15 @@ import javassist.bytecode.ConstPool; import javassist.bytecode.annotation.Annotation; -import java.util.ArrayList; -import java.util.List; +import java.util.HashMap; +import java.util.Map; public class CtFieldBuilder { public static final String DEFAULT_FIELD_NAME = "field"; private CtClass type = CtClass.intType; private String name = DEFAULT_FIELD_NAME; private int modifier = Modifier.PUBLIC; - private final List annotations = new ArrayList<>(); + private final Map annotations = new HashMap<>(); private Object constantValue = null; public CtFieldBuilder type(CtClass ctClass) { @@ -53,11 +53,14 @@ public CtField addToClass(CtClass ctClass) throws CannotCompileException { } else { ctClass.addField(ctField); } - for (String annotation : annotations) { + for (String annotation : annotations.keySet()) { ClassFile classFile = ctClass.getClassFile(); ConstPool constPool = classFile.getConstPool(); AnnotationsAttribute attr = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag); Annotation annot = new Annotation(annotation, constPool); + for (CtElement element : annotations.get(annotation)) { + annot.addMemberValue(element.name, element.value.apply(constPool)); + } attr.setAnnotation(annot); ctField.getFieldInfo().addAttribute(attr); } @@ -68,8 +71,8 @@ public static CtFieldBuilder create() { return new CtFieldBuilder(); } - public CtFieldBuilder withAnnotation(String annotation) { - this.annotations.add(annotation); + public CtFieldBuilder withAnnotation(String annotation, CtElement... elements) { + this.annotations.put(annotation, elements); return this; } diff --git a/japicmp/src/test/java/japicmp/util/CtMethodBuilder.java b/japicmp/src/test/java/japicmp/util/CtMethodBuilder.java index ecfea127..05ecb661 100644 --- a/japicmp/src/test/java/japicmp/util/CtMethodBuilder.java +++ b/japicmp/src/test/java/japicmp/util/CtMethodBuilder.java @@ -9,15 +9,15 @@ import javassist.bytecode.SignatureAttribute; import javassist.bytecode.annotation.Annotation; -import java.util.ArrayList; -import java.util.List; +import java.util.HashMap; +import java.util.Map; public class CtMethodBuilder extends CtBehaviorBuilder { private static final String DEFAULT_METHOD_NAME = "method"; private String body = "return null;"; private String name = DEFAULT_METHOD_NAME; private CtClass returnType; - private final List annotations = new ArrayList<>(); + private final Map annotations = new HashMap<>(); public CtMethodBuilder name(String name) { this.name = name; @@ -80,8 +80,8 @@ public CtMethodBuilder finalMethod() { return (CtMethodBuilder) super.finalMethod(); } - public CtMethodBuilder withAnnotation(String annotation) { - this.annotations.add(annotation); + public CtMethodBuilder withAnnotation(String annotation, CtElement... elements) { + this.annotations.put(annotation, elements); return this; } @@ -97,10 +97,13 @@ public CtMethod addToClass(CtClass declaringClass) throws CannotCompileException CtMethod ctMethod = CtNewMethod.make(this.modifier, this.returnType, this.name, this.parameters, this.exceptions, this.body, declaringClass); ctMethod.setModifiers(this.modifier); declaringClass.addMethod(ctMethod); - for (String annotation : annotations) { + for (String annotation : annotations.keySet()) { ConstPool constPool = declaringClass.getClassFile().getConstPool(); AnnotationsAttribute attr = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag); Annotation annot = new Annotation(annotation, constPool); + for (CtElement element : annotations.get(annotation)) { + annot.addMemberValue(element.name, element.value.apply(constPool)); + } attr.setAnnotation(annot); ctMethod.getMethodInfo().addAttribute(attr); }