FileDocCategorySizeDatePackage
APIComparator.javaAPI DocAndroid 1.5 API44540Wed May 06 22:41:22 BST 2009jdiff

APIComparator

public class APIComparator extends Object
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

Fields Summary
public APIDiff
apiDiff
Top-level object representing the differences between two APIs. It is this object which is used to generate the report later on.
public PackageDiff
pkgDiff
Package-level object representing the differences between two packages. This object is also used to determine which file to write documentation differences into.
private static API
oldAPI_
For easy local access to the old API object.
private static API
newAPI_
For easy local access to the new API object.
private boolean
trace
Set to enable increased logging verbosity for debugging.
Constructors Summary
public APIComparator()
Default constructor.

        apiDiff = new APIDiff();
    
Methods Summary
public static intchangedInheritance(java.lang.String oldInherit, java.lang.String newInherit)
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.

        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;
    
public voidcompareAPIs(API oldAPI, API newAPI)
Compare two APIs.

        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();
    
public booleancompareAllCtors(ClassAPI oldClass, ClassAPI newClass, ClassDiff classDiff)
Compare all the constructors in two classes. The compareTo method in the ConstructorAPI class acts only upon the type.

        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.type_;
                    memberDiff.oldExceptions_ = oldCtor.exceptions_;
                    ConstructorAPI newCtor  = (ConstructorAPI)(newClass.ctors_.get(0));
                    memberDiff.newType_ = newCtor.type_;
                    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;
    
public booleancompareAllFields(ClassAPI oldClass, ClassAPI newClass, ClassDiff classDiff)
Compare all the fields in two classes.

        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;
    
public booleancompareAllMethods(ClassAPI oldClass, ClassAPI newClass, ClassDiff classDiff)
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.

        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;
    
public doublecompareClasses(ClassAPI oldClass, ClassAPI newClass, PackageDiff pkgDiff)
Compare two classes. Need to compare constructors, methods and fields.

        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();
    
public booleancompareMethods(MethodAPI oldMethod, MethodAPI newMethod, ClassDiff classDiff)
Compare two methods which have the same name.

        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;
    
public doublecomparePackages(PackageAPI oldPkg, PackageAPI newPkg)
Compare two packages.

        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();
    
public static booleandocChanged(java.lang.String oldDoc, java.lang.String newDoc)
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.

        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;
    
public static java.lang.StringlinkToClass(MethodAPI m, boolean useNew)
Generate a link to the Javadoc page for the given method.

        String sig = m.getSignature();
        if (sig.compareTo("void") == 0)
            sig = "";
        return linkToClass(m.inheritedFrom_, m.name_, sig, useNew);
    
public static java.lang.StringlinkToClass(FieldAPI m, boolean useNew)
Generate a link to the Javadoc page for the given field.

        return linkToClass(m.inheritedFrom_, m.name_, null, useNew);
    
public static java.lang.StringlinkToClass(java.lang.String className, java.lang.String memberName, java.lang.String memberType, boolean 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.

        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>";
    
public intnumLocalFields(java.util.List fields)
Return the number of fields which are locally defined.

        int res = 0;
        Iterator iter = fields.iterator();
        while (iter.hasNext()) {
            FieldAPI f = (FieldAPI)(iter.next());
            if (f.inheritedFrom_ == null) 
                res++;
        }
        return res;
    
public intnumLocalMethods(java.util.List methods)
Return the number of methods which are locally defined.

        int res = 0;
        Iterator iter = methods.iterator();
        while (iter.hasNext()) {
            MethodAPI m = (MethodAPI)(iter.next());
            if (m.inheritedFrom_ == null) 
                res++;
        }
        return res;