diff --git a/japicmp/src/main/java/japicmp/compat/CompatibilityChanges.java b/japicmp/src/main/java/japicmp/compat/CompatibilityChanges.java index bc2ea174b..99bce5d75 100755 --- a/japicmp/src/main/java/japicmp/compat/CompatibilityChanges.java +++ b/japicmp/src/main/java/japicmp/compat/CompatibilityChanges.java @@ -265,6 +265,7 @@ private void checkIfConstructorsHaveChangedIncompatible(JApiClass jApiClass, Map } } checkIfAnnotationDeprecatedAdded(constructor); + checkIfVarargsChanged(constructor); } } @@ -367,6 +368,7 @@ public Integer callback(JApiClass superclass, Map classMap, J checkAbstractMethod(jApiClass, classMap, method); checkIfExceptionIsNowChecked(method); checkIfAnnotationDeprecatedAdded(method); + checkIfVarargsChanged(method); } } @@ -380,6 +382,15 @@ private void checkIfAnnotationDeprecatedAdded(JApiHasAnnotations jApiHasAnnotati } } + private void checkIfVarargsChanged(JApiBehavior behavior) { + if (behavior.getVarargsModifier().hasChangedFromTo(VarargsModifier.VARARGS, VarargsModifier.NON_VARARGS)) { + addCompatibilityChange(behavior, JApiCompatibilityChange.METHOD_NO_LONGER_VARARGS); + } + if (behavior.getVarargsModifier().hasChangedFromTo(VarargsModifier.NON_VARARGS, VarargsModifier.VARARGS)) { + addCompatibilityChange(behavior, JApiCompatibilityChange.METHOD_NOW_VARARGS); + } + } + private void checkAbstractMethod(JApiClass jApiClass, Map classMap, JApiMethod method) { if (isInterface(jApiClass)) { if (jApiClass.getChangeStatus() != JApiChangeStatus.NEW) { diff --git a/japicmp/src/main/java/japicmp/model/JApiBehavior.java b/japicmp/src/main/java/japicmp/model/JApiBehavior.java index a9ba6ff35..b95081100 100644 --- a/japicmp/src/main/java/japicmp/model/JApiBehavior.java +++ b/japicmp/src/main/java/japicmp/model/JApiBehavior.java @@ -39,6 +39,7 @@ public class JApiBehavior implements JApiHasModifiers, JApiHasChangeStatus, JApi private final JApiModifier bridgeModifier; private final JApiModifier syntheticModifier; private final JApiAttribute syntheticAttribute; + private final JApiModifier varargsModifier; private final List exceptions; protected JApiChangeStatus changeStatus; private final Optional oldLineNumber; @@ -57,6 +58,7 @@ public JApiBehavior(JApiClass jApiClass, String name, Optional extractVarargsModifier(Optional oldBehaviorOptional, Optional newBehaviorOptional) { + return ModifierHelper.extractModifierFromBehavior(oldBehaviorOptional, newBehaviorOptional, new ModifierHelper.ExtractModifierFromBehaviorCallback() { + private VarargsModifier getModifier(CtBehavior behavior) { + return Modifier.isVarArgs(behavior.getModifiers()) ? VarargsModifier.VARARGS : VarargsModifier.NON_VARARGS; + } + + @Override + public VarargsModifier getModifierForOld(CtBehavior oldBehavior) { + return getModifier(oldBehavior); + } + + @Override + public VarargsModifier getModifierForNew(CtBehavior newBehavior) { + return getModifier(newBehavior); + } + }); + } + + @Override @XmlElementWrapper(name = "modifiers") @XmlElement(name = "modifier") public List>>> getModifiers() { @@ -342,6 +366,7 @@ public String getName() { } @XmlAttribute + @Override public JApiChangeStatus getChangeStatus() { return changeStatus; } @@ -357,20 +382,24 @@ public void addParameter(JApiParameter jApiParameter) { } @XmlTransient + @Override public JApiModifier getAccessModifier() { return accessModifier; } @XmlTransient + @Override public JApiModifier getFinalModifier() { return finalModifier; } @XmlTransient + @Override public JApiModifier getStaticModifier() { return staticModifier; } + @Override public JApiModifier getAbstractModifier() { return this.abstractModifier; } @@ -384,20 +413,28 @@ public List>> getAttributes() { } @XmlTransient + @Override public JApiModifier getBridgeModifier() { return this.bridgeModifier; } @XmlTransient + @Override public JApiModifier getSyntheticModifier() { return this.syntheticModifier; } @XmlTransient + @Override public JApiAttribute getSyntheticAttribute() { return syntheticAttribute; } + @XmlTransient + public JApiModifier getVarargsModifier() { + return varargsModifier; + } + @Override @XmlAttribute public boolean isBinaryCompatible() { @@ -424,12 +461,14 @@ public boolean isSourceCompatible() { @XmlElementWrapper(name = "compatibilityChanges") @XmlElement(name = "compatibilityChange") + @Override public List getCompatibilityChanges() { return compatibilityChanges; } @XmlElementWrapper(name = "annotations") @XmlElement(name = "annotation") + @Override public List getAnnotations() { return annotations; } diff --git a/japicmp/src/main/java/japicmp/model/JApiCompatibilityChange.java b/japicmp/src/main/java/japicmp/model/JApiCompatibilityChange.java index dcef5d0a7..43d943472 100644 --- a/japicmp/src/main/java/japicmp/model/JApiCompatibilityChange.java +++ b/japicmp/src/main/java/japicmp/model/JApiCompatibilityChange.java @@ -28,6 +28,8 @@ public enum JApiCompatibilityChange { METHOD_NOW_FINAL(false, false, JApiSemanticVersionLevel.MAJOR), METHOD_NOW_STATIC(false, false, JApiSemanticVersionLevel.MAJOR), METHOD_NO_LONGER_STATIC(false, false, JApiSemanticVersionLevel.MAJOR), + METHOD_NOW_VARARGS(true, true, JApiSemanticVersionLevel.MINOR), + METHOD_NO_LONGER_VARARGS(true, false, JApiSemanticVersionLevel.MINOR), METHOD_ADDED_TO_INTERFACE(true, false, JApiSemanticVersionLevel.MINOR), METHOD_ADDED_TO_PUBLIC_CLASS(true, true, JApiSemanticVersionLevel.PATCH), METHOD_NOW_THROWS_CHECKED_EXCEPTION(true, false, JApiSemanticVersionLevel.MINOR), diff --git a/japicmp/src/main/java/japicmp/model/VarargsModifier.java b/japicmp/src/main/java/japicmp/model/VarargsModifier.java new file mode 100644 index 000000000..9063d3501 --- /dev/null +++ b/japicmp/src/main/java/japicmp/model/VarargsModifier.java @@ -0,0 +1,8 @@ +package japicmp.model; + +/** + * Represents the varargs modifier of a callable. + */ +public enum VarargsModifier implements JApiModifierBase { + VARARGS, NON_VARARGS +} diff --git a/japicmp/src/main/java/japicmp/util/ModifierHelper.java b/japicmp/src/main/java/japicmp/util/ModifierHelper.java index f53d4f37c..98a6e2387 100644 --- a/japicmp/src/main/java/japicmp/util/ModifierHelper.java +++ b/japicmp/src/main/java/japicmp/util/ModifierHelper.java @@ -1,6 +1,5 @@ package japicmp.util; -import japicmp.util.Optional; import japicmp.cmp.JarArchiveComparatorOptions; import japicmp.config.Options; import japicmp.model.AccessModifier; diff --git a/japicmp/src/test/java/japicmp/compat/CompatibilityChangesTest.java b/japicmp/src/test/java/japicmp/compat/CompatibilityChangesTest.java index 81ecdd0e7..7cd5a6b3f 100755 --- a/japicmp/src/test/java/japicmp/compat/CompatibilityChangesTest.java +++ b/japicmp/src/test/java/japicmp/compat/CompatibilityChangesTest.java @@ -6,6 +6,8 @@ import japicmp.util.*; import javassist.ClassPool; import javassist.CtClass; +import javassist.Modifier; + import org.hamcrest.core.Is; import org.junit.Test; @@ -18,6 +20,7 @@ import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.IsNot.not; +import static org.junit.Assert.assertEquals; public class CompatibilityChangesTest { @@ -646,6 +649,66 @@ public List createNewClasses(ClassPool classPool) throws Exception { assertThat(jApiMethod.isBinaryCompatible(), is(false)); } + @Test + public void testMethodNowVarargs() throws Exception { + JarArchiveComparatorOptions options = new JarArchiveComparatorOptions(); + List jApiClasses = ClassesHelper.compareClasses(options, new ClassesHelper.ClassesGenerator() { + @Override + public List createOldClasses(ClassPool classPool) throws Exception { + CtClass arrayClass = classPool.getCtClass("[I"); + + CtClass ctClass = CtClassBuilder.create().name("japicmp.Test").addToClassPool(classPool); + CtMethodBuilder.create().publicAccess().parameter(arrayClass).name("methodNowVarargs").addToClass(ctClass); + return Collections.singletonList(ctClass); + } + + @Override + public List createNewClasses(ClassPool classPool) throws Exception { + CtClass arrayClass = classPool.getCtClass("[I"); + + CtClass ctClass = CtClassBuilder.create().name("japicmp.Test").addToClassPool(classPool); + CtMethodBuilder.create().modifier(Modifier.VARARGS).publicAccess().parameter(arrayClass).name("methodNowVarargs").addToClass(ctClass); + return Collections.singletonList(ctClass); + } + }); + JApiClass jApiClass = getJApiClass(jApiClasses, "japicmp.Test"); + assertThat(jApiClass.getChangeStatus(), is(JApiChangeStatus.MODIFIED)); + assertThat(jApiClass.isBinaryCompatible(), is(true)); + assertThat(jApiClass.isSourceCompatible(), is(true)); + JApiMethod jApiMethod = getJApiMethod(jApiClass.getMethods(), "methodNowVarargs"); + assertEquals(jApiMethod.getCompatibilityChanges(), Collections.singletonList(JApiCompatibilityChange.METHOD_NOW_VARARGS)); + } + + @Test + public void testMethodNoLongerVarargs() throws Exception { + JarArchiveComparatorOptions options = new JarArchiveComparatorOptions(); + List jApiClasses = ClassesHelper.compareClasses(options, new ClassesHelper.ClassesGenerator() { + @Override + public List createOldClasses(ClassPool classPool) throws Exception { + CtClass arrayClass = classPool.getCtClass("[I"); + + CtClass ctClass = CtClassBuilder.create().name("japicmp.Test").addToClassPool(classPool); + CtMethodBuilder.create().modifier(Modifier.VARARGS).publicAccess().parameter(arrayClass).name("methodNoLongerVarargs").addToClass(ctClass); + return Collections.singletonList(ctClass); + } + + @Override + public List createNewClasses(ClassPool classPool) throws Exception { + CtClass arrayClass = classPool.getCtClass("[I"); + + CtClass ctClass = CtClassBuilder.create().name("japicmp.Test").addToClassPool(classPool); + CtMethodBuilder.create().publicAccess().parameter(arrayClass).name("methodNoLongerVarargs").addToClass(ctClass); + return Collections.singletonList(ctClass); + } + }); + JApiClass jApiClass = getJApiClass(jApiClasses, "japicmp.Test"); + assertThat(jApiClass.getChangeStatus(), is(JApiChangeStatus.MODIFIED)); + assertThat(jApiClass.isBinaryCompatible(), is(true)); + assertThat(jApiClass.isSourceCompatible(), is(false)); + JApiMethod jApiMethod = getJApiMethod(jApiClass.getMethods(), "methodNoLongerVarargs"); + assertEquals(jApiMethod.getCompatibilityChanges(), Collections.singletonList(JApiCompatibilityChange.METHOD_NO_LONGER_VARARGS)); + } + @Test public void testFieldStaticOverridesStatic() throws Exception { JarArchiveComparatorOptions options = new JarArchiveComparatorOptions(); diff --git a/src/site/markdown/MavenPlugin.md b/src/site/markdown/MavenPlugin.md index c3faf298c..db7ff0155 100644 --- a/src/site/markdown/MavenPlugin.md +++ b/src/site/markdown/MavenPlugin.md @@ -281,6 +281,8 @@ for each check. This allows you to customize the following verifications: | METHOD_NOW_FINAL | false | false | MAJOR | | METHOD_NOW_STATIC | false | false | MAJOR | | METHOD_NO_LONGER_STATIC | false | false | MAJOR | +| METHOD_NOW_VARARGS | true | true | MINOR | +| METHOD_NO_LONGER_VARARGS | true | false | MINOR | | METHOD_ADDED_TO_INTERFACE | true | false | MINOR | | METHOD_ADDED_TO_PUBLIC_CLASS | true | true | PATCH | | METHOD_NOW_THROWS_CHECKED_EXCEPTION | true | false | MINOR |