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@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 */ mergeRemoveAdd(APIDiff apiDiff)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 */ mergeRemoveAddCtor(ConstructorAPI removedCtor, ClassDiff classDiff, PackageDiff pkgDiff)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 */ mergeRemoveAddMethod(MethodAPI removedMethod, ClassDiff classDiff, PackageDiff pkgDiff)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 */ mergeSingleMethods(MethodAPI removedMethod, ClassDiff classDiff, PackageDiff pkgDiff)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 */ mergeMultipleMethods(MethodAPI removedMethod, ClassDiff classDiff, PackageDiff pkgDiff)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 */ diffMethods(MemberDiff methodDiff, MethodAPI oldMethod, MethodAPI newMethod)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 */ mergeRemoveAddField(FieldAPI removedField, ClassDiff classDiff, PackageDiff pkgDiff)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 }