Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optionally take changes to annotations into account #392

Merged
merged 4 commits into from
Apr 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion japicmp-testbase/japicmp-test-ant-task/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@
</path>

<taskdef resource="japicmp/ant/antlib.xml" classpathref="task.classpath" />
<japicmp oldjar="${project.build.directory}/guava-18.0.jar" newjar="${project.build.directory}/guava-19.0.jar" oldclasspath="old.classpath" newclasspath="new.classpath" onlyBinaryIncompatible="false" onlyModified="true" />
<japicmp oldjar="${project.build.directory}/guava-18.0.jar" newjar="${project.build.directory}/guava-19.0.jar" oldclasspath="old.classpath" newclasspath="new.classpath" onlyBinaryIncompatible="false" onlyModified="true" noAnnotations="true" />

</target>
</configuration>
Expand Down
38 changes: 28 additions & 10 deletions japicmp/src/main/java/japicmp/compat/CompatibilityChanges.java
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,12 @@ private void evaluateBinaryCompatibility(JApiClass jApiClass, Map<String, JApiCl
// section 13.4.4 of "Java Language Specification" SE7
checkIfSuperclassesOrInterfacesChangedIncompatible(jApiClass, classMap);
checkIfMethodsHaveChangedIncompatible(jApiClass, classMap);
checkIfConstructorsHaveChangedIncompatible(jApiClass);
checkIfConstructorsHaveChangedIncompatible(jApiClass, classMap);
checkIfFieldsHaveChangedIncompatible(jApiClass, classMap);
if (jApiClass.getClassType().getChangeStatus() == JApiChangeStatus.MODIFIED) {
addCompatibilityChange(jApiClass, JApiCompatibilityChangeType.CLASS_TYPE_CHANGED);
}
checkIfAnnotationDeprecatedAdded(jApiClass);
checkIfAnnotationsChanged(jApiClass, classMap);
if (hasModifierLevelDecreased(jApiClass)) {
addCompatibilityChange(jApiClass, JApiCompatibilityChangeType.CLASS_LESS_ACCESSIBLE);
}
Expand Down Expand Up @@ -193,7 +193,7 @@ private void checkIfFieldsHaveChangedIncompatible(JApiClass jApiClass, Map<Strin
if (isNotPrivate(field) && field.getType().hasChanged()) {
addCompatibilityChange(field, JApiCompatibilityChangeType.FIELD_TYPE_CHANGED);
}
checkIfAnnotationDeprecatedAdded(field);
checkIfAnnotationsChanged(field, classMap);
checkIfFieldGenericsChanged(field);
}
}
Expand Down Expand Up @@ -292,7 +292,7 @@ private boolean fieldTypeMatches(JApiField field1, JApiField field2) {
return matches;
}

