Home | History | Annotate | Download | only in jdiff
      1 package jdiff;
      2 
      3 import java.util.*;
      4 
      5 /**
      6  * Convert some remove and add operations into change operations.
      7  *
      8  * Once the numbers of members removed and added are known
      9  * we can deduce more information about changes. For instance, if there are
     10  * two methods with the same name, and one or more of them has a
     11  * parameter type change, then this can only be reported as removing
     12  * the old version(s) and adding the new version(s), because there are
     13  * multiple methods with the same name.
     14  *
     15  * However, if only <i>one</i> method with a given name is removed, and
     16  * only <i>one</i> method with the same name is added, we can convert these
     17  * operations to a change operation. For constructors, this is true if
     18  * the types are the same. For fields, the field names have to be the same,
     19  * though this should never occur, since field names are unique.
     20  *
     21  * Another merge which can be made is if two or more methods with the same name
     22  * were marked as removed and added because of changes other than signature.
     23  *
     24  * See the file LICENSE.txt for copyright details.
     25  * @author Matthew Doar, mdoar (at) pobox.com
     26  */
     27 class MergeChanges {
     28 
     29     /**
     30      * Convert some remove and add operations into change operations.
     31      *
     32      * Note that if a single thread modifies a collection directly while it is
     33      * iterating over the collection with a fail-fast iterator, the iterator
     34      * will throw java.util.ConcurrentModificationException
     35      */
     36     public static void mergeRemoveAdd(APIDiff apiDiff) {
     37         // Go through all the ClassDiff objects searching for the above cases.
     38         Iterator iter = apiDiff.packagesChanged.iterator();
     39         while (iter.hasNext()) {
     40             PackageDiff pkgDiff = (PackageDiff)(iter.next());
     41             Iterator iter2 = pkgDiff.classesChanged.iterator();
     42             while (iter2.hasNext()) {
     43                 ClassDiff classDiff = (ClassDiff)(iter2.next());
     44                 // Note: using iterators to step through the members gives a
     45                 // ConcurrentModificationException exception with large files.
     46                 // Constructors
     47                 ConstructorAPI[] ctorArr = new ConstructorAPI[classDiff.ctorsRemoved.size()];
     48                 ctorArr = (ConstructorAPI[])classDiff.ctorsRemoved.toArray(ctorArr);
     49                 for (int ctorIdx = 0; ctorIdx < ctorArr.length; ctorIdx++) {
     50                     ConstructorAPI removedCtor = ctorArr[ctorIdx];
     51                     mergeRemoveAddCtor(removedCtor, classDiff, pkgDiff);
     52                 }
     53                 // Methods
     54                 MethodAPI[] methodArr = new MethodAPI[classDiff.methodsRemoved.size()];
     55                 methodArr = (MethodAPI[])classDiff.methodsRemoved.toArray(methodArr);
     56                 for (int methodIdx = 0; methodIdx < methodArr.length; methodIdx++) {
     57                     MethodAPI removedMethod = methodArr[methodIdx];
     58                     // Only merge locally defined methods
     59                     if (removedMethod.inheritedFrom_ == null)
     60                         mergeRemoveAddMethod(removedMethod, classDiff, pkgDiff);
     61                 }
     62                 // Fields
     63                 FieldAPI[] fieldArr = new FieldAPI[classDiff.fieldsRemoved.size()];
     64                 fieldArr = (FieldAPI[])classDiff.fieldsRemoved.toArray(fieldArr);
     65                 for (int fieldIdx = 0; fieldIdx < fieldArr.length; fieldIdx++) {
     66                     FieldAPI removedField = fieldArr[fieldIdx];
     67                     // Only merge locally defined fields
     68                     if (removedField.inheritedFrom_ == null)
     69                         mergeRemoveAddField(removedField, classDiff, pkgDiff);
     70                 }
     71             }
     72         }
     73     }
     74 
     75     /**
     76      * Convert some removed and added constructors into changed constructors.
     77      */
     78     public static void mergeRemoveAddCtor(ConstructorAPI removedCtor, ClassDiff classDiff, PackageDiff pkgDiff) {
     79         // Search on the type of the constructor
     80         int startRemoved = classDiff.ctorsRemoved.indexOf(removedCtor);
     81         int endRemoved = classDiff.ctorsRemoved.lastIndexOf(removedCtor);
     82         int startAdded = classDiff.ctorsAdded.indexOf(removedCtor);
     83         int endAdded = classDiff.ctorsAdded.lastIndexOf(removedCtor);
     84         if (startRemoved != -1 && startRemoved == endRemoved &&
     85             startAdded != -1 && startAdded == endAdded) {
     86             // There is only one constructor with the type of the
     87             // removedCtor in both the removed and added constructors.
     88             ConstructorAPI addedCtor = (ConstructorAPI)(classDiff.ctorsAdded.get(startAdded));
     89             // Create a MemberDiff for this change
     90             MemberDiff ctorDiff = new MemberDiff(classDiff.name_);
     91             ctorDiff.oldType_ = removedCtor.getSignature();
     92             ctorDiff.newType_ = addedCtor.getSignature(); // Should be the same as removedCtor.type
     93             ctorDiff.oldExceptions_ = removedCtor.exceptions_;
     94             ctorDiff.newExceptions_ = addedCtor.exceptions_;
     95             ctorDiff.addModifiersChange(removedCtor.modifiers_.diff(addedCtor.modifiers_));
     96             // Track changes in documentation
     97             if (APIComparator.docChanged(removedCtor.doc_, addedCtor.doc_)) {
     98                 String type = ctorDiff.newType_;
     99                 if (type.compareTo("void") == 0)
    100                     type = "";
    101                 String fqName = pkgDiff.name_ + "." + classDiff.name_;
    102                 String link1 = "<a href=\"" + fqName + HTMLReportGenerator.reportFileExt + "\" class=\"hiddenlink\">";
    103                 String link2 = "<a href=\"" + fqName + HTMLReportGenerator.reportFileExt + "#" + fqName + ".ctor_changed(" + type + ")\" class=\"hiddenlink\">";
    104                 String id = pkgDiff.name_ + "." + classDiff.name_ + ".ctor(" + HTMLReportGenerator.simpleName(type) + ")";
    105                 String title = link1 + "Class <b>" + classDiff.name_ +
    106                     "</b></a>, " + link2 + "constructor <b>" + classDiff.name_ + "(" + HTMLReportGenerator.simpleName(type) + ")</b></a>";
    107                 ctorDiff.documentationChange_ = Diff.saveDocDiffs(pkgDiff.name_, classDiff.name_, removedCtor.doc_, addedCtor.doc_, id, title);
    108             }
    109             classDiff.ctorsChanged.add(ctorDiff);
    110             // Now remove the entries from the remove and add lists
    111             classDiff.ctorsRemoved.remove(startRemoved);
    112             classDiff.ctorsAdded.remove(startAdded);
    113             if (trace && ctorDiff.modifiersChange_ != null)
    114                 System.out.println("Merged the removal and addition of constructor into one change: " + ctorDiff.modifiersChange_);
    115         }
    116     }
    117 
    118     /**
    119      * Convert some removed and added methods into changed methods.
    120      */
    121     public static void mergeRemoveAddMethod(MethodAPI removedMethod,
    122                                             ClassDiff classDiff,
    123                                             PackageDiff pkgDiff) {
    124         mergeSingleMethods(removedMethod, classDiff, pkgDiff);
    125         mergeMultipleMethods(removedMethod, classDiff, pkgDiff);
    126     }
    127 
    128     /**
    129      * Convert single removed and added methods into a changed method.
    130      */
    131     public static void mergeSingleMethods(MethodAPI removedMethod, ClassDiff classDiff, PackageDiff pkgDiff) {
    132         // Search on the name of the method
    133         int startRemoved = classDiff.methodsRemoved.indexOf(removedMethod);
    134         int endRemoved = classDiff.methodsRemoved.lastIndexOf(removedMethod);
    135         int startAdded = classDiff.methodsAdded.indexOf(removedMethod);
    136         int endAdded = classDiff.methodsAdded.lastIndexOf(removedMethod);
    137         if (startRemoved != -1 && startRemoved == endRemoved &&
    138             startAdded != -1 && startAdded == endAdded) {
    139             // There is only one method with the name of the
    140             // removedMethod in both the removed and added methods.
    141             MethodAPI addedMethod = (MethodAPI)(classDiff.methodsAdded.get(startAdded));
    142             if (addedMethod.inheritedFrom_ == null) {
    143                 // Create a MemberDiff for this change
    144                 MemberDiff methodDiff = new MemberDiff(removedMethod.name_);
    145                 methodDiff.oldType_ = removedMethod.returnType_;
    146                 methodDiff.newType_ = addedMethod.returnType_;
    147                 methodDiff.oldSignature_ = removedMethod.getSignature();
    148                 methodDiff.newSignature_ = addedMethod.getSignature();
    149                 methodDiff.oldExceptions_ = removedMethod.exceptions_;
    150                 methodDiff.newExceptions_ = addedMethod.exceptions_;
    151                 // The addModifiersChange field may not have been
    152                 // initialized yet if there were multiple methods of the same
    153                 // name.
    154                 diffMethods(methodDiff, removedMethod, addedMethod);
    155                 methodDiff.addModifiersChange(removedMethod.modifiers_.diff(addedMethod.modifiers_));
    156                 // Track changes in documentation
    157                 if (APIComparator.docChanged(removedMethod.doc_, addedMethod.doc_)) {
    158                     String sig = methodDiff.newSignature_;
    159                     if (sig.compareTo("void") == 0)
    160                         sig = "";
    161                     String fqName = pkgDiff.name_ + "." + classDiff.name_;
    162                     String link1 = "<a href=\"" + fqName + HTMLReportGenerator.reportFileExt + "\" class=\"hiddenlink\">";
    163                     String link2 = "<a href=\"" + fqName + HTMLReportGenerator.reportFileExt + "#" + fqName + "." + addedMethod.name_ + "_changed(" + sig + ")\" class=\"hiddenlink\">";
    164                     String id = pkgDiff.name_ + "." + classDiff.name_ + ".dmethod." + addedMethod.name_ + "(" + HTMLReportGenerator.simpleName(sig) + ")";
    165                     String title = link1 + "Class <b>" + classDiff.name_ + "</b></a>, " +
    166                         link2 +  HTMLReportGenerator.simpleName(methodDiff.newType_) + " <b>" + addedMethod.name_ + "(" + HTMLReportGenerator.simpleName(sig) + ")</b></a>";
    167                     methodDiff.documentationChange_ = Diff.saveDocDiffs(pkgDiff.name_, classDiff.name_, removedMethod.doc_, addedMethod.doc_, id, title);
    168                 }
    169                 classDiff.methodsChanged.add(methodDiff);
    170                 // Now remove the entries from the remove and add lists
    171                 classDiff.methodsRemoved.remove(startRemoved);
    172                 classDiff.methodsAdded.remove(startAdded);
    173                 if (trace) {
    174                     System.out.println("Merged the removal and addition of method " +
    175                                        removedMethod.name_ +
    176                                        " into one change");
    177                 }
    178             } //if (addedMethod.inheritedFrom_ == null)
    179         }
    180     }
    181 
    182     /**
    183      * Convert multiple removed and added methods into changed methods.
    184      * This handles the case where the methods' signatures are unchanged, but
    185      * something else changed.
    186      */
    187     public static void mergeMultipleMethods(MethodAPI removedMethod, ClassDiff classDiff, PackageDiff pkgDiff) {
    188         // Search on the name and signature of the method
    189         int startRemoved = classDiff.methodsRemoved.indexOf(removedMethod);
    190         int endRemoved = classDiff.methodsRemoved.lastIndexOf(removedMethod);
    191         int startAdded = classDiff.methodsAdded.indexOf(removedMethod);
    192         int endAdded = classDiff.methodsAdded.lastIndexOf(removedMethod);
    193         if (startRemoved != -1 && endRemoved != -1 &&
    194             startAdded != -1 && endAdded != -1) {
    195             // Find the index of the current removed method
    196             int removedIdx = -1;
    197             for (int i = startRemoved; i <= endRemoved; i++) {
    198                 if (removedMethod.equalSignatures(classDiff.methodsRemoved.get(i))) {
    199                     removedIdx = i;
    200                     break;
    201                 }
    202             }
    203             if (removedIdx == -1) {
    204                 System.out.println("Error: removed method index not found");
    205                 System.exit(5);
    206             }
    207             // Find the index of the added method with the same signature, if
    208             // it exists, and make sure it is defined locally.
    209             int addedIdx = -1;
    210             for (int i = startAdded; i <= endAdded; i++) {
    211                 MethodAPI addedMethod2 = (MethodAPI)(classDiff.methodsAdded.get(i));
    212                 if (addedMethod2.inheritedFrom_ == null &&
    213                     removedMethod.equalSignatures(addedMethod2)) {
    214                     addedIdx = i;
    215                     break;
    216                 }
    217             }
    218             if (addedIdx == -1)
    219                 return;
    220             MethodAPI addedMethod = (MethodAPI)(classDiff.methodsAdded.get(addedIdx));
    221             // Create a MemberDiff for this change
    222             MemberDiff methodDiff = new MemberDiff(removedMethod.name_);
    223             methodDiff.oldType_ = removedMethod.returnType_;
    224             methodDiff.newType_ = addedMethod.returnType_;
    225             methodDiff.oldSignature_ = removedMethod.getSignature();
    226             methodDiff.newSignature_ = addedMethod.getSignature();
    227             methodDiff.oldExceptions_ = removedMethod.exceptions_;
    228             methodDiff.newExceptions_ = addedMethod.exceptions_;
    229                 // The addModifiersChange field may not have been
    230                 // initialized yet if there were multiple methods of the same
    231                 // name.
    232                 diffMethods(methodDiff, removedMethod, addedMethod);
    233             methodDiff.addModifiersChange(removedMethod.modifiers_.diff(addedMethod.modifiers_));
    234             // Track changes in documentation
    235             if (APIComparator.docChanged(removedMethod.doc_, addedMethod.doc_)) {
    236                 String sig = methodDiff.newSignature_;
    237                 if (sig.compareTo("void") == 0)
    238                     sig = "";
    239                 String fqName = pkgDiff.name_ + "." + classDiff.name_;
    240                 String link1 = "<a href=\"" + fqName + HTMLReportGenerator.reportFileExt + "\" class=\"hiddenlink\">";
    241                 String link2 = "<a href=\"" + fqName + HTMLReportGenerator.reportFileExt + "#" + fqName + "." + addedMethod.name_ + "_changed(" + sig + ")\" class=\"hiddenlink\">";
    242                 String id = pkgDiff.name_ + "." + classDiff.name_ + ".dmethod." + addedMethod.name_ + "(" + HTMLReportGenerator.simpleName(sig) + ")";
    243                 String title = link1 + "Class <b>" + classDiff.name_ + "</b></a>, " +
    244                     link2 +  HTMLReportGenerator.simpleName(methodDiff.newType_) + " <b>" + addedMethod.name_ + "(" + HTMLReportGenerator.simpleName(sig) + ")</b></a>";
    245                 methodDiff.documentationChange_ = Diff.saveDocDiffs(pkgDiff.name_, classDiff.name_, removedMethod.doc_, addedMethod.doc_, id, title);
    246             }
    247             classDiff.methodsChanged.add(methodDiff);
    248             // Now remove the entries from the remove and add lists
    249             classDiff.methodsRemoved.remove(removedIdx);
    250             classDiff.methodsAdded.remove(addedIdx);
    251             if (trace) {
    252                 System.out.println("Merged the removal and addition of method " +
    253                                    removedMethod.name_ +
    254                                    " into one change. There were multiple methods of this name.");
    255             }
    256         }
    257     }
    258 
    259     /**
    260      * Track changes in methods related to abstract, native, and
    261      * synchronized modifiers here.
    262      */
    263     public static void diffMethods(MemberDiff methodDiff,
    264                                    MethodAPI oldMethod,
    265                                    MethodAPI newMethod) {
    266         // Abstract or not
    267         if (oldMethod.isAbstract_ != newMethod.isAbstract_) {
    268             String changeText = "";
    269             if (oldMethod.isAbstract_)
    270                 changeText += "Changed from abstract to non-abstract.";
    271             else
    272                 changeText += "Changed from non-abstract to abstract.";
    273             methodDiff.addModifiersChange(changeText);
    274         }
    275         // Native or not
    276         if (Diff.showAllChanges &&
    277 	    oldMethod.isNative_ != newMethod.isNative_) {
    278             String changeText = "";
    279             if (oldMethod.isNative_)
    280                 changeText += "Changed from native to non-native.";
    281             else
    282                 changeText += "Changed from non-native to native.";
    283             methodDiff.addModifiersChange(changeText);
    284         }
    285         // Synchronized or not
    286         if (Diff.showAllChanges &&
    287 	    oldMethod.isSynchronized_ != newMethod.isSynchronized_) {
    288             String changeText = "";
    289             if (oldMethod.isSynchronized_)
    290                 changeText += "Changed from synchronized to non-synchronized.";
    291             else
    292                 changeText += "Changed from non-synchronized to synchronized.";
    293             methodDiff.addModifiersChange(changeText);
    294         }
    295     }
    296 
    297     /**
    298      * Convert some removed and added fields into changed fields.
    299      */
    300     public static void mergeRemoveAddField(FieldAPI removedField, ClassDiff classDiff, PackageDiff pkgDiff) {
    301         // Search on the name of the field
    302         int startRemoved = classDiff.fieldsRemoved.indexOf(removedField);
    303         int endRemoved = classDiff.fieldsRemoved.lastIndexOf(removedField);
    304         int startAdded = classDiff.fieldsAdded.indexOf(removedField);
    305         int endAdded = classDiff.fieldsAdded.lastIndexOf(removedField);
    306         if (startRemoved != -1 && startRemoved == endRemoved &&
    307             startAdded != -1 && startAdded == endAdded) {
    308             // There is only one field with the name of the
    309             // removedField in both the removed and added fields.
    310             FieldAPI addedField = (FieldAPI)(classDiff.fieldsAdded.get(startAdded));
    311             if (addedField.inheritedFrom_ == null) {
    312                 // Create a MemberDiff for this change
    313                 MemberDiff fieldDiff = new MemberDiff(removedField.name_);
    314                 fieldDiff.oldType_ = removedField.type_;
    315                 fieldDiff.newType_ = addedField.type_;
    316                 fieldDiff.addModifiersChange(removedField.modifiers_.diff(addedField.modifiers_));
    317                 // Track changes in documentation
    318                 if (APIComparator.docChanged(removedField.doc_, addedField.doc_)) {
    319                     String fqName = pkgDiff.name_ + "." + classDiff.name_;
    320                     String link1 = "<a href=\"" + fqName + HTMLReportGenerator.reportFileExt + "\" class=\"hiddenlink\">";
    321                     String link2 = "<a href=\"" + fqName + HTMLReportGenerator.reportFileExt + "#" + fqName + "." + addedField.name_ + "\" class=\"hiddenlink\">";
    322                     String id = pkgDiff.name_ + "." + classDiff.name_ + ".field." + addedField.name_;
    323                     String title = link1 + "Class <b>" + classDiff.name_ + "</b></a>, " +
    324                         link2 + HTMLReportGenerator.simpleName(fieldDiff.newType_) + " <b>" + addedField.name_ + "</b></a>";
    325                     fieldDiff.documentationChange_ = Diff.saveDocDiffs(pkgDiff.name_, classDiff.name_, removedField.doc_, addedField.doc_, id, title);
    326                 }
    327                 classDiff.fieldsChanged.add(fieldDiff);
    328                 // Now remove the entries from the remove and add lists
    329                 classDiff.fieldsRemoved.remove(startRemoved);
    330                 classDiff.fieldsAdded.remove(startAdded);
    331                 if (trace) {
    332                     System.out.println("Merged the removal and addition of field " +
    333                                        removedField.name_ +
    334                                        " into one change");
    335                 }
    336             } //if (addedField.inheritedFrom == null)
    337         }
    338     }
    339 
    340     /** Set to enable increased logging verbosity for debugging. */
    341     private static boolean trace = false;
    342 
    343 }
    344