diff --git a/japicmp-testbase/japicmp-test-v1/src/main/java/japicmp/test/Interfaces.java b/japicmp-testbase/japicmp-test-v1/src/main/java/japicmp/test/Interfaces.java index 90dfbc8d7..77642efdb 100644 --- a/japicmp-testbase/japicmp-test-v1/src/main/java/japicmp/test/Interfaces.java +++ b/japicmp-testbase/japicmp-test-v1/src/main/java/japicmp/test/Interfaces.java @@ -77,4 +77,12 @@ public interface InterfaceAddMethod { public static class ClassImplementsComparable { } + + public interface InterfaceWithStaticMethod { + + } + + public interface InterfaceWithDefaultMethod { + + } } diff --git a/japicmp-testbase/japicmp-test-v2/src/main/java/japicmp/test/Interfaces.java b/japicmp-testbase/japicmp-test-v2/src/main/java/japicmp/test/Interfaces.java index 845350af2..041e8aef5 100644 --- a/japicmp-testbase/japicmp-test-v2/src/main/java/japicmp/test/Interfaces.java +++ b/japicmp-testbase/japicmp-test-v2/src/main/java/japicmp/test/Interfaces.java @@ -83,4 +83,16 @@ public int compareTo(ClassImplementsComparable o) { return 0; } } + + public interface InterfaceWithStaticMethod { + static void test() { + + } + } + + public interface InterfaceWithDefaultMethod { + default void test() { + + } + } } diff --git a/japicmp/src/main/java/japicmp/compat/CompatibilityChanges.java b/japicmp/src/main/java/japicmp/compat/CompatibilityChanges.java index 85aa7d470..81001550c 100755 --- a/japicmp/src/main/java/japicmp/compat/CompatibilityChanges.java +++ b/japicmp/src/main/java/japicmp/compat/CompatibilityChanges.java @@ -402,7 +402,7 @@ public Integer callback(JApiClass superclass, Map classMap, J } } // section 13.4.18 of "Java Language Specification" SE7 - if (isNotPrivate(method)) { + if (isNotPrivate(method) && !isInterface(method.getjApiClass())) { if (method.getStaticModifier().hasChangedFromTo(StaticModifier.NON_STATIC, StaticModifier.STATIC)) { addCompatibilityChange(method, JApiCompatibilityChange.METHOD_NOW_STATIC); } @@ -478,8 +478,12 @@ private void checkAbstractMethod(JApiClass jApiClass, Map cla List methodsWithSameSignature = getMethodsInImplementedInterfacesWithSameSignature(jApiClass, classMap, method); if (methodsWithSameSignature.size() == 0) { // new default method in interface - if (method.getAbstractModifier().hasChangedTo(AbstractModifier.NON_ABSTRACT)) { - addCompatibilityChange(method, JApiCompatibilityChange.METHOD_NEW_DEFAULT); + if (method.getAbstractModifier().getNewModifier().get() == AbstractModifier.NON_ABSTRACT) { + if (method.getStaticModifier().getNewModifier().get() == StaticModifier.STATIC) { + addCompatibilityChange(method, JApiCompatibilityChange.METHOD_NEW_STATIC_ADDED_TO_INTERFACE); + } else { + addCompatibilityChange(method, JApiCompatibilityChange.METHOD_NEW_DEFAULT); + } } else { addCompatibilityChange(method, JApiCompatibilityChange.METHOD_ADDED_TO_INTERFACE); } @@ -488,6 +492,7 @@ private void checkAbstractMethod(JApiClass jApiClass, Map cla for (JApiMethod jApiMethod : methodsWithSameSignature) { if (jApiMethod.getChangeStatus() != JApiChangeStatus.NEW) { allNew = false; + break; } } if (allNew) { @@ -496,11 +501,17 @@ private void checkAbstractMethod(JApiClass jApiClass, Map cla } } else if (method.getChangeStatus() == JApiChangeStatus.MODIFIED || method.getChangeStatus() == JApiChangeStatus.UNCHANGED) { JApiModifier abstractModifier = method.getAbstractModifier(); - // method changed from abstract to default if (abstractModifier.getOldModifier().isPresent() && abstractModifier.getOldModifier().get() == AbstractModifier.ABSTRACT && abstractModifier.getNewModifier().isPresent() && abstractModifier.getNewModifier().get() == AbstractModifier.NON_ABSTRACT) { + // method changed from abstract to default addCompatibilityChange(method, JApiCompatibilityChange.METHOD_ABSTRACT_NOW_DEFAULT); } + JApiModifier staticModifier = method.getStaticModifier(); + if (staticModifier.hasChangedFromTo(StaticModifier.NON_STATIC, StaticModifier.STATIC)) { + addCompatibilityChange(method, JApiCompatibilityChange.METHOD_NON_STATIC_IN_INTERFACE_NOW_STATIC); + } else if (staticModifier.hasChangedFromTo(StaticModifier.STATIC, StaticModifier.NON_STATIC)) { + addCompatibilityChange(method, JApiCompatibilityChange.METHOD_STATIC_IN_INTERFACE_NO_LONGER_STATIC); + } } } } else { diff --git a/japicmp/src/main/java/japicmp/model/JApiCompatibilityChange.java b/japicmp/src/main/java/japicmp/model/JApiCompatibilityChange.java index 02a59f401..9e4c06e22 100644 --- a/japicmp/src/main/java/japicmp/model/JApiCompatibilityChange.java +++ b/japicmp/src/main/java/japicmp/model/JApiCompatibilityChange.java @@ -43,8 +43,11 @@ public enum JApiCompatibilityChange { METHOD_ABSTRACT_ADDED_IN_IMPLEMENTED_INTERFACE(true, false, JApiSemanticVersionLevel.MINOR), METHOD_DEFAULT_ADDED_IN_IMPLEMENTED_INTERFACE(true, true, JApiSemanticVersionLevel.MINOR), METHOD_NEW_DEFAULT(true, true, JApiSemanticVersionLevel.MINOR), + METHOD_NEW_STATIC_ADDED_TO_INTERFACE(true, true, JApiSemanticVersionLevel.MINOR), METHOD_MOVED_TO_SUPERCLASS(true, true, JApiSemanticVersionLevel.PATCH), METHOD_ABSTRACT_NOW_DEFAULT(false, false, JApiSemanticVersionLevel.MAJOR), + METHOD_NON_STATIC_IN_INTERFACE_NOW_STATIC(true, true, JApiSemanticVersionLevel.MINOR), + METHOD_STATIC_IN_INTERFACE_NO_LONGER_STATIC(false, false, JApiSemanticVersionLevel.MAJOR), FIELD_STATIC_AND_OVERRIDES_STATIC(false, false, JApiSemanticVersionLevel.MAJOR), FIELD_LESS_ACCESSIBLE_THAN_IN_SUPERCLASS(false, false, JApiSemanticVersionLevel.MAJOR), FIELD_NOW_FINAL(false, false, JApiSemanticVersionLevel.MAJOR), diff --git a/japicmp/src/test/java/japicmp/compat/CompatibilityChangesTest.java b/japicmp/src/test/java/japicmp/compat/CompatibilityChangesTest.java index 3c95b9df4..6d52c9dcc 100755 --- a/japicmp/src/test/java/japicmp/compat/CompatibilityChangesTest.java +++ b/japicmp/src/test/java/japicmp/compat/CompatibilityChangesTest.java @@ -1124,6 +1124,98 @@ public List createNewClasses(ClassPool classPool) throws Exception { assertThat(jApiMethod.isSourceCompatible(), is(false)); } + @Test + public void testMethodStaticAddedToInterface() throws Exception { + JarArchiveComparatorOptions options = new JarArchiveComparatorOptions(); + options.setIncludeSynthetic(true); + options.setAccessModifier(AccessModifier.PRIVATE); + List jApiClasses = ClassesHelper.compareClasses(options, new ClassesHelper.ClassesGenerator() { + @Override + public List createOldClasses(ClassPool classPool) throws Exception { + CtClass ctClass = CtInterfaceBuilder.create().name("japicmp.Test").addToClassPool(classPool); + return Collections.singletonList(ctClass); + } + + @Override + public List createNewClasses(ClassPool classPool) throws Exception { + CtClass ctClass = CtInterfaceBuilder.create().name("japicmp.Test").addToClassPool(classPool); + CtMethodBuilder.create().publicAccess().staticAccess().name("method").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(), "method"); + assertThat(jApiMethod.getChangeStatus(), is(JApiChangeStatus.NEW)); + assertThat(jApiMethod.getCompatibilityChanges(), hasItem(JApiCompatibilityChange.METHOD_NEW_STATIC_ADDED_TO_INTERFACE)); + assertThat(jApiMethod.isBinaryCompatible(), is(true)); + assertThat(jApiMethod.isSourceCompatible(), is(true)); + } + + @Test + public void testMethodNotStaticChangesToStaticInInterface() throws Exception { + JarArchiveComparatorOptions options = new JarArchiveComparatorOptions(); + options.setIncludeSynthetic(true); + options.setAccessModifier(AccessModifier.PRIVATE); + List jApiClasses = ClassesHelper.compareClasses(options, new ClassesHelper.ClassesGenerator() { + @Override + public List createOldClasses(ClassPool classPool) throws Exception { + CtClass ctClass = CtInterfaceBuilder.create().name("japicmp.Test").addToClassPool(classPool); + CtMethodBuilder.create().publicAccess().name("method").addToClass(ctClass); + return Collections.singletonList(ctClass); + } + + @Override + public List createNewClasses(ClassPool classPool) throws Exception { + CtClass ctClass = CtInterfaceBuilder.create().name("japicmp.Test").addToClassPool(classPool); + CtMethodBuilder.create().publicAccess().staticAccess().name("method").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(), "method"); + assertThat(jApiMethod.getChangeStatus(), is(JApiChangeStatus.MODIFIED)); + assertThat(jApiMethod.getCompatibilityChanges(), hasItem(JApiCompatibilityChange.METHOD_NON_STATIC_IN_INTERFACE_NOW_STATIC)); + assertThat(jApiMethod.isBinaryCompatible(), is(true)); + assertThat(jApiMethod.isSourceCompatible(), is(true)); + } + + @Test + public void testMethodStaticChangesToNotStaticInInterface() throws Exception { + JarArchiveComparatorOptions options = new JarArchiveComparatorOptions(); + options.setIncludeSynthetic(true); + options.setAccessModifier(AccessModifier.PRIVATE); + List jApiClasses = ClassesHelper.compareClasses(options, new ClassesHelper.ClassesGenerator() { + @Override + public List createOldClasses(ClassPool classPool) throws Exception { + CtClass ctClass = CtInterfaceBuilder.create().name("japicmp.Test").addToClassPool(classPool); + CtMethodBuilder.create().publicAccess().staticAccess().name("method").addToClass(ctClass); + return Collections.singletonList(ctClass); + } + + @Override + public List createNewClasses(ClassPool classPool) throws Exception { + CtClass ctClass = CtInterfaceBuilder.create().name("japicmp.Test").addToClassPool(classPool); + CtMethodBuilder.create().publicAccess().name("method").addToClass(ctClass); + return Collections.singletonList(ctClass); + } + }); + JApiClass jApiClass = getJApiClass(jApiClasses, "japicmp.Test"); + assertThat(jApiClass.getChangeStatus(), is(JApiChangeStatus.MODIFIED)); + assertThat(jApiClass.isBinaryCompatible(), is(false)); + assertThat(jApiClass.isSourceCompatible(), is(false)); + JApiMethod jApiMethod = getJApiMethod(jApiClass.getMethods(), "method"); + assertThat(jApiMethod.getChangeStatus(), is(JApiChangeStatus.MODIFIED)); + assertThat(jApiMethod.getCompatibilityChanges(), hasItem(JApiCompatibilityChange.METHOD_STATIC_IN_INTERFACE_NO_LONGER_STATIC)); + assertThat(jApiMethod.isBinaryCompatible(), is(false)); + assertThat(jApiMethod.isSourceCompatible(), is(false)); + } + @Test public void testMethodAddedToPublicClass() throws Exception { JarArchiveComparatorOptions options = new JarArchiveComparatorOptions(); diff --git a/src/site/markdown/MavenPlugin.md b/src/site/markdown/MavenPlugin.md index 7d7747b40..960de6fca 100644 --- a/src/site/markdown/MavenPlugin.md +++ b/src/site/markdown/MavenPlugin.md @@ -300,8 +300,11 @@ for each check. This allows you to customize the following verifications: | METHOD_ABSTRACT_ADDED_IN_IMPLEMENTED_INTERFACE | true | false | MINOR | | METHOD_DEFAULT_ADDED_IN_IMPLEMENTED_INTERFACE | true | true | MINOR | | METHOD_NEW_DEFAULT | true | true | MINOR | +| METHOD_NEW_STATIC_ADDED_TO_INTERFACE | true | true | MINOR | | METHOD_ABSTRACT_NOW_DEFAULT | false | false | MAJOR | | METHOD_MOVED_TO_SUPERCLASS | true | true | PATCH | +| METHOD_NON_STATIC_IN_INTERFACE_NOW_STATIC | true | true | MINOR | +| METHOD_STATIC_IN_INTERFACE_NO_LONGER_STATIC | false | false | MAJOR | | FIELD_STATIC_AND_OVERRIDES_STATIC | false | false | MAJOR | | FIELD_LESS_ACCESSIBLE_THAN_IN_SUPERCLASS | false | false | MAJOR | | FIELD_NOW_FINAL | false | false | MAJOR |