| 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;
|
| + }
|
| +}
|
|
|