| package jdiff; | |
| import java.util.*; | |
| /** | |
| * This class contains method to compare two API objects. | |
| * The differences are stored in an APIDiff object. | |
| * | |
| * See the file LICENSE.txt for copyright details. | |
| * @author Matthew Doar, mdoar@pobox.com | |
| */ | |
| public class APIComparator { | |
| /** | |
| * Top-level object representing the differences between two APIs. | |
| * It is this object which is used to generate the report later on. | |
| */ | |
| public APIDiff apiDiff; | |
| /** | |
| * Package-level object representing the differences between two packages. | |
| * This object is also used to determine which file to write documentation | |
| * differences into. | |
| */ | |
| public PackageDiff pkgDiff; | |
| /** Default constructor. */ | |
| public APIComparator() { | |
| apiDiff = new APIDiff(); | |
| } | |
| /** For easy local access to the old API object. */ | |
| private static API oldAPI_; | |
| /** For easy local access to the new API object. */ | |
| private static API newAPI_; | |
| /** | |
| * Compare two APIs. | |
| */ | |
| public void compareAPIs(API oldAPI, API newAPI) { | |
| System.out.println("JDiff: comparing the old and new APIs ..."); | |
| oldAPI_ = oldAPI; | |
| newAPI_ = newAPI; | |
| double differs = 0.0; | |
| apiDiff.oldAPIName_ = oldAPI.name_; | |
| apiDiff.newAPIName_ = newAPI.name_; | |
| Collections.sort(oldAPI.packages_); | |
| Collections.sort(newAPI.packages_); | |
| // Find packages which were removed in the new API | |
| Iterator iter = oldAPI.packages_.iterator(); | |
| while (iter.hasNext()) { | |
| PackageAPI oldPkg = (PackageAPI)(iter.next()); | |
| // This search is looking for an *exact* match. This is true in | |
| // all the *API classes. | |
| int idx = Collections.binarySearch(newAPI.packages_, oldPkg); | |
| if (idx < 0) { | |
| // If there an instance of a package with the same name | |
| // in both the old and new API, then treat it as changed, | |
| // rather than removed and added. There will never be more than | |
| // one instance of a package with the same name in an API. | |
| int existsNew = newAPI.packages_.indexOf(oldPkg); | |
| if (existsNew != -1) { | |
| // Package by the same name exists in both APIs | |
| // but there has been some or other change. | |
| differs += 2.0 * comparePackages(oldPkg, (PackageAPI)(newAPI.packages_.get(existsNew))); | |
| } else { | |
| if (trace) | |
| System.out.println("Package " + oldPkg.name_ + " was removed"); | |
| apiDiff.packagesRemoved.add(oldPkg); | |
| differs += 1.0; | |
| } | |
| } else { | |
| // The package exists unchanged in name or doc, but may | |
| // differ in classes and their members, so it still needs to | |
| // be compared. | |
| differs += 2.0 * comparePackages(oldPkg, (PackageAPI)(newAPI.packages_.get(idx))); | |
| } | |
| } // while (iter.hasNext()) | |
| // Find packages which were added or changed in the new API | |
| iter = newAPI.packages_.iterator(); | |
| while (iter.hasNext()) { | |
| PackageAPI newPkg = (PackageAPI)(iter.next()); | |
| int idx = Collections.binarySearch(oldAPI.packages_, newPkg); | |
| if (idx < 0) { | |
| // See comments above | |
| int existsOld = oldAPI.packages_.indexOf(newPkg); | |
| if (existsOld != -1) { | |
| // Don't mark a package as added or compare it | |
| // if it was already marked as changed | |
| } else { | |
| if (trace) | |
| System.out.println("Package " + newPkg.name_ + " was added"); | |
| apiDiff.packagesAdded.add(newPkg); | |
| differs += 1.0; | |
| } | |
| } else { | |
| // It will already have been compared above. | |
| } | |
| } // while (iter.hasNext()) | |
| // Now that the numbers of members removed and added are known | |
| // we can deduce more information about changes. | |
| MergeChanges.mergeRemoveAdd(apiDiff); | |
| // The percent change statistic reported for all elements in each API is | |
| // defined recursively as follows: | |
| // | |
| // %age change = 100 * (added + removed + 2*changed) | |
| // ----------------------------------- | |
| // sum of public elements in BOTH APIs | |
| // | |
| // The definition ensures that if all classes are removed and all new classes | |
| // added, the change will be 100%. | |
| // Evaluation of the visibility of elements has already been done when the | |
| // XML was written out. | |
| // Note that this doesn't count changes in the modifiers of classes and | |
| // packages. Other changes in members are counted. | |
| Long denom = new Long(oldAPI.packages_.size() + newAPI.packages_.size()); | |
| // This should never be zero because an API always has packages? | |
| if (denom.intValue() == 0) { | |
| System.out.println("Error: no packages found in the APIs."); | |
| return; | |
| } | |
| if (trace) | |
| System.out.println("Top level changes: " + differs + "/" + denom.intValue()); | |
| differs = (100.0 * differs)/denom.doubleValue(); | |
| // Some differences such as documentation changes are not tracked in | |
| // the difference statistic, so a value of 0.0 does not mean that there | |
| // were no differences between the APIs. | |
| apiDiff.pdiff = differs; | |
| Double percentage = new Double(differs); | |
| int approxPercentage = percentage.intValue(); | |
| if (approxPercentage == 0) | |
| System.out.println(" Approximately " + percentage + "% difference between the APIs"); | |
| else | |
| System.out.println(" Approximately " + approxPercentage + "% difference between the APIs"); | |
| Diff.closeDiffFile(); | |
| } | |
| /** | |
| * Compare two packages. | |
| */ | |
| public double comparePackages(PackageAPI oldPkg, PackageAPI newPkg) { | |
| if (trace) | |
| System.out.println("Comparing old package " + oldPkg.name_ + | |
| " and new package " + newPkg.name_); | |
| pkgDiff = new PackageDiff(oldPkg.name_); | |
| double differs = 0.0; | |
| Collections.sort(oldPkg.classes_); | |
| Collections.sort(newPkg.classes_); | |
| // Find classes which were removed in the new package | |
| Iterator iter = oldPkg.classes_.iterator(); | |
| while (iter.hasNext()) { | |
| ClassAPI oldClass = (ClassAPI)(iter.next()); | |
| // This search is looking for an *exact* match. This is true in | |
| // all the *API classes. | |
| int idx = Collections.binarySearch(newPkg.classes_, oldClass); | |
| if (idx < 0) { | |
| // If there an instance of a class with the same name | |
| // in both the old and new package, then treat it as changed, | |
| // rather than removed and added. There will never be more than | |
| // one instance of a class with the same name in a package. | |
| int existsNew = newPkg.classes_.indexOf(oldClass); | |
| if (existsNew != -1) { | |
| // Class by the same name exists in both packages | |
| // but there has been some or other change. | |
| differs += 2.0 * compareClasses(oldClass, (ClassAPI)(newPkg.classes_.get(existsNew)), pkgDiff); | |
| } else { | |
| if (trace) | |
| System.out.println(" Class " + oldClass.name_ + " was removed"); | |
| pkgDiff.classesRemoved.add(oldClass); | |
| differs += 1.0; | |
| } | |
| } else { | |
| // The class exists unchanged in name or modifiers, but may | |
| // differ in members, so it still needs to be compared. | |
| differs += 2.0 * compareClasses(oldClass, (ClassAPI)(newPkg.classes_.get(idx)), pkgDiff); | |
| } | |
| } // while (iter.hasNext()) | |
| // Find classes which were added or changed in the new package | |
| iter = newPkg.classes_.iterator(); | |
| while (iter.hasNext()) { | |
| ClassAPI newClass = (ClassAPI)(iter.next()); | |
| int idx = Collections.binarySearch(oldPkg.classes_, newClass); | |
| if (idx < 0) { | |
| // See comments above | |
| int existsOld = oldPkg.classes_.indexOf(newClass); | |
| if (existsOld != -1) { | |
| // Don't mark a class as added or compare it | |
| // if it was already marked as changed | |
| } else { | |
| if (trace) | |
| System.out.println(" Class " + newClass.name_ + " was added"); | |
| pkgDiff.classesAdded.add(newClass); | |
| differs += 1.0; | |
| } | |
| } else { | |
| // It will already have been compared above. | |
| } | |
| } // while (iter.hasNext()) | |
| // Check if the only change was in documentation. Bug 472521. | |
| boolean differsFlag = false; | |
| if (docChanged(oldPkg.doc_, newPkg.doc_)) { | |
| String link = "<a href=\"pkg_" + oldPkg.name_ + HTMLReportGenerator.reportFileExt + "\" class=\"hiddenlink\">"; | |
| String id = oldPkg.name_ + "!package"; | |
| String title = link + "Package <b>" + oldPkg.name_ + "</b></a>"; | |
| pkgDiff.documentationChange_ = Diff.saveDocDiffs(pkgDiff.name_, null, oldPkg.doc_, newPkg.doc_, id, title); | |
| differsFlag = true; | |
| } | |
| // Only add to the parent Diff object if some difference has been found | |
| if (differs != 0.0 || differsFlag) | |
| apiDiff.packagesChanged.add(pkgDiff); | |
| Long denom = new Long(oldPkg.classes_.size() + newPkg.classes_.size()); | |
| // This should never be zero because a package always has classes? | |
| if (denom.intValue() == 0) { | |
| System.out.println("Warning: no classes found in the package " + oldPkg.name_); | |
| return 0.0; | |
| } | |
| if (trace) | |
| System.out.println("Package " + pkgDiff.name_ + " had a difference of " + differs + "/" + denom.intValue()); | |
| pkgDiff.pdiff = 100.0 * differs/denom.doubleValue(); | |
| return differs/denom.doubleValue(); | |
| } // comparePackages() | |
| /** | |
| * Compare two classes. | |
| * | |
| * Need to compare constructors, methods and fields. | |
| */ | |
| public double compareClasses(ClassAPI oldClass, ClassAPI newClass, PackageDiff pkgDiff) { | |
| if (trace) | |
| System.out.println(" Comparing old class " + oldClass.name_ + | |
| " and new class " + newClass.name_); | |
| boolean differsFlag = false; | |
| double differs = 0.0; | |
| ClassDiff classDiff = new ClassDiff(oldClass.name_); | |
| classDiff.isInterface_ = newClass.isInterface_; // Used in the report | |
| // Track changes in modifiers - class or interface | |
| if (oldClass.isInterface_ != newClass.isInterface_) { | |
| classDiff.modifiersChange_ = "Changed from "; | |
| if (oldClass.isInterface_) | |
| classDiff.modifiersChange_ += "an interface to a class."; | |
| else | |
| classDiff.modifiersChange_ += "a class to an interface."; | |
| differsFlag = true; | |
| } | |
| // Track changes in inheritance | |
| String inheritanceChange = ClassDiff.diff(oldClass, newClass); | |
| if (inheritanceChange != null) { | |
| classDiff.inheritanceChange_ = inheritanceChange; | |
| differsFlag = true; | |
| } | |
| // Abstract or not | |
| if (oldClass.isAbstract_ != newClass.isAbstract_) { | |
| String changeText = ""; | |
| if (oldClass.isAbstract_) | |
| changeText += "Changed from abstract to non-abstract."; | |
| else | |
| changeText += "Changed from non-abstract to abstract."; | |
| classDiff.addModifiersChange(changeText); | |
| differsFlag = true; | |
| } | |
| // Track changes in documentation | |
| if (docChanged(oldClass.doc_, newClass.doc_)) { | |
| String fqName = pkgDiff.name_ + "." + classDiff.name_; | |
| String link = "<a href=\"" + fqName + HTMLReportGenerator.reportFileExt + "\" class=\"hiddenlink\">"; | |
| String id = pkgDiff.name_ + "." + classDiff.name_ + "!class"; | |
| String title = link + "Class <b>" + classDiff.name_ + "</b></a>"; | |
| classDiff.documentationChange_ = Diff.saveDocDiffs(pkgDiff.name_, | |
| classDiff.name_, oldClass.doc_, newClass.doc_, id, title); | |
| differsFlag = true; | |
| } | |
| // All other modifiers | |
| String modifiersChange = oldClass.modifiers_.diff(newClass.modifiers_); | |
| if (modifiersChange != null) { | |
| differsFlag = true; | |
| if (modifiersChange.indexOf("Change from deprecated to undeprecated") != -1) { | |
| System.out.println("JDiff: warning: change from deprecated to undeprecated for class " + pkgDiff.name_ + "." + newClass.name_); | |
| } | |
| } | |
| classDiff.addModifiersChange(modifiersChange); | |
| // Track changes in members | |
| boolean differsCtors = | |
| compareAllCtors(oldClass, newClass, classDiff); | |
| boolean differsMethods = | |
| compareAllMethods(oldClass, newClass, classDiff); | |
| boolean differsFields = | |
| compareAllFields(oldClass, newClass, classDiff); | |
| if (differsCtors || differsMethods || differsFields) | |
| differsFlag = true; | |
| if (trace) { | |
| System.out.println(" Ctors differ? " + differsCtors + | |
| ", Methods differ? " + differsMethods + | |
| ", Fields differ? " + differsFields); | |
| } | |
| // Only add to the parent if some difference has been found | |
| if (differsFlag) | |
| pkgDiff.classesChanged.add(classDiff); | |
| // Get the numbers of affected elements from the classDiff object | |
| differs = | |
| classDiff.ctorsRemoved.size() + classDiff.ctorsAdded.size() + | |
| classDiff.ctorsChanged.size() + | |
| classDiff.methodsRemoved.size() + classDiff.methodsAdded.size() + | |
| classDiff.methodsChanged.size() + | |
| classDiff.fieldsRemoved.size() + classDiff.fieldsAdded.size() + | |
| classDiff.fieldsChanged.size(); | |
| Long denom = new Long( | |
| oldClass.ctors_.size() + | |
| numLocalMethods(oldClass.methods_) + | |
| numLocalFields(oldClass.fields_) + | |
| newClass.ctors_.size() + | |
| numLocalMethods(newClass.methods_) + | |
| numLocalFields(newClass.fields_)); | |
| if (denom.intValue() == 0) { | |
| // This is probably a placeholder interface, but documentation | |
| // or modifiers etc may have changed | |
| if (differsFlag) { | |
| classDiff.pdiff = 0.0; // 100.0 is too much | |
| return 1.0; | |
| } else { | |
| return 0.0; | |
| } | |
| } | |
| // Handle the case where the only change is in documentation or | |
| // the modifiers | |
| if (differsFlag && differs == 0.0) { | |
| differs = 1.0; | |
| } | |
| if (trace) | |
| System.out.println(" Class " + classDiff.name_ + " had a difference of " + differs + "/" + denom.intValue()); | |
| classDiff.pdiff = 100.0 * differs/denom.doubleValue(); | |
| return differs/denom.doubleValue(); | |
| } // compareClasses() | |
| /** | |
| * Compare all the constructors in two classes. | |
| * | |
| * The compareTo method in the ConstructorAPI class acts only upon the type. | |
| */ | |
| public boolean compareAllCtors(ClassAPI oldClass, ClassAPI newClass, | |
| ClassDiff classDiff) { | |
| if (trace) | |
| System.out.println(" Comparing constructors: #old " + | |
| oldClass.ctors_.size() + ", #new " + newClass.ctors_.size()); | |
| boolean differs = false; | |
| boolean singleCtor = false; // Set if there is only one ctor | |
| Collections.sort(oldClass.ctors_); | |
| Collections.sort(newClass.ctors_); | |
| // Find ctors which were removed in the new class | |
| Iterator iter = oldClass.ctors_.iterator(); | |
| while (iter.hasNext()) { | |
| ConstructorAPI oldCtor = (ConstructorAPI)(iter.next()); | |
| int idx = Collections.binarySearch(newClass.ctors_, oldCtor); | |
| if (idx < 0) { | |
| int oldSize = oldClass.ctors_.size(); | |
| int newSize = newClass.ctors_.size(); | |
| if (oldSize == 1 && oldSize == newSize) { | |
| // If there is one constructor in the oldClass and one | |
| // constructor in the new class, then mark it as changed | |
| MemberDiff memberDiff = new MemberDiff(oldClass.name_); | |
| memberDiff.oldType_ = oldCtor.getSignature(); | |
| memberDiff.oldExceptions_ = oldCtor.exceptions_; | |
| ConstructorAPI newCtor = (ConstructorAPI)(newClass.ctors_.get(0)); | |
| memberDiff.newType_ = newCtor.getSignature(); | |
| memberDiff.newExceptions_ = newCtor.exceptions_; | |
| // Track changes in documentation | |
| if (docChanged(oldCtor.doc_, newCtor.doc_)) { | |
| String type = memberDiff.newType_; | |
| if (type.compareTo("void") == 0) | |
| type = ""; | |
| String fqName = pkgDiff.name_ + "." + classDiff.name_; | |
| String link1 = "<a href=\"" + fqName + HTMLReportGenerator.reportFileExt + "\" class=\"hiddenlink\">"; | |
| String link2 = "<a href=\"" + fqName + HTMLReportGenerator.reportFileExt + "#" + fqName + ".ctor_changed(" + type + ")\" class=\"hiddenlink\">"; | |
| String id = pkgDiff.name_ + "." + classDiff.name_ + ".ctor(" + HTMLReportGenerator.simpleName(type) + ")"; | |
| String title = link1 + "Class <b>" + classDiff.name_ + | |
| "</b></a>, " + link2 + "constructor <b>" + classDiff.name_ + "(" + HTMLReportGenerator.simpleName(type) + ")</b></a>"; | |
| memberDiff.documentationChange_ = Diff.saveDocDiffs( | |
| pkgDiff.name_, classDiff.name_, oldCtor.doc_, newCtor.doc_, id, title); | |
| } | |
| String modifiersChange = oldCtor.modifiers_.diff(newCtor.modifiers_); | |
| if (modifiersChange != null && modifiersChange.indexOf("Change from deprecated to undeprecated") != -1) { | |
| System.out.println("JDiff: warning: change from deprecated to undeprecated for a constructor in class" + newClass.name_); | |
| } | |
| memberDiff.addModifiersChange(modifiersChange); | |
| if (trace) | |
| System.out.println(" The single constructor was changed"); | |
| classDiff.ctorsChanged.add(memberDiff); | |
| singleCtor = true; | |
| } else { | |
| if (trace) | |
| System.out.println(" Constructor " + oldClass.name_ + " was removed"); | |
| classDiff.ctorsRemoved.add(oldCtor); | |
| } | |
| differs = true; | |
| } | |
| } // while (iter.hasNext()) | |
| // Find ctors which were added in the new class | |
| iter = newClass.ctors_.iterator(); | |
| while (iter.hasNext()) { | |
| ConstructorAPI newCtor = (ConstructorAPI)(iter.next()); | |
| int idx = Collections.binarySearch(oldClass.ctors_, newCtor); | |
| if (idx < 0) { | |
| if (!singleCtor) { | |
| if (trace) | |
| System.out.println(" Constructor " + oldClass.name_ + " was added"); | |
| classDiff.ctorsAdded.add(newCtor); | |
| differs = true; | |
| } | |
| } | |
| } // while (iter.hasNext()) | |
| return differs; | |
| } // compareAllCtors() | |
| /** | |
| * Compare all the methods in two classes. | |
| * | |
| * We have to deal with the cases where: | |
| * - there is only one method with a given name, but its signature changes | |
| * - there is more than one method with the same name, and some of them | |
| * may have signature changes | |
| * The simplest way to deal with this is to make the MethodAPI comparator | |
| * check the params and return type, as well as the name. This means that | |
| * changing a parameter's type would cause the method to be seen as | |
| * removed and added. To avoid this for the simple case, check for before | |
| * recording a method as removed or added. | |
| */ | |
| public boolean compareAllMethods(ClassAPI oldClass, ClassAPI newClass, ClassDiff classDiff) { | |
| if (trace) | |
| System.out.println(" Comparing methods: #old " + | |
| oldClass.methods_.size() + ", #new " + | |
| newClass.methods_.size()); | |
| boolean differs = false; | |
| Collections.sort(oldClass.methods_); | |
| Collections.sort(newClass.methods_); | |
| // Find methods which were removed in the new class | |
| Iterator iter = oldClass.methods_.iterator(); | |
| while (iter.hasNext()) { | |
| MethodAPI oldMethod = (MethodAPI)(iter.next()); | |
| int idx = -1; | |
| MethodAPI[] methodArr = new MethodAPI[newClass.methods_.size()]; | |
| methodArr = (MethodAPI[])newClass.methods_.toArray(methodArr); | |
| for (int methodIdx = 0; methodIdx < methodArr.length; methodIdx++) { | |
| MethodAPI newMethod = methodArr[methodIdx]; | |
| if (oldMethod.compareTo(newMethod) == 0) { | |
| idx = methodIdx; | |
| break; | |
| } | |
| } | |
| // NOTE: there was a problem with the binarySearch for | |
| // java.lang.Byte.toString(byte b) returning -16 when the compareTo method | |
| // returned 0 on entry 13. Changed to use arrays instead, so maybe it was | |
| // an issue with methods having another List of params used indirectly by | |
| // compareTo(), unlike constructors and fields? | |
| // int idx = Collections.binarySearch(newClass.methods_, oldMethod); | |
| if (idx < 0) { | |
| // If there is only one instance of a method with this name | |
| // in both the old and new class, then treat it as changed, | |
| // rather than removed and added. | |
| // Find how many instances of this method name there are in | |
| // the old and new class. The equals comparator is just on | |
| // the method name. | |
| int startOld = oldClass.methods_.indexOf(oldMethod); | |
| int endOld = oldClass.methods_.lastIndexOf(oldMethod); | |
| int startNew = newClass.methods_.indexOf(oldMethod); | |
| int endNew = newClass.methods_.lastIndexOf(oldMethod); | |
| if (startOld != -1 && startOld == endOld && | |
| startNew != -1 && startNew == endNew) { | |
| MethodAPI newMethod = (MethodAPI)(newClass.methods_.get(startNew)); | |
| // Only one method with that name exists in both packages, | |
| // so it is valid to compare the two methods. We know it | |
| // has changed, because the binarySearch did not find it. | |
| if (oldMethod.inheritedFrom_ == null || | |
| newMethod.inheritedFrom_ == null) { | |
| // We also know that at least one of the methods is | |
| // locally defined. | |
| compareMethods(oldMethod, newMethod, classDiff); | |
| differs = true; | |
| } | |
| } else if (oldMethod.inheritedFrom_ == null) { | |
| // Only concerned with locally defined methods | |
| if (trace) | |
| System.out.println(" Method " + oldMethod.name_ + | |
| "(" + oldMethod.getSignature() + | |
| ") was removed"); | |
| classDiff.methodsRemoved.add(oldMethod); | |
| differs = true; | |
| } | |
| } | |
| } // while (iter.hasNext()) | |
| // Find methods which were added in the new class | |
| iter = newClass.methods_.iterator(); | |
| while (iter.hasNext()) { | |
| MethodAPI newMethod = (MethodAPI)(iter.next()); | |
| // Only concerned with locally defined methods | |
| if (newMethod.inheritedFrom_ != null) | |
| continue; | |
| int idx = -1; | |
| MethodAPI[] methodArr = new MethodAPI[oldClass.methods_.size()]; | |
| methodArr = (MethodAPI[])oldClass.methods_.toArray(methodArr); | |
| for (int methodIdx = 0; methodIdx < methodArr.length; methodIdx++) { | |
| MethodAPI oldMethod = methodArr[methodIdx]; | |
| if (newMethod.compareTo(oldMethod) == 0) { | |
| idx = methodIdx; | |
| break; | |
| } | |
| } | |
| // See note above about searching an array instead of binarySearch | |
| // int idx = Collections.binarySearch(oldClass.methods_, newMethod); | |
| if (idx < 0) { | |
| // See comments above | |
| int startOld = oldClass.methods_.indexOf(newMethod); | |
| int endOld = oldClass.methods_.lastIndexOf(newMethod); | |
| int startNew = newClass.methods_.indexOf(newMethod); | |
| int endNew = newClass.methods_.lastIndexOf(newMethod); | |
| if (startOld != -1 && startOld == endOld && | |
| startNew != -1 && startNew == endNew) { | |
| // Don't mark a method as added if it was marked as changed | |
| // The comparison will have been done just above here. | |
| } else { | |
| if (trace) | |
| System.out.println(" Method " + newMethod.name_ + | |
| "(" + newMethod.getSignature() + ") was added"); | |
| classDiff.methodsAdded.add(newMethod); | |
| differs = true; | |
| } | |
| } | |
| } // while (iter.hasNext()) | |
| return differs; | |
| } // compareAllMethods() | |
| /** | |
| * Compare two methods which have the same name. | |
| */ | |
| public boolean compareMethods(MethodAPI oldMethod, MethodAPI newMethod, ClassDiff classDiff) { | |
| MemberDiff methodDiff = new MemberDiff(oldMethod.name_); | |
| boolean differs = false; | |
| // Check changes in return type | |
| methodDiff.oldType_ = oldMethod.returnType_; | |
| methodDiff.newType_ = newMethod.returnType_; | |
| if (oldMethod.returnType_.compareTo(newMethod.returnType_) != 0) { | |
| differs = true; | |
| } | |
| // Check changes in signature | |
| String oldSig = oldMethod.getSignature(); | |
| String newSig = newMethod.getSignature(); | |
| methodDiff.oldSignature_ = oldSig; | |
| methodDiff.newSignature_ = newSig; | |
| if (oldSig.compareTo(newSig) != 0) { | |
| differs = true; | |
| } | |
| // Changes in inheritance | |
| int inh = changedInheritance(oldMethod.inheritedFrom_, newMethod.inheritedFrom_); | |
| if (inh != 0) | |
| differs = true; | |
| if (inh == 1) { | |
| methodDiff.addModifiersChange("Method was locally defined, but is now inherited from " + linkToClass(newMethod, true) + "."); | |
| methodDiff.inheritedFrom_ = newMethod.inheritedFrom_; | |
| } else if (inh == 2) { | |
| methodDiff.addModifiersChange("Method was inherited from " + linkToClass(oldMethod, false) + ", but is now defined locally."); | |
| } else if (inh == 3) { | |
| methodDiff.addModifiersChange("Method was inherited from " + | |
| linkToClass(oldMethod, false) + ", and is now inherited from " + linkToClass(newMethod, true) + "."); | |
| methodDiff.inheritedFrom_ = newMethod.inheritedFrom_; | |
| } | |
| // Abstract or not | |
| if (oldMethod.isAbstract_ != newMethod.isAbstract_) { | |
| String changeText = ""; | |
| if (oldMethod.isAbstract_) | |
| changeText += "Changed from abstract to non-abstract."; | |
| else | |
| changeText += "Changed from non-abstract to abstract."; | |
| methodDiff.addModifiersChange(changeText); | |
| differs = true; | |
| } | |
| // Native or not | |
| if (Diff.showAllChanges && | |
| oldMethod.isNative_ != newMethod.isNative_) { | |
| String changeText = ""; | |
| if (oldMethod.isNative_) | |
| changeText += "Changed from native to non-native."; | |
| else | |
| changeText += "Changed from non-native to native."; | |
| methodDiff.addModifiersChange(changeText); | |
| differs = true; | |
| } | |
| // Synchronized or not | |
| if (Diff.showAllChanges && | |
| oldMethod.isSynchronized_ != newMethod.isSynchronized_) { | |
| String changeText = ""; | |
| if (oldMethod.isSynchronized_) | |
| changeText += "Changed from synchronized to non-synchronized."; | |
| else | |
| changeText += "Changed from non-synchronized to synchronized."; | |
| methodDiff.addModifiersChange(changeText); | |
| differs = true; | |
| } | |
| // Check changes in exceptions thrown | |
| methodDiff.oldExceptions_ = oldMethod.exceptions_; | |
| methodDiff.newExceptions_ = newMethod.exceptions_; | |
| if (oldMethod.exceptions_.compareTo(newMethod.exceptions_) != 0) { | |
| differs = true; | |
| } | |
| // Track changes in documentation | |
| if (docChanged(oldMethod.doc_, newMethod.doc_)) { | |
| String sig = methodDiff.newSignature_; | |
| if (sig.compareTo("void") == 0) | |
| sig = ""; | |
| String fqName = pkgDiff.name_ + "." + classDiff.name_; | |
| String link1 = "<a href=\"" + fqName + HTMLReportGenerator.reportFileExt + "\" class=\"hiddenlink\">"; | |
| String link2 = "<a href=\"" + fqName + HTMLReportGenerator.reportFileExt + "#" + fqName + "." + newMethod.name_ + "_changed(" + sig + ")\" class=\"hiddenlink\">"; | |
| String id = pkgDiff.name_ + "." + classDiff.name_ + ".dmethod." + newMethod.name_ + "(" + HTMLReportGenerator.simpleName(sig) + ")"; | |
| String title = link1 + "Class <b>" + classDiff.name_ + "</b></a>, " + | |
| link2 + HTMLReportGenerator.simpleName(methodDiff.newType_) + " <b>" + newMethod.name_ + "(" + HTMLReportGenerator.simpleName(sig) + ")</b></a>"; | |
| methodDiff.documentationChange_ = Diff.saveDocDiffs(pkgDiff.name_, classDiff.name_, oldMethod.doc_, newMethod.doc_, id, title); | |
| differs = true; | |
| } | |
| // All other modifiers | |
| String modifiersChange = oldMethod.modifiers_.diff(newMethod.modifiers_); | |
| if (modifiersChange != null) { | |
| differs = true; | |
| if (modifiersChange.indexOf("Change from deprecated to undeprecated") != -1) { | |
| System.out.println("JDiff: warning: change from deprecated to undeprecated for method " + classDiff.name_ + "." + newMethod.name_); | |
| } | |
| } | |
| methodDiff.addModifiersChange(modifiersChange); | |
| // Only add to the parent if some difference has been found | |
| if (differs) { | |
| if (trace) { | |
| System.out.println(" Method " + newMethod.name_ + | |
| " was changed: old: " + | |
| oldMethod.returnType_ + "(" + oldSig + "), new: " + | |
| newMethod.returnType_ + "(" + newSig + ")"); | |
| if (methodDiff.modifiersChange_ != null) | |
| System.out.println(" Modifier change: " + methodDiff.modifiersChange_); | |
| } | |
| classDiff.methodsChanged.add(methodDiff); | |
| } | |
| return differs; | |
| } // compareMethods() | |
| /** | |
| * Compare all the fields in two classes. | |
| */ | |
| public boolean compareAllFields(ClassAPI oldClass, ClassAPI newClass, | |
| ClassDiff classDiff) { | |
| if (trace) | |
| System.out.println(" Comparing fields: #old " + | |
| oldClass.fields_.size() + ", #new " | |
| + newClass.fields_.size()); | |
| boolean differs = false; | |
| Collections.sort(oldClass.fields_); | |
| Collections.sort(newClass.fields_); | |
| // Find fields which were removed in the new class | |
| Iterator iter = oldClass.fields_.iterator(); | |
| while (iter.hasNext()) { | |
| FieldAPI oldField = (FieldAPI)(iter.next()); | |
| int idx = Collections.binarySearch(newClass.fields_, oldField); | |
| if (idx < 0) { | |
| // If there an instance of a field with the same name | |
| // in both the old and new class, then treat it as changed, | |
| // rather than removed and added. There will never be more than | |
| // one instance of a field with the same name in a class. | |
| int existsNew = newClass.fields_.indexOf(oldField); | |
| if (existsNew != -1) { | |
| FieldAPI newField = (FieldAPI)(newClass.fields_.get(existsNew)); | |
| if (oldField.inheritedFrom_ == null || | |
| newField.inheritedFrom_ == null) { | |
| // We also know that one of the fields is locally defined. | |
| MemberDiff memberDiff = new MemberDiff(oldField.name_); | |
| memberDiff.oldType_ = oldField.type_; | |
| memberDiff.newType_ = newField.type_; | |
| // Changes in inheritance | |
| int inh = changedInheritance(oldField.inheritedFrom_, newField.inheritedFrom_); | |
| if (inh != 0) | |
| differs = true; | |
| if (inh == 1) { | |
| memberDiff.addModifiersChange("Field was locally defined, but is now inherited from " + linkToClass(newField, true) + "."); | |
| memberDiff.inheritedFrom_ = newField.inheritedFrom_; | |
| } else if (inh == 2) { | |
| memberDiff.addModifiersChange("Field was inherited from " + linkToClass(oldField, false) + ", but is now defined locally."); | |
| } else if (inh == 3) { | |
| memberDiff.addModifiersChange("Field was inherited from " + linkToClass(oldField, false) + ", and is now inherited from " + linkToClass(newField, true) + "."); | |
| memberDiff.inheritedFrom_ = newField.inheritedFrom_; | |
| } | |
| // Transient or not | |
| if (oldField.isTransient_ != newField.isTransient_) { | |
| String changeText = ""; | |
| if (oldField.isTransient_) | |
| changeText += "Changed from transient to non-transient."; | |
| else | |
| changeText += "Changed from non-transient to transient."; | |
| memberDiff.addModifiersChange(changeText); | |
| differs = true; | |
| } | |
| // Volatile or not | |
| if (oldField.isVolatile_ != newField.isVolatile_) { | |
| String changeText = ""; | |
| if (oldField.isVolatile_) | |
| changeText += "Changed from volatile to non-volatile."; | |
| else | |
| changeText += "Changed from non-volatile to volatile."; | |
| memberDiff.addModifiersChange(changeText); | |
| differs = true; | |
| } | |
| // Change in value of the field | |
| if (oldField.value_ != null && | |
| newField.value_ != null && | |
| oldField.value_.compareTo(newField.value_) != 0) { | |
| String changeText = "Changed in value from " + oldField.value_ | |
| + " to " + newField.value_ +"."; | |
| memberDiff.addModifiersChange(changeText); | |
| differs = true; | |
| } | |
| // Track changes in documentation | |
| if (docChanged(oldField.doc_, newField.doc_)) { | |
| String fqName = pkgDiff.name_ + "." + classDiff.name_; | |
| String link1 = "<a href=\"" + fqName + HTMLReportGenerator.reportFileExt + "\" class=\"hiddenlink\">"; | |
| String link2 = "<a href=\"" + fqName + HTMLReportGenerator.reportFileExt + "#" + fqName + "." + newField.name_ + "\" class=\"hiddenlink\">"; | |
| String id = pkgDiff.name_ + "." + classDiff.name_ + ".field." + newField.name_; | |
| String title = link1 + "Class <b>" + classDiff.name_ + "</b></a>, " + | |
| link2 + HTMLReportGenerator.simpleName(memberDiff.newType_) + " <b>" + newField.name_ + "</b></a>"; | |
| memberDiff.documentationChange_ = Diff.saveDocDiffs(pkgDiff.name_, classDiff.name_, oldField.doc_, newField.doc_, id, title); | |
| differs = true; | |
| } | |
| // Other differences | |
| String modifiersChange = oldField.modifiers_.diff(newField.modifiers_); | |
| memberDiff.addModifiersChange(modifiersChange); | |
| if (modifiersChange != null && modifiersChange.indexOf("Change from deprecated to undeprecated") != -1) { | |
| System.out.println("JDiff: warning: change from deprecated to undeprecated for class " + newClass.name_ + ", field " + newField.name_); | |
| } | |
| if (trace) | |
| System.out.println(" Field " + newField.name_ + " was changed"); | |
| classDiff.fieldsChanged.add(memberDiff); | |
| differs = true; | |
| } | |
| } else if (oldField.inheritedFrom_ == null) { | |
| if (trace) | |
| System.out.println(" Field " + oldField.name_ + " was removed"); | |
| classDiff.fieldsRemoved.add(oldField); | |
| differs = true; | |
| } | |
| } | |
| } // while (iter.hasNext()) | |
| // Find fields which were added in the new class | |
| iter = newClass.fields_.iterator(); | |
| while (iter.hasNext()) { | |
| FieldAPI newField = (FieldAPI)(iter.next()); | |
| // Only concerned with locally defined fields | |
| if (newField.inheritedFrom_ != null) | |
| continue; | |
| int idx = Collections.binarySearch(oldClass.fields_, newField); | |
| if (idx < 0) { | |
| // See comments above | |
| int existsOld = oldClass.fields_.indexOf(newField); | |
| if (existsOld != -1) { | |
| // Don't mark a field as added if it was marked as changed | |
| } else { | |
| if (trace) | |
| System.out.println(" Field " + newField.name_ + " was added"); | |
| classDiff.fieldsAdded.add(newField); | |
| differs = true; | |
| } | |
| } | |
| } // while (iter.hasNext()) | |
| return differs; | |
| } // compareFields() | |
| /** | |
| * Decide if two blocks of documentation changed. | |
| * | |
| * @return true if both are non-null and differ, | |
| * or if one is null and the other is not. | |
| */ | |
| public static boolean docChanged(String oldDoc, String newDoc) { | |
| if (!HTMLReportGenerator.reportDocChanges) | |
| return false; // Don't even count doc changes as changes | |
| if (oldDoc == null && newDoc != null) | |
| return true; | |
| if (oldDoc != null && newDoc == null) | |
| return true; | |
| if (oldDoc != null && newDoc != null && oldDoc.compareTo(newDoc) != 0) | |
| return true; | |
| return false; | |
| } | |
| /** | |
| * Decide if two elements changed where they were defined. | |
| * | |
| * @return 0 if both are null, or both are non-null and are the same. | |
| * 1 if the oldInherit was null and newInherit is non-null. | |
| * 2 if the oldInherit was non-null and newInherit is null. | |
| * 3 if the oldInherit was non-null and newInherit is non-null | |
| * and they differ. | |
| */ | |
| public static int changedInheritance(String oldInherit, String newInherit) { | |
| if (oldInherit == null && newInherit == null) | |
| return 0; | |
| if (oldInherit == null && newInherit != null) | |
| return 1; | |
| if (oldInherit != null && newInherit == null) | |
| return 2; | |
| if (oldInherit.compareTo(newInherit) == 0) | |
| return 0; | |
| else | |
| return 3; | |
| } | |
| /** | |
| * Generate a link to the Javadoc page for the given method. | |
| */ | |
| public static String linkToClass(MethodAPI m, boolean useNew) { | |
| String sig = m.getSignature(); | |
| if (sig.compareTo("void") == 0) | |
| sig = ""; | |
| return linkToClass(m.inheritedFrom_, m.name_, sig, useNew); | |
| } | |
| /** | |
| * Generate a link to the Javadoc page for the given field. | |
| */ | |
| public static String linkToClass(FieldAPI m, boolean useNew) { | |
| return linkToClass(m.inheritedFrom_, m.name_, null, useNew); | |
| } | |
| /** | |
| * Given the name of the class, generate a link to a relevant page. | |
| * This was originally for inheritance changes, so the JDiff page could | |
| * be a class changes page, or a section in a removed or added classes | |
| * table. Since there was no easy way to tell which type the link | |
| * should be, it is now just a link to the relevant Javadoc page. | |
| */ | |
| public static String linkToClass(String className, String memberName, | |
| String memberType, boolean useNew) { | |
| if (!useNew && HTMLReportGenerator.oldDocPrefix == null) { | |
| return "<tt>" + className + "</tt>"; // No link possible | |
| } | |
| API api = oldAPI_; | |
| String prefix = HTMLReportGenerator.oldDocPrefix; | |
| if (useNew) { | |
| api = newAPI_; | |
| prefix = HTMLReportGenerator.newDocPrefix; | |
| } | |
| ClassAPI cls = (ClassAPI)api.classes_.get(className); | |
| if (cls == null) { | |
| if (useNew) | |
| System.out.println("Warning: class " + className + " not found in the new API when creating Javadoc link"); | |
| else | |
| System.out.println("Warning: class " + className + " not found in the old API when creating Javadoc link"); | |
| return "<tt>" + className + "</tt>"; | |
| } | |
| int clsIdx = className.indexOf(cls.name_); | |
| if (clsIdx != -1) { | |
| String pkgRef = className.substring(0, clsIdx); | |
| pkgRef = pkgRef.replace('.', '/'); | |
| String res = "<a href=\"" + prefix + pkgRef + cls.name_ + ".html#" + memberName; | |
| if (memberType != null) | |
| res += "(" + memberType + ")"; | |
| res += "\" target=\"_top\">" + "<tt>" + cls.name_ + "</tt></a>"; | |
| return res; | |
| } | |
| return "<tt>" + className + "</tt>"; | |
| } | |
| /** | |
| * Return the number of methods which are locally defined. | |
| */ | |
| public int numLocalMethods(List methods) { | |
| int res = 0; | |
| Iterator iter = methods.iterator(); | |
| while (iter.hasNext()) { | |
| MethodAPI m = (MethodAPI)(iter.next()); | |
| if (m.inheritedFrom_ == null) | |
| res++; | |
| } | |
| return res; | |
| } | |
| /** | |
| * Return the number of fields which are locally defined. | |
| */ | |
| public int numLocalFields(List fields) { | |
| int res = 0; | |
| Iterator iter = fields.iterator(); | |
| while (iter.hasNext()) { | |
| FieldAPI f = (FieldAPI)(iter.next()); | |
| if (f.inheritedFrom_ == null) | |
| res++; | |
| } | |
| return res; | |
| } | |
| /** Set to enable increased logging verbosity for debugging. */ | |
| private boolean trace = false; | |
| } |