FileDocCategorySizeDatePackage
MergeChanges.javaAPI DocAndroid 1.5 API19652Wed May 06 22:41:22 BST 2009jdiff

MergeChanges

public class MergeChanges extends Object
Convert some remove and add operations into change operations. Once the numbers of members removed and added are known we can deduce more information about changes. For instance, if there are two methods with the same name, and one or more of them has a parameter type change, then this can only be reported as removing the old version(s) and adding the new version(s), because there are multiple methods with the same name. However, if only one method with a given name is removed, and only one method with the same name is added, we can convert these operations to a change operation. For constructors, this is true if the types are the same. For fields, the field names have to be the same, though this should never occur, since field names are unique. Another merge which can be made is if two or more methods with the same name were marked as removed and added because of changes other than signature. See the file LICENSE.txt for copyright details.
author
Matthew Doar, mdoar@pobox.com

Fields Summary
private static boolean
trace
Set to enable increased logging verbosity for debugging.
Constructors Summary
Methods Summary
public static voiddiffMethods(MemberDiff methodDiff, MethodAPI oldMethod, MethodAPI newMethod)
Track changes in methods related to abstract, native, and synchronized modifiers here.

        // 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);
        }
        // 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);
        }
        // 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);
        }
    
public static voidmergeMultipleMethods(MethodAPI removedMethod, ClassDiff classDiff, PackageDiff pkgDiff)
Convert multiple removed and added methods into changed methods. This handles the case where the methods' signatures are unchanged, but something else changed.

        // Search on the name and signature of the method
        int startRemoved = classDiff.methodsRemoved.indexOf(removedMethod);
        int endRemoved = classDiff.methodsRemoved.lastIndexOf(removedMethod);
        int startAdded = classDiff.methodsAdded.indexOf(removedMethod);
        int endAdded = classDiff.methodsAdded.lastIndexOf(removedMethod);
        if (startRemoved != -1 && endRemoved != -1 && 
            startAdded != -1 && endAdded != -1) {
            // Find the index of the current removed method
            int removedIdx = -1;
            for (int i = startRemoved; i <= endRemoved; i++) {                
                if (removedMethod.equalSignatures(classDiff.methodsRemoved.get(i))) {
                    removedIdx = i;
                    break;
                }
            }
            if (removedIdx == -1) {
                System.out.println("Error: removed method index not found");
                System.exit(5);
            }
            // Find the index of the added method with the same signature, if 
            // it exists, and make sure it is defined locally.
            int addedIdx = -1;
            for (int i = startAdded; i <= endAdded; i++) {
                MethodAPI addedMethod2 = (MethodAPI)(classDiff.methodsAdded.get(i));
                if (addedMethod2.inheritedFrom_ == null &&
                    removedMethod.equalSignatures(addedMethod2))
                    addedIdx = i;
                    break;
            }
            if (addedIdx == -1)
                return;
            MethodAPI addedMethod = (MethodAPI)(classDiff.methodsAdded.get(addedIdx));
            // Create a MemberDiff for this change
            MemberDiff methodDiff = new MemberDiff(removedMethod.name_);
            methodDiff.oldType_ = removedMethod.returnType_;
            methodDiff.newType_ = addedMethod.returnType_;
            methodDiff.oldSignature_ = removedMethod.getSignature();
            methodDiff.newSignature_ = addedMethod.getSignature();
            methodDiff.oldExceptions_ = removedMethod.exceptions_;
            methodDiff.newExceptions_ = addedMethod.exceptions_;
                // The addModifiersChange field may not have been
                // initialized yet if there were multiple methods of the same
                // name.
                diffMethods(methodDiff, removedMethod, addedMethod);
            methodDiff.addModifiersChange(removedMethod.modifiers_.diff(addedMethod.modifiers_));
            // Track changes in documentation
            if (APIComparator.docChanged(removedMethod.doc_, addedMethod.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 + "." + addedMethod.name_ + "_changed(" + sig + ")\" class=\"hiddenlink\">";
                String id = pkgDiff.name_ + "." + classDiff.name_ + ".dmethod." + addedMethod.name_ + "(" + HTMLReportGenerator.simpleName(sig) + ")";
                String title = link1 + "Class <b>" + classDiff.name_ + "</b></a>, " +
                    link2 +  HTMLReportGenerator.simpleName(methodDiff.newType_) + " <b>" + addedMethod.name_ + "(" + HTMLReportGenerator.simpleName(sig) + ")</b></a>";
                methodDiff.documentationChange_ = Diff.saveDocDiffs(pkgDiff.name_, classDiff.name_, removedMethod.doc_, addedMethod.doc_, id, title);
            }
            classDiff.methodsChanged.add(methodDiff);
            // Now remove the entries from the remove and add lists
            classDiff.methodsRemoved.remove(removedIdx);
            classDiff.methodsAdded.remove(addedIdx);
            if (trace) {
                System.out.println("Merged the removal and addition of method " + 
                                   removedMethod.name_ + 
                                   " into one change. There were multiple methods of this name.");
            }
        }
    
public static voidmergeRemoveAdd(APIDiff apiDiff)
Convert some remove and add operations into change operations. Note that if a single thread modifies a collection directly while it is iterating over the collection with a fail-fast iterator, the iterator will throw java.util.ConcurrentModificationException

        // Go through all the ClassDiff objects searching for the above cases.
        Iterator iter = apiDiff.packagesChanged.iterator();
        while (iter.hasNext()) {
            PackageDiff pkgDiff = (PackageDiff)(iter.next());
            Iterator iter2 = pkgDiff.classesChanged.iterator();
            while (iter2.hasNext()) {
                ClassDiff classDiff = (ClassDiff)(iter2.next());
                // Note: using iterators to step through the members gives a
                // ConcurrentModificationException exception with large files.
                // Constructors
                ConstructorAPI[] ctorArr = new ConstructorAPI[classDiff.ctorsRemoved.size()];
                ctorArr = (ConstructorAPI[])classDiff.ctorsRemoved.toArray(ctorArr);
                for (int ctorIdx = 0; ctorIdx < ctorArr.length; ctorIdx++) {
                    ConstructorAPI removedCtor = ctorArr[ctorIdx];
                    mergeRemoveAddCtor(removedCtor, classDiff, pkgDiff);
                }
                // Methods
                MethodAPI[] methodArr = new MethodAPI[classDiff.methodsRemoved.size()];
                methodArr = (MethodAPI[])classDiff.methodsRemoved.toArray(methodArr);
                for (int methodIdx = 0; methodIdx < methodArr.length; methodIdx++) {
                    MethodAPI removedMethod = methodArr[methodIdx];
                    // Only merge locally defined methods
                    if (removedMethod.inheritedFrom_ == null)
                        mergeRemoveAddMethod(removedMethod, classDiff, pkgDiff);
                }
                // Fields
                FieldAPI[] fieldArr = new FieldAPI[classDiff.fieldsRemoved.size()];
                fieldArr = (FieldAPI[])classDiff.fieldsRemoved.toArray(fieldArr);
                for (int fieldIdx = 0; fieldIdx < fieldArr.length; fieldIdx++) {
                    FieldAPI removedField = fieldArr[fieldIdx]; 
                    // Only merge locally defined fields
                    if (removedField.inheritedFrom_ == null)
                        mergeRemoveAddField(removedField, classDiff, pkgDiff);
                }
            }
        }        
    
public static voidmergeRemoveAddCtor(ConstructorAPI removedCtor, ClassDiff classDiff, PackageDiff pkgDiff)
Convert some removed and added constructors into changed constructors.

        // Search on the type of the constructor
        int startRemoved = classDiff.ctorsRemoved.indexOf(removedCtor);
        int endRemoved = classDiff.ctorsRemoved.lastIndexOf(removedCtor);
        int startAdded = classDiff.ctorsAdded.indexOf(removedCtor);
        int endAdded = classDiff.ctorsAdded.lastIndexOf(removedCtor);
        if (startRemoved != -1 && startRemoved == endRemoved && 
            startAdded != -1 && startAdded == endAdded) {
            // There is only one constructor with the type of the
            // removedCtor in both the removed and added constructors.
            ConstructorAPI addedCtor = (ConstructorAPI)(classDiff.ctorsAdded.get(startAdded));
            // Create a MemberDiff for this change
            MemberDiff ctorDiff = new MemberDiff(classDiff.name_);
            ctorDiff.oldType_ = removedCtor.type_;
            ctorDiff.newType_ = addedCtor.type_; // Should be the same as removedCtor.type
            ctorDiff.oldExceptions_ = removedCtor.exceptions_;
            ctorDiff.newExceptions_ = addedCtor.exceptions_;
            ctorDiff.addModifiersChange(removedCtor.modifiers_.diff(addedCtor.modifiers_));
            // Track changes in documentation
            if (APIComparator.docChanged(removedCtor.doc_, addedCtor.doc_)) {
                String type = ctorDiff.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>";
                ctorDiff.documentationChange_ = Diff.saveDocDiffs(pkgDiff.name_, classDiff.name_, removedCtor.doc_, addedCtor.doc_, id, title);
            }
            classDiff.ctorsChanged.add(ctorDiff);
            // Now remove the entries from the remove and add lists
            classDiff.ctorsRemoved.remove(startRemoved);
            classDiff.ctorsAdded.remove(startAdded);
            if (trace && ctorDiff.modifiersChange_ != null)
                System.out.println("Merged the removal and addition of constructor into one change: " + ctorDiff.modifiersChange_);
        }
    
public static voidmergeRemoveAddField(FieldAPI removedField, ClassDiff classDiff, PackageDiff pkgDiff)
Convert some removed and added fields into changed fields.

        // Search on the name of the field
        int startRemoved = classDiff.fieldsRemoved.indexOf(removedField);
        int endRemoved = classDiff.fieldsRemoved.lastIndexOf(removedField);
        int startAdded = classDiff.fieldsAdded.indexOf(removedField);
        int endAdded = classDiff.fieldsAdded.lastIndexOf(removedField);
        if (startRemoved != -1 && startRemoved == endRemoved && 
            startAdded != -1 && startAdded == endAdded) {
            // There is only one field with the name of the
            // removedField in both the removed and added fields.
            FieldAPI addedField = (FieldAPI)(classDiff.fieldsAdded.get(startAdded));
            if (addedField.inheritedFrom_ == null) {
                // Create a MemberDiff for this change
                MemberDiff fieldDiff = new MemberDiff(removedField.name_);
                fieldDiff.oldType_ = removedField.type_;
                fieldDiff.newType_ = addedField.type_;
                fieldDiff.addModifiersChange(removedField.modifiers_.diff(addedField.modifiers_));
                // Track changes in documentation
                if (APIComparator.docChanged(removedField.doc_, addedField.doc_)) {
                    String fqName = pkgDiff.name_ + "." + classDiff.name_;
                    String link1 = "<a href=\"" + fqName + HTMLReportGenerator.reportFileExt + "\" class=\"hiddenlink\">";
                    String link2 = "<a href=\"" + fqName + HTMLReportGenerator.reportFileExt + "#" + fqName + "." + addedField.name_ + "\" class=\"hiddenlink\">";
                    String id = pkgDiff.name_ + "." + classDiff.name_ + ".field." + addedField.name_;
                    String title = link1 + "Class <b>" + classDiff.name_ + "</b></a>, " +
                        link2 + HTMLReportGenerator.simpleName(fieldDiff.newType_) + " <b>" + addedField.name_ + "</b></a>";
                    fieldDiff.documentationChange_ = Diff.saveDocDiffs(pkgDiff.name_, classDiff.name_, removedField.doc_, addedField.doc_, id, title);
                }
                classDiff.fieldsChanged.add(fieldDiff);
                // Now remove the entries from the remove and add lists
                classDiff.fieldsRemoved.remove(startRemoved);
                classDiff.fieldsAdded.remove(startAdded);
                if (trace) {
                    System.out.println("Merged the removal and addition of field " + 
                                       removedField.name_ + 
                                       " into one change");
                }
            } //if (addedField.inheritedFrom == null) 
        }
    
public static voidmergeRemoveAddMethod(MethodAPI removedMethod, ClassDiff classDiff, PackageDiff pkgDiff)
Convert some removed and added methods into changed methods.

        mergeSingleMethods(removedMethod, classDiff, pkgDiff);
        mergeMultipleMethods(removedMethod, classDiff, pkgDiff);
    
public static voidmergeSingleMethods(MethodAPI removedMethod, ClassDiff classDiff, PackageDiff pkgDiff)
Convert single removed and added methods into a changed method.

        // Search on the name of the method
        int startRemoved = classDiff.methodsRemoved.indexOf(removedMethod);
        int endRemoved = classDiff.methodsRemoved.lastIndexOf(removedMethod);
        int startAdded = classDiff.methodsAdded.indexOf(removedMethod);
        int endAdded = classDiff.methodsAdded.lastIndexOf(removedMethod);
        if (startRemoved != -1 && startRemoved == endRemoved && 
            startAdded != -1 && startAdded == endAdded) {
            // There is only one method with the name of the
            // removedMethod in both the removed and added methods.
            MethodAPI addedMethod = (MethodAPI)(classDiff.methodsAdded.get(startAdded));
            if (addedMethod.inheritedFrom_ == null) {
                // Create a MemberDiff for this change
                MemberDiff methodDiff = new MemberDiff(removedMethod.name_);
                methodDiff.oldType_ = removedMethod.returnType_;
                methodDiff.newType_ = addedMethod.returnType_;
                methodDiff.oldSignature_ = removedMethod.getSignature();
                methodDiff.newSignature_ = addedMethod.getSignature();
                methodDiff.oldExceptions_ = removedMethod.exceptions_;
                methodDiff.newExceptions_ = addedMethod.exceptions_;
                // The addModifiersChange field may not have been
                // initialized yet if there were multiple methods of the same
                // name.
                diffMethods(methodDiff, removedMethod, addedMethod);
                methodDiff.addModifiersChange(removedMethod.modifiers_.diff(addedMethod.modifiers_));
                // Track changes in documentation
                if (APIComparator.docChanged(removedMethod.doc_, addedMethod.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 + "." + addedMethod.name_ + "_changed(" + sig + ")\" class=\"hiddenlink\">";
                    String id = pkgDiff.name_ + "." + classDiff.name_ + ".dmethod." + addedMethod.name_ + "(" + HTMLReportGenerator.simpleName(sig) + ")";
                    String title = link1 + "Class <b>" + classDiff.name_ + "</b></a>, " +
                        link2 +  HTMLReportGenerator.simpleName(methodDiff.newType_) + " <b>" + addedMethod.name_ + "(" + HTMLReportGenerator.simpleName(sig) + ")</b></a>";
                    methodDiff.documentationChange_ = Diff.saveDocDiffs(pkgDiff.name_, classDiff.name_, removedMethod.doc_, addedMethod.doc_, id, title);
                }
                classDiff.methodsChanged.add(methodDiff);
                // Now remove the entries from the remove and add lists
                classDiff.methodsRemoved.remove(startRemoved);
                classDiff.methodsAdded.remove(startAdded);
                if (trace) {
                    System.out.println("Merged the removal and addition of method " + 
                                       removedMethod.name_ + 
                                       " into one change");
                }
            } //if (addedMethod.inheritedFrom_ == null)
        }