private void checkIfConstructorsHaveChangedIncompatible(JApiClass jApiClass) {
private void checkIfConstructorsHaveChangedIncompatible(JApiClass jApiClass, Map<String, JApiClass> classMap) {
for (JApiConstructor constructor : jApiClass.getConstructors()) {
// section 13.4.6 of "Java Language Specification" SE7
if (isNotPrivate(constructor) && constructor.getChangeStatus() == JApiChangeStatus.REMOVED) {
Expand All @@ -305,7 +305,7 @@ private void checkIfConstructorsHaveChangedIncompatible(JApiClass jApiClass) {
}
}
checkIfExceptionIsNowChecked(constructor);
checkIfAnnotationDeprecatedAdded(constructor);
checkIfAnnotationsChanged(constructor, classMap);
checkIfVarargsChanged(constructor);
checkIfParametersGenericsChanged(constructor);
if (jApiClass.getChangeStatus().isNotNewOrRemoved()) {
Expand Down Expand Up @@ -425,7 +425,7 @@ private void checkIfMethodsHaveChangedIncompatible(JApiClass jApiClass, Map<Stri
}
checkAbstractMethod(jApiClass, classMap, method);
checkIfExceptionIsNowChecked(method);
checkIfAnnotationDeprecatedAdded(method);
checkIfAnnotationsChanged(method, classMap);
checkIfVarargsChanged(method);
checkIfParametersGenericsChanged(method);
if (method.getChangeStatus().isNotNewOrRemoved()) {
Expand Down Expand Up @@ -471,11 +471,29 @@ private void checkIfFieldGenericsChanged(JApiField jApiField) {
}
}

private void checkIfAnnotationDeprecatedAdded(JApiHasAnnotations jApiHasAnnotations) {
private void checkIfAnnotationsChanged(JApiHasAnnotations jApiHasAnnotations, Map<String, JApiClass> classMap) {
for (JApiAnnotation annotation : jApiHasAnnotations.getAnnotations()) {
if (annotation.getChangeStatus() == JApiChangeStatus.NEW || annotation.getChangeStatus() == JApiChangeStatus.MODIFIED) {
if (annotation.getFullyQualifiedName().equals(Deprecated.class.getName())) {
addCompatibilityChange(jApiHasAnnotations, JApiCompatibilityChangeType.ANNOTATION_DEPRECATED_ADDED);
final JApiChangeStatus status = annotation.getChangeStatus();
if (status == JApiChangeStatus.REMOVED) {
addCompatibilityChange(jApiHasAnnotations, JApiCompatibilityChangeType.ANNOTATION_REMOVED);
} else {
final boolean isNoAnnotations = this.jarArchiveComparator.getJarArchiveComparatorOptions().isNoAnnotations();
final boolean isDeprecated = annotation.getFullyQualifiedName().equals(Deprecated.class.getName());
final JApiClass annotationClass;
if (isNoAnnotations && !isDeprecated) {
annotationClass = null;
} else {
annotationClass = classMap.computeIfAbsent(annotation.getFullyQualifiedName(), fqn -> loadClass(fqn, EnumSet.allOf(Classpath.class)));
annotation.setJApiClass(annotationClass);
}
if (status == JApiChangeStatus.NEW || status == JApiChangeStatus.MODIFIED) {
addCompatibilityChange(jApiHasAnnotations, isDeprecated ? JApiCompatibilityChangeType.ANNOTATION_DEPRECATED_ADDED : JApiCompatibilityChangeType.ANNOTATION_ADDED);
} else if (annotationClass != null) {
checkIfMethodsHaveChangedIncompatible(annotationClass, classMap);
checkIfFieldsHaveChangedIncompatible(annotationClass, classMap);
if (!annotationClass.isSourceCompatible() || !annotationClass.isBinaryCompatible()) {
addCompatibilityChange(jApiHasAnnotations, JApiCompatibilityChangeType.ANNOTATION_MODIFIED_INCOMPATIBLE);
}
}
}
}
Expand Down
9 changes: 9 additions & 0 deletions japicmp/src/main/java/japicmp/model/JApiAnnotation.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public class JApiAnnotation implements JApiHasChangeStatus, JApiCompatibility {
private final Optional<Annotation> newAnnotation;
private final List<JApiAnnotationElement> elements = new LinkedList<>();
private final JApiChangeStatus changeStatus;
private Optional<JApiClass> correspondingJApiClass = Optional.absent();

public JApiAnnotation(String fullyQualifiedName, Optional<Annotation> oldAnnotation, Optional<Annotation> newAnnotation, JApiChangeStatus changeStatus) {
this.fullyQualifiedName = fullyQualifiedName;
Expand Down Expand Up @@ -134,6 +135,14 @@ public String getFullyQualifiedName() {
return fullyQualifiedName;
}

public void setJApiClass(JApiClass jApiClass) {
this.correspondingJApiClass = Optional.of(jApiClass);
}

public Optional<JApiClass> getCorrespondingJApiClass() {
return correspondingJApiClass;
}

@XmlTransient
public Optional<Annotation> getOldAnnotation() {
return oldAnnotation;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@

@XmlEnum
public enum JApiCompatibilityChangeType {
ANNOTATION_ADDED(true, true, JApiSemanticVersionLevel.PATCH),
ANNOTATION_DEPRECATED_ADDED(true, true, JApiSemanticVersionLevel.MINOR),
ANNOTATION_MODIFIED_INCOMPATIBLE(true, true, JApiSemanticVersionLevel.PATCH),
ANNOTATION_REMOVED(true, true, JApiSemanticVersionLevel.PATCH),
CLASS_REMOVED(false, false, JApiSemanticVersionLevel.MAJOR),
CLASS_NOW_ABSTRACT(false, false, JApiSemanticVersionLevel.MAJOR),
CLASS_NOW_FINAL(false, false, JApiSemanticVersionLevel.MAJOR),
Expand Down
142 changes: 142 additions & 0 deletions japicmp/src/test/java/japicmp/compat/CompatibilityChangesTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -1925,6 +1925,7 @@ public List<CtClass> createNewClasses(ClassPool classPool) throws Exception {
JApiClass jApiClass = getJApiClass(jApiClasses, "japicmp.Test");
JApiMethod jApiMethod = getJApiMethod(jApiClass.getMethods(), "method");
assertThat(jApiMethod.getCompatibilityChanges(), hasItem(new JApiCompatibilityChange(JApiCompatibilityChangeType.ANNOTATION_DEPRECATED_ADDED)));
assertThat(jApiMethod.getCompatibilityChanges(), not(hasItem(new JApiCompatibilityChange(JApiCompatibilityChangeType.ANNOTATION_ADDED))));
}

@Test
Expand All @@ -1945,6 +1946,7 @@ public List<CtClass> createNewClasses(ClassPool classPool) {
});
JApiClass jApiClass = getJApiClass(jApiClasses, "japicmp.Test");
assertThat(jApiClass.getCompatibilityChanges(), hasItem(new JApiCompatibilityChange(JApiCompatibilityChangeType.ANNOTATION_DEPRECATED_ADDED)));
assertThat(jApiClass.getCompatibilityChanges(), not(hasItem(new JApiCompatibilityChange(JApiCompatibilityChangeType.ANNOTATION_ADDED))));
}

@Test
Expand All @@ -1965,6 +1967,7 @@ public List<CtClass> createNewClasses(ClassPool classPool) {
});
JApiClass jApiClass = getJApiClass(jApiClasses, "japicmp.Test");
assertThat(jApiClass.getCompatibilityChanges(), not(hasItem(new JApiCompatibilityChange(JApiCompatibilityChangeType.ANNOTATION_DEPRECATED_ADDED))));
assertThat(jApiClass.getCompatibilityChanges(), hasItem(new JApiCompatibilityChange(JApiCompatibilityChangeType.ANNOTATION_REMOVED)));
}

/**
Expand Down Expand Up @@ -2516,4 +2519,143 @@ public List<CtClass> createNewClasses(ClassPool classPool) throws Exception {
assertThat(jApiMethod.getCompatibilityChanges().size(), is(1));
assertThat(jApiMethod.getCompatibilityChanges(), hasItem(new JApiCompatibilityChange(JApiCompatibilityChangeType.CLASS_GENERIC_TEMPLATE_CHANGED)));
}

@Test
public void testAnnotationAddedToClass() throws Exception {
JarArchiveComparatorOptions options = new JarArchiveComparatorOptions();
List<JApiClass> jApiClasses = ClassesHelper.compareClasses(options, new ClassesHelper.ClassesGenerator() {
@Override
public List<CtClass> createOldClasses(ClassPool classPool) {
CtClass aClass = CtClassBuilder.create().name("japicmp.Test").addToClassPool(classPool);
return Collections.singletonList(aClass);
}

@Override
public List<CtClass> createNewClasses(ClassPool classPool) {
CtClass anAnnotation = CtAnnotationBuilder.create().name("japicmp.MyAnnotation").addToClassPool(classPool);
CtClass aClass = CtClassBuilder.create().name("japicmp.Test").withAnnotation("japicmp.MyAnnotation").addToClassPool(classPool);
return Arrays.asList(aClass, anAnnotation);
}
});
JApiClass jApiClass = getJApiClass(jApiClasses, "japicmp.Test");
assertThat(jApiClass.getCompatibilityChanges(), hasItem(new JApiCompatibilityChange(JApiCompatibilityChangeType.ANNOTATION_ADDED)));
}

@Test
public void testAnnotationRemovedFromClass() throws Exception {
JarArchiveComparatorOptions options = new JarArchiveComparatorOptions();
List<JApiClass> jApiClasses = ClassesHelper.compareClasses(options, new ClassesHelper.ClassesGenerator() {
@Override
public List<CtClass> createOldClasses(ClassPool classPool) {
CtClass anAnnotation = CtAnnotationBuilder.create().name("japicmp.MyAnnotation").addToClassPool(classPool);
CtClass aClass = CtClassBuilder.create().name("japicmp.Test").withAnnotation("japicmp.MyAnnotation").addToClassPool(classPool);
return Arrays.asList(aClass, anAnnotation);
}

@Override
public List<CtClass> createNewClasses(ClassPool classPool) {
CtClass aClass = CtClassBuilder.create().name("japicmp.Test").addToClassPool(classPool);
return Collections.singletonList(aClass);
}
});
JApiClass jApiClass = getJApiClass(jApiClasses, "japicmp.Test");
assertThat(jApiClass.getCompatibilityChanges(), hasItem(new JApiCompatibilityChange(JApiCompatibilityChangeType.ANNOTATION_REMOVED)));
}

@Test
public void testAnnotationOnClassModified() throws Exception {
JarArchiveComparatorOptions options = new JarArchiveComparatorOptions();
List<JApiClass> jApiClasses = ClassesHelper.compareClasses(options, new ClassesHelper.ClassesGenerator() {
@Override
public List<CtClass> 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").withAnnotation("japicmp.MyAnnotation").addToClassPool(classPool);
return Arrays.asList(aClass, anAnnotation);
}

@Override
public List<CtClass> createNewClasses(ClassPool classPool) {
CtClass anAnnotation = CtAnnotationBuilder.create().name("japicmp.MyAnnotation").addToClassPool(classPool);
CtClass aClass = CtClassBuilder.create().name("japicmp.Test").withAnnotation("japicmp.MyAnnotation").addToClassPool(classPool);
return Arrays.asList(aClass, anAnnotation);
}
});
JApiClass jApiClass = getJApiClass(jApiClasses, "japicmp.Test");
assertThat(jApiClass.getCompatibilityChanges(), hasItem(new JApiCompatibilityChange(JApiCompatibilityChangeType.ANNOTATION_MODIFIED_INCOMPATIBLE)));
}

@Test
public void testAnnotationAddedToMethod() throws Exception {
JarArchiveComparatorOptions options = new JarArchiveComparatorOptions();
List<JApiClass> jApiClasses = ClassesHelper.compareClasses(options, new ClassesHelper.ClassesGenerator() {
@Override
public List<CtClass> createOldClasses(ClassPool classPool) throws Exception {
CtClass aClass = CtClassBuilder.create().name("japicmp.Test").addToClassPool(classPool);
CtMethodBuilder.create().publicAccess().name("method").addToClass(aClass);
return Collections.singletonList(aClass);
}

@Override
public List<CtClass> 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().withAnnotation("japicmp.MyAnnotation").name("method").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_ADDED)));
}

@Test
public void testAnnotationRemovedFromMethod() throws Exception {
JarArchiveComparatorOptions options = new JarArchiveComparatorOptions();
List<JApiClass> jApiClasses = ClassesHelper.compareClasses(options, new ClassesHelper.ClassesGenerator() {
@Override
public List<CtClass> createOldClasses(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").addToClass(aClass);
return Arrays.asList(aClass, anAnnotation);
}

@Override
public List<CtClass> createNewClasses(ClassPool classPool) throws Exception {
CtClass aClass = CtClassBuilder.create().name("japicmp.Test").addToClassPool(classPool);
CtMethodBuilder.create().publicAccess().name("method").addToClass(aClass);
return Collections.singletonList(aClass);
}
});
JApiClass jApiClass = getJApiClass(jApiClasses, "japicmp.Test");
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<JApiClass> jApiClasses = ClassesHelper.compareClasses(options, new ClassesHelper.ClassesGenerator() {
@Override
public List<CtClass> 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").addToClass(aClass);
return Arrays.asList(aClass, anAnnotation);
}

@Override
public List<CtClass> 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").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_INCOMPATIBLE)));
}
}
22 changes: 22 additions & 0 deletions japicmp/src/test/java/japicmp/util/CtAnnotationBuilder.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package japicmp.util;

import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;

public class CtAnnotationBuilder {
private String name = "japicmp.Test";

public CtAnnotationBuilder name(String name) {
this.name = name;
return this;
}

public CtClass addToClassPool(ClassPool classPool) {
return classPool.makeAnnotation(this.name);
}

public static CtAnnotationBuilder create() {
return new CtAnnotationBuilder();
}
}
Loading