Index: third_party/jmake/src/org/pantsbuild/jmake/CompatibilityChecker.java |
diff --git a/third_party/jmake/src/org/pantsbuild/jmake/CompatibilityChecker.java b/third_party/jmake/src/org/pantsbuild/jmake/CompatibilityChecker.java |
new file mode 100644 |
index 0000000000000000000000000000000000000000..bd74cfb3e0ad6529372945a2deb9b84c653de339 |
--- /dev/null |
+++ b/third_party/jmake/src/org/pantsbuild/jmake/CompatibilityChecker.java |
@@ -0,0 +1,610 @@ |
+/* Copyright (c) 2002-2008 Sun Microsystems, Inc. All rights reserved |
+ * |
+ * This program is distributed under the terms of |
+ * the GNU General Public License Version 2. See the LICENSE file |
+ * at the top of the source tree. |
+ */ |
+package org.pantsbuild.jmake; |
+ |
+import java.lang.reflect.Modifier; |
+import java.util.List; |
+import java.util.Set; |
+ |
+/** |
+ * This class implements checking of source compatibility of classes and supporting operations |
+ * |
+ * @author Misha Dmitriev |
+ * 12 March 2004 |
+ */ |
+public class CompatibilityChecker { |
+ |
+ private PCDManager pcdm; |
+ private RefClassFinder rf; |
+ ClassInfo oldClassInfo = null; |
+ ClassInfo newClassInfo = null; |
+ private boolean versionsCompatible; |
+ private boolean publicConstantChanged; |
+ |
+ public CompatibilityChecker(PCDManager pcdm, boolean failOnDependentJar, boolean noWarnOnDependentJar) { |
+ this.pcdm = pcdm; |
+ publicConstantChanged = false; |
+ rf = new RefClassFinder(pcdm, failOnDependentJar, noWarnOnDependentJar); |
+ } |
+ |
+ /** |
+ * Compares the two class versions for the given PCDEntry. Returns true if all changes are source |
+ * compatible, and false otherwise. |
+ */ |
+ public boolean compareClassVersions(PCDEntry entry) { |
+ // I once had the following optimization here with the comment "No sense to make any further checks if |
+ // everything is recompiled anyway", but now I believe it's wrong. For each class that was found changed |
+ // we need to know whether the new version is compatible with the old or not, since this may determine |
+ // whether the new version of this class is promoted into the pdb or not (see PCDManager.updateClassInfoInPCD()). |
+ // So, all changed classes should be checked just to correctly determine version compatibility. |
+ // if (publicConstantChanged) return false; |
+ |
+ oldClassInfo = pcdm.getClassInfoForPCDEntry(ClassInfo.VER_OLD, entry); |
+ newClassInfo = pcdm.getClassInfoForPCDEntry(ClassInfo.VER_NEW, entry); |
+ |
+ rf.initialize(oldClassInfo.name, entry.javaFileFullPath.endsWith(".jar")); |
+ versionsCompatible = true; |
+ |
+ checkAccessFlags(); |
+ checkSuperclasses(); |
+ checkImplementedInterfaces(); |
+ checkFields(); |
+ checkMethodsAndConstructors(); |
+ |
+ return versionsCompatible; |
+ } |
+ |
+ /** Find all dependent classes for a deleted class. */ |
+ public void checkDeletedClass(PCDEntry entry) { |
+ oldClassInfo = entry.oldClassInfo; |
+ rf.initialize(oldClassInfo.name, entry.javaFileFullPath.endsWith(".jar")); |
+ rf.findReferencingClassesForDeletedClass(oldClassInfo); |
+ // It may happen that the only reference to deleted class X is via "X.class" construct |
+ String packageToLookIn = |
+ oldClassInfo.isPublic() ? null : oldClassInfo.packageName; |
+ rf.findClassesDeclaringField(("class$" + oldClassInfo.name).intern(), "java/lang/Class", true, packageToLookIn); |
+ checkForFinalFields(); |
+ } |
+ |
+ /** Returns the names of classes affected by source incompatible changes to the new version of the checked class. */ |
+ public String[] getAffectedClasses() { |
+ return rf.getAffectedClassNames(); |
+ } |
+ |
+ /** All of the following methods return true if no source incompatible changes found, and false otherwise */ |
+ private void checkAccessFlags() { |
+ char oldClassFlags = oldClassInfo.accessFlags; |
+ char newClassFlags = newClassInfo.accessFlags; |
+ if (oldClassFlags == newClassFlags) { |
+ return; |
+ } |
+ |
+ if (!Modifier.isFinal(oldClassFlags) && Modifier.isFinal(newClassFlags)) { |
+ versionsCompatible = false; |
+ rf.findDirectSubclasses(oldClassInfo); |
+ } |
+ |
+ if (!Modifier.isAbstract(oldClassFlags) && Modifier.isAbstract(newClassFlags)) { |
+ versionsCompatible = false; |
+ rf.findReferencingClasses0(oldClassInfo); |
+ } |
+ |
+ // Now to accessibility modifiers checking... |
+ if (Modifier.isPublic(newClassFlags)) { |
+ return; |
+ } |
+ |
+ if (Modifier.isProtected(newClassFlags)) { |
+ if (Modifier.isPublic(oldClassFlags)) { |
+ versionsCompatible = false; |
+ rf.findDiffPackageAndNotSubReferencingClasses1(oldClassInfo); |
+ } |
+ } else if (Modifier.isPrivate(newClassFlags)) { |
+ if (!Modifier.isPrivate(oldClassFlags)) { |
+ versionsCompatible = false; |
+ } else { |
+ return; // private -> private, nothing more to check |
+ } |
+ if (Modifier.isPublic(oldClassFlags)) { |
+ rf.findReferencingClasses1(oldClassInfo); |
+ } else if (Modifier.isProtected(oldClassFlags)) { |
+ rf.findThisPackageOrSubReferencingClasses1(oldClassInfo); |
+ } else { |
+ rf.findThisPackageReferencingClasses1(oldClassInfo); |
+ } |
+ } else { // newClassFlags has default access, since public has already been excluded |
+ if (Modifier.isPublic(oldClassFlags)) { |
+ versionsCompatible = false; |
+ rf.findDiffPackageReferencingClasses1(oldClassInfo); |
+ } else if (Modifier.isProtected(oldClassFlags)) { |
+ versionsCompatible = false; |
+ rf.findDiffPackageAndSubReferencingClasses1(oldClassInfo); |
+ } |
+ } |
+ } |
+ |
+ private void checkSuperclasses() { |
+ List<String> oldSuperNames = oldClassInfo.getAllSuperclassNames(); |
+ List<String> newSuperNames = newClassInfo.getAllSuperclassNames(); |
+ |
+ int oldNamesSizeMinusOne = oldSuperNames.size() - 1; |
+ for (int i = 0; i <= oldNamesSizeMinusOne; i++) { |
+ String oldSuperName = oldSuperNames.get(i); |
+ if (!newSuperNames.contains(oldSuperName)) { |
+ versionsCompatible = false; |
+ ClassInfo missingSuperClass = |
+ pcdm.getClassInfoForName(ClassInfo.VER_OLD, oldSuperName); |
+ if (missingSuperClass == null) { // This class is not in project |
+ missingSuperClass = |
+ ClassPath.getClassInfoForName(oldSuperName, pcdm); |
+ if (missingSuperClass == null) { |
+ missingSuperClass = new ClassInfo(oldSuperName, pcdm); |
+ } |
+ } |
+ rf.findReferencingClasses2(missingSuperClass, oldClassInfo); |
+ } |
+ } |
+ |
+ // Now check if the class is an exception, and its kind has changed from unchecked to checked |
+ if (oldClassInfo.isInterface() || oldSuperNames.size() == 0) { |
+ return; |
+ } |
+ if (!(oldSuperNames.contains("java/lang/RuntimeException") || oldSuperNames.contains("java/lang/Error"))) { |
+ return; |
+ } |
+ if (!(newSuperNames.contains("java/lang/RuntimeException") || newSuperNames.contains("java/lang/Error"))) { |
+ if (!newSuperNames.contains("java/lang/Throwable")) { |
+ return; |
+ } |
+ // Ok, exception kind has changed from unchecked to checked. |
+ versionsCompatible = false; |
+ rf.findReferencingClasses0(oldClassInfo); |
+ rf.findRefsToMethodsThrowingException(oldClassInfo); |
+ } |
+ } |
+ |
+ private void checkImplementedInterfaces() { |
+ Set<String> oldIntfNames = oldClassInfo.getAllImplementedIntfNames(); |
+ Set<String> newIntfNames = newClassInfo.getAllImplementedIntfNames(); |
+ |
+ for (String oldIntfName : oldIntfNames) { |
+ if (!newIntfNames.contains(oldIntfName)) { |
+ versionsCompatible = false; |
+ ClassInfo missingSuperInterface = |
+ pcdm.getClassInfoForName(ClassInfo.VER_OLD, oldIntfName); |
+ if (missingSuperInterface == null) { // This class is not in project |
+ missingSuperInterface = |
+ ClassPath.getClassInfoForName(oldIntfName, pcdm); |
+ if (missingSuperInterface == null) { |
+ missingSuperInterface = new ClassInfo(oldIntfName, pcdm); |
+ } |
+ } |
+ rf.findReferencingClasses2(missingSuperInterface, oldClassInfo); |
+ } |
+ } |
+ |
+ // Check if the class is abstract, and an interface has been added to its list of implemented interfaces |
+ if (newClassInfo.isAbstract()) { |
+ for (String newIntfName : newIntfNames) { |
+ if (!oldIntfNames.contains(newIntfName)) { |
+ versionsCompatible = false; |
+ rf.findConcreteSubclasses(oldClassInfo); |
+ break; |
+ } |
+ } |
+ } |
+ } |
+ |
+ private void checkFields() { |
+ String oFNames[] = oldClassInfo.fieldNames; |
+ String oFSignatures[] = oldClassInfo.fieldSignatures; |
+ char oFFlags[] = oldClassInfo.fieldAccessFlags; |
+ String nFNames[] = newClassInfo.fieldNames; |
+ String nFSignatures[] = newClassInfo.fieldSignatures; |
+ char nFFlags[] = newClassInfo.fieldAccessFlags; |
+ int oFLen = oFNames != null ? oFNames.length : 0; |
+ int nFLen = nFNames != null ? nFNames.length : 0; |
+ |
+ int oFMod, nFMod; |
+ String oFName, oFSig, nFName; |
+ int i, j, k, endIdx; |
+ int nonMatchingNewFields = nFLen; |
+ |
+ for (i = 0; i < oFLen; i++) { |
+ oFMod = oFFlags[i]; |
+ if (Modifier.isPrivate(oFMod)) { |
+ continue; // Changes to private fields don't affect compatibility |
+ } |
+ oFName = oFNames[i]; |
+ oFSig = oFSignatures[i]; |
+ boolean found = false; |
+ |
+ // Look for the same field in the new version considering name and type |
+ endIdx = nFLen - 1; |
+ k = i < nFLen ? i : endIdx; |
+ for (j = 0; j < nFLen; j++) { |
+ if (oFName.equals(nFNames[k]) && |
+ oFSig.equals(nFSignatures[k])) { |
+ found = true; |
+ break; |
+ } |
+ if (k < endIdx) { |
+ k++; |
+ } else { |
+ k = 0; |
+ } |
+ } |
+ |
+ if (found) { |
+ nonMatchingNewFields--; |
+ nFMod = nFFlags[k]; |
+ checkFieldModifiers(oFMod, nFMod, i, k); |
+ if (publicConstantChanged) { |
+ return; |
+ } |
+ } else { // Matching field not found |
+ if (Modifier.isStatic(oFMod) && Modifier.isFinal(oFMod) && |
+ oldClassInfo.primitiveConstantInitValues != null && |
+ oldClassInfo.primitiveConstantInitValues[i] != null) { |
+ // Compile-time constant deleted |
+ versionsCompatible = false; |
+ rf.findAllProjectClasses(oldClassInfo, i); |
+ if (Modifier.isPublic(oFMod)) { |
+ publicConstantChanged = true; |
+ return; |
+ } |
+ } else { |
+ versionsCompatible = false; |
+ rf.findReferencingClassesForField(oldClassInfo, i); |
+ } |
+ } |
+ } |
+ |
+ if (nonMatchingNewFields > 0) { // There are some fields declared in the new version which don't exist in the old one |
+ // Look for fields hiding same-named fields in superclasses |
+ for (i = 0; i < nFLen; i++) { |
+ nFName = nFNames[i]; |
+ |
+ boolean found = false; |
+ for (j = 0; j < oFLen; j++) { |
+ if (nFName.equals(oFNames[j])) { |
+ found = true; |
+ break; |
+ } |
+ } |
+ if (found) { |
+ continue; // nFName is not an added field |
+ } |
+ String superName = oldClassInfo.superName; |
+ ClassInfo superInfo; |
+ while (superName != null) { |
+ superInfo = |
+ pcdm.getClassInfoForName(ClassInfo.VER_OLD, superName); |
+ if (superInfo == null) { |
+ break; |
+ } |
+ String[] superOFNames = superInfo.fieldNames; |
+ int superOFNamesLen = superOFNames != null ? superOFNames.length |
+ : 0; |
+ for (j = 0; j < superOFNamesLen; j++) { |
+ if (nFName == superOFNames[j]) { |
+ versionsCompatible = false; |
+ rf.findReferencingClassesForField(superInfo, j); |
+ } |
+ } |
+ superName = superInfo.superName; |
+ } |
+ } |
+ } |
+ } |
+ |
+ /** It is already known that old field is not private */ |
+ private void checkFieldModifiers(int oFMod, int nFMod, int oldFieldIdx, int newFieldIdx) { |
+ if (oFMod == nFMod) { |
+ if (Modifier.isFinal(oFMod) && |
+ (!ClassInfo.constFieldInitValuesEqual(oldClassInfo, oldFieldIdx, newClassInfo, newFieldIdx))) { |
+ versionsCompatible = false; |
+ rf.findAllProjectClasses(oldClassInfo, oldFieldIdx); |
+ if (Modifier.isPublic(oFMod)) { |
+ publicConstantChanged = true; // Means we will have to recompile ALL project classes |
+ } |
+ return; |
+ } |
+ } |
+ |
+ // These tests are ordered such that if a previous test succeeds, there is no need to do further tests, since that |
+ // former test will cause more classes to be checked than any of the further tests. That is why it is possible to |
+ // check properties that are in fact independent (e.g. accessibility vs. static/non-static) together. But this |
+ // optimization only works since all kinds of tests result in the same kind of find..ReferencingClassesForField() |
+ // outcome. For methods this is not true, and so there we have to check independent properties separately. |
+ if (Modifier.isStatic(oFMod) && Modifier.isFinal(oFMod) && // oFMod is known to be non-private |
+ (!Modifier.isFinal(nFMod) || !ClassInfo.constFieldInitValuesEqual(oldClassInfo, oldFieldIdx, newClassInfo, newFieldIdx))) { |
+ versionsCompatible = false; |
+ rf.findAllProjectClasses(oldClassInfo, oldFieldIdx); |
+ if (Modifier.isPublic(oFMod)) { |
+ publicConstantChanged = true; |
+ } |
+ } else if (Modifier.isPrivate(nFMod) || // oFMod is known to be non-private |
+ (!Modifier.isFinal(oFMod) && Modifier.isFinal(nFMod)) || |
+ (Modifier.isStatic(oFMod) != Modifier.isStatic(nFMod)) || |
+ (Modifier.isVolatile(oFMod) != Modifier.isVolatile(nFMod))) { |
+ versionsCompatible = false; |
+ rf.findReferencingClassesForField(oldClassInfo, oldFieldIdx); |
+ } else if (Modifier.isPublic(oFMod) && Modifier.isProtected(nFMod)) { |
+ versionsCompatible = false; |
+ rf.findDiffPackageReferencingClassesForField(oldClassInfo, oldFieldIdx); |
+ } else if ((Modifier.isPublic(oFMod) || Modifier.isProtected(oFMod)) && |
+ (!(Modifier.isPublic(nFMod) || Modifier.isProtected(nFMod) || Modifier.isPrivate(nFMod)))) { |
+ versionsCompatible = false; |
+ if (Modifier.isPublic(oFMod)) { |
+ rf.findDiffPackageReferencingClassesForField(oldClassInfo, oldFieldIdx); |
+ } else { |
+ rf.findDiffPackageAndSubReferencingClassesForField(oldClassInfo, oldFieldIdx); |
+ } |
+ } |
+ } |
+ |
+ private void checkForFinalFields() { |
+ char oFFlags[] = oldClassInfo.fieldAccessFlags; |
+ int oFLen = oldClassInfo.fieldNames != null ? oldClassInfo.fieldNames.length |
+ : 0; |
+ int oFMod; |
+ |
+ for (int i = 0; i < oFLen; i++) { |
+ oFMod = oFFlags[i]; |
+ if (Modifier.isPrivate(oFMod)) { |
+ continue; // Changes to private fields don't affect compatibility |
+ } |
+ if (Modifier.isStatic(oFMod) && Modifier.isFinal(oFMod)) { |
+ rf.findAllProjectClasses(oldClassInfo, i); |
+ if (Modifier.isPublic(oFMod)) { |
+ publicConstantChanged = true; |
+ return; |
+ } |
+ } |
+ } |
+ } |
+ |
+ private void checkMethodsAndConstructors() { |
+ String oMNames[] = oldClassInfo.methodNames; |
+ String oMSignatures[] = oldClassInfo.methodSignatures; |
+ char oMFlags[] = oldClassInfo.methodAccessFlags; |
+ String nMNames[] = newClassInfo.methodNames; |
+ String nMSignatures[] = newClassInfo.methodSignatures; |
+ char nMFlags[] = newClassInfo.methodAccessFlags; |
+ int oMLen = oMNames != null ? oMNames.length : 0; |
+ int nMLen = nMNames != null ? nMNames.length : 0; |
+ |
+ int oMMod, nMMod; |
+ String oMName, oMSig, nMName, nMSig; |
+ int i, j, k, endIdx; |
+ int nonMatchingNewMethods = nMLen; |
+ |
+ for (i = 0; i < oMLen; i++) { |
+ oMMod = oMFlags[i]; |
+ if (Modifier.isPrivate(oMMod)) { |
+ continue; // Changes to private methods don't affect compatibility |
+ } |
+ oMName = oMNames[i]; |
+ oMSig = oMSignatures[i]; |
+ boolean found = false; |
+ |
+ // Look for the same method in the new version considering name and signature |
+ endIdx = nMLen - 1; |
+ k = i < nMLen ? i : endIdx; |
+ for (j = 0; j < nMLen; j++) { |
+ if (oMName == nMNames[k] && oMSig == nMSignatures[k]) { |
+ found = true; |
+ break; |
+ } |
+ if (k < endIdx) { |
+ k++; |
+ } else { |
+ k = 0; |
+ } |
+ } |
+ |
+ if (found) { |
+ nonMatchingNewMethods--; |
+ nMMod = nMFlags[k]; |
+ if (oMMod != nMMod) { |
+ checkMethodModifiers(oMMod, nMMod, i); |
+ } |
+ |
+ // Check if the new method throws more exceptions than the old one |
+ if (newClassInfo.checkedExceptions != null && newClassInfo.checkedExceptions[k] != null) { |
+ if (oldClassInfo.checkedExceptions == null) { |
+ versionsCompatible = false; |
+ rf.findReferencingClassesForMethod(oldClassInfo, i); |
+ } else if (oldClassInfo.checkedExceptions[i] == null) { |
+ versionsCompatible = false; |
+ rf.findReferencingClassesForMethod(oldClassInfo, i); |
+ } else { |
+ String oldExceptions[] = |
+ oldClassInfo.checkedExceptions[i]; |
+ String newExceptions[] = |
+ newClassInfo.checkedExceptions[k]; |
+ for (int ei = 0; ei < newExceptions.length; ei++) { |
+ String newEx = newExceptions[ei]; |
+ found = false; |
+ for (int ej = 0; ej < oldExceptions.length; ej++) { |
+ if (newEx.equals(oldExceptions[ej])) { |
+ found = true; |
+ break; |
+ } |
+ } |
+ if (!found) { |
+ versionsCompatible = false; |
+ rf.findReferencingClassesForMethod(oldClassInfo, i); |
+ break; |
+ } |
+ } |
+ } |
+ } |
+ } else { // Matching method not found |
+ versionsCompatible = false; |
+ rf.findReferencingClassesForMethod(oldClassInfo, i); |
+ // Deleting a concrete method from an abstract class is a special case |
+ if (oldClassInfo.isAbstract() && !Modifier.isAbstract(oMMod)) { |
+ rf.findConcreteSubclassesNotOverridingAbstractMethod(oldClassInfo, oldClassInfo, i); |
+ } |
+ } |
+ } |
+ |
+ if (nonMatchingNewMethods > 0) { // There are some methods/constructors declared in the new version which don't exist in the old one |
+ if (!oldClassInfo.isInterface()) { |
+ for (i = 0; i < nMLen; i++) { |
+ nMMod = nMFlags[i]; |
+ if (Modifier.isPrivate(nMMod)) { |
+ continue; |
+ } |
+ String newMName = nMNames[i]; |
+ final String newMSig = nMSignatures[i]; |
+ final boolean isStatic = Modifier.isStatic(nMMod); |
+ |
+ boolean found = false; |
+ for (j = 0; j < oMLen; j++) { |
+ if (newMName.equals(oMNames[j]) && |
+ newMSig.equals(oMSignatures[j])) { |
+ found = true; |
+ break; |
+ } |
+ } |
+ if (found) { |
+ continue; // nMName is not an added method |
+ } |
+ // Check if the new method is a static one that hides an inherited static method |
+ // Check if the new method overloads an existing (declared or inherited) method. Overloading test is rough - |
+ // we just check if the number of parameters is the same. Note that if a new constructor has been added, it |
+ // can be treated in the same way, except that we shouldn't look up "same name methods" for it in superclasses. |
+ oldClassInfo.findExistingSameNameMethods(newMName, |
+ !newMName.equals("<init>"), false, |
+ new ClassInfo.MethodHandler() { |
+ |
+ void handleMethod(ClassInfo classInfo, int methodIdx) { |
+ String otherMSig = |
+ classInfo.methodSignatures[methodIdx]; |
+ if ((newMSig.equals(otherMSig) && isStatic && |
+ classInfo != oldClassInfo) || |
+ (newMSig != otherMSig && |
+ Utils.sameParamNumber(newMSig, otherMSig))) { |
+ versionsCompatible = false; |
+ rf.findReferencingClassesForMethod(classInfo, methodIdx); |
+ } |
+ } |
+ }); |
+ |
+ if (Modifier.isAbstract(nMMod)) { |
+ // An abstract method added to the class. Find any concrete subclasses that don't override |
+ // or inherit a concrete implementation of this method. |
+ versionsCompatible = false; |
+ rf.findConcreteSubclassesNotOverridingAbstractMethod(oldClassInfo, newClassInfo, i); |
+ } |
+ // Check if there is a method with the same name in some subclass, such that it now overrides |
+ // or overloads the added method. |
+ if (subclassesDeclareSameNameMethod(oldClassInfo, newMName)) { |
+ versionsCompatible = false; |
+ } |
+ } |
+ } else { // We are checking an interface. |
+ for (i = 0; i < nMLen; i++) { |
+ String newMName = nMNames[i]; |
+ final String newMSig = nMSignatures[i]; |
+ |
+ boolean found = false; |
+ for (j = 0; j < oMLen; j++) { |
+ if (newMName == oMNames[j] && newMSig == oMSignatures[j]) { |
+ found = true; |
+ break; |
+ } |
+ } |
+ |
+ if (!found) { |
+ versionsCompatible = false; |
+ |
+ // Check if the new method overloads an existing (declared or inherited) method. Overloading test is rough - |
+ // we just check if the number of parameters is the same. |
+ oldClassInfo.findExistingSameNameMethods(newMName, true, true, new ClassInfo.MethodHandler() { |
+ |
+ void handleMethod(ClassInfo classInfo, int methodIdx) { |
+ String otherMSig = |
+ classInfo.methodSignatures[methodIdx]; |
+ if (newMSig != otherMSig && |
+ Utils.sameParamNumber(newMSig, otherMSig)) { |
+ rf.findReferencingClassesForMethod(classInfo, methodIdx); |
+ } |
+ } |
+ }); |
+ |
+ rf.findDirectlyAndOtherwiseImplementingConcreteClasses(oldClassInfo); |
+ rf.findAbstractSubtypesWithSameNameMethod(oldClassInfo, newMName, newMSig); |
+ break; |
+ } |
+ } |
+ } |
+ } |
+ } |
+ |
+ private void checkMethodModifiers(int oMMod, int nMMod, int oldMethodIdx) { |
+ if (Modifier.isPrivate(nMMod)) { |
+ versionsCompatible = false; |
+ rf.findReferencingClassesForMethod(oldClassInfo, oldMethodIdx); |
+ } else if (Modifier.isPublic(oMMod) && Modifier.isProtected(nMMod)) { |
+ versionsCompatible = false; |
+ rf.findDiffPackageReferencingClassesForMethod(oldClassInfo, oldMethodIdx); |
+ } else if ((Modifier.isPublic(oMMod) || Modifier.isProtected(oMMod)) && |
+ (!(Modifier.isPublic(nMMod) || Modifier.isProtected(nMMod) || Modifier.isPrivate(nMMod)))) { |
+ versionsCompatible = false; |
+ if (Modifier.isPublic(oMMod)) { |
+ rf.findDiffPackageReferencingClassesForMethod(oldClassInfo, oldMethodIdx); |
+ } else { |
+ rf.findDiffPackageAndSubReferencingClassesForMethod(oldClassInfo, oldMethodIdx); |
+ } |
+ } else if ((Modifier.isPrivate(oMMod) && !Modifier.isPrivate(nMMod)) || |
+ (Modifier.isProtected(oMMod) && Modifier.isPublic(nMMod)) || |
+ (!(Modifier.isPublic(oMMod) || Modifier.isProtected(oMMod) || Modifier.isPrivate(oMMod)) && |
+ (Modifier.isPublic(nMMod) || Modifier.isProtected(nMMod)))) { |
+ versionsCompatible = false; |
+ rf.findSubclassesReimplementingMethod(oldClassInfo, oldMethodIdx); |
+ } |
+ |
+ if ((!Modifier.isAbstract(oMMod) && Modifier.isAbstract(nMMod)) || |
+ (Modifier.isStatic(oMMod) != Modifier.isStatic(nMMod))) { |
+ versionsCompatible = false; |
+ rf.findReferencingClassesForMethod(oldClassInfo, oldMethodIdx); |
+ if (!Modifier.isAbstract(oMMod) && Modifier.isAbstract(nMMod)) { |
+ rf.findConcreteSubclassesNotOverridingAbstractMethod(oldClassInfo, newClassInfo, oldMethodIdx); |
+ } |
+ } |
+ if (!Modifier.isFinal(oMMod) && Modifier.isFinal(nMMod)) { |
+ versionsCompatible = false; |
+ rf.findSubclassesReimplementingMethod(oldClassInfo, oldMethodIdx); |
+ } |
+ } |
+ |
+ /** |
+ * Returns true if any subclass(es), direct or indirect, declare a method with name methodName. |
+ * For each such occurence, referencing classes are looked up and added to the list of affected classes. |
+ */ |
+ private boolean subclassesDeclareSameNameMethod(ClassInfo oldClassInfo, String methodName) { |
+ boolean res = false; |
+ ClassInfo[] directSubclasses = oldClassInfo.getDirectSubclasses(); |
+ for (int i = 0; i < directSubclasses.length; i++) { |
+ ClassInfo subclass = directSubclasses[i]; |
+ int methNo = subclass.declaresSameNameMethod(methodName); |
+ if (methNo >= 0) { |
+ rf.addToAffectedClassNames(subclass.name); |
+ rf.findReferencingClassesForMethod(subclass, methNo); |
+ res = true; |
+ } |
+ if (subclassesDeclareSameNameMethod(subclass, methodName)) { |
+ res = true; |
+ } |
+ } |
+ return res; |
+ } |
+} |