1 package jdiff; 2 3 import java.util.*; 4 5 /** 6 * This class contains method to compare two API objects. 7 * The differences are stored in an APIDiff object. 8 * 9 * See the file LICENSE.txt for copyright details. 10 * @author Matthew Doar, mdoar (at) pobox.com 11 */ 12 public class APIComparator { 13 14 /** 15 * Top-level object representing the differences between two APIs. 16 * It is this object which is used to generate the report later on. 17 */ 18 public APIDiff apiDiff; 19 20 /** 21 * Package-level object representing the differences between two packages. 22 * This object is also used to determine which file to write documentation 23 * differences into. 24 */ 25 public PackageDiff pkgDiff; 26 27 /** Default constructor. */ 28 public APIComparator() { 29 apiDiff = new APIDiff(); 30 } 31 32 /** For easy local access to the old API object. */ 33 private static API oldAPI_; 34 /** For easy local access to the new API object. */ 35 private static API newAPI_; 36 37 /** 38 * Compare two APIs. 39 */ 40 public void compareAPIs(API oldAPI, API newAPI) { 41 System.out.println("JDiff: comparing the old and new APIs ..."); 42 oldAPI_ = oldAPI; 43 newAPI_ = newAPI; 44 45 double differs = 0.0; 46 47 apiDiff.oldAPIName_ = oldAPI.name_; 48 apiDiff.newAPIName_ = newAPI.name_; 49 50 Collections.sort(oldAPI.packages_); 51 Collections.sort(newAPI.packages_); 52 53 // Find packages which were removed in the new API 54 Iterator iter = oldAPI.packages_.iterator(); 55 while (iter.hasNext()) { 56 PackageAPI oldPkg = (PackageAPI)(iter.next()); 57 // This search is looking for an *exact* match. This is true in 58 // all the *API classes. 59 int idx = Collections.binarySearch(newAPI.packages_, oldPkg); 60 if (idx < 0) { 61 // If there an instance of a package with the same name 62 // in both the old and new API, then treat it as changed, 63 // rather than removed and added. There will never be more than 64 // one instance of a package with the same name in an API. 65 int existsNew = newAPI.packages_.indexOf(oldPkg); 66 if (existsNew != -1) { 67 // Package by the same name exists in both APIs 68 // but there has been some or other change. 69 differs += 2.0 * comparePackages(oldPkg, (PackageAPI)(newAPI.packages_.get(existsNew))); 70 } else { 71 if (trace) 72 System.out.println("Package " + oldPkg.name_ + " was removed"); 73 apiDiff.packagesRemoved.add(oldPkg); 74 differs += 1.0; 75 } 76 } else { 77 // The package exists unchanged in name or doc, but may 78 // differ in classes and their members, so it still needs to 79 // be compared. 80 differs += 2.0 * comparePackages(oldPkg, (PackageAPI)(newAPI.packages_.get(idx))); 81 } 82 } // while (iter.hasNext()) 83 84 // Find packages which were added or changed in the new API 85 iter = newAPI.packages_.iterator(); 86 while (iter.hasNext()) { 87 PackageAPI newPkg = (PackageAPI)(iter.next()); 88 int idx = Collections.binarySearch(oldAPI.packages_, newPkg); 89 if (idx < 0) { 90 // See comments above 91 int existsOld = oldAPI.packages_.indexOf(newPkg); 92 if (existsOld != -1) { 93 // Don't mark a package as added or compare it 94 // if it was already marked as changed 95 } else { 96 if (trace) 97 System.out.println("Package " + newPkg.name_ + " was added"); 98 apiDiff.packagesAdded.add(newPkg); 99 differs += 1.0; 100 } 101 } else { 102 // It will already have been compared above. 103 } 104 } // while (iter.hasNext()) 105 106 // Now that the numbers of members removed and added are known 107 // we can deduce more information about changes. 108 MergeChanges.mergeRemoveAdd(apiDiff); 109 110 // The percent change statistic reported for all elements in each API is 111 // defined recursively as follows: 112 // 113 // %age change = 100 * (added + removed + 2*changed) 114 // ----------------------------------- 115 // sum of public elements in BOTH APIs 116 // 117 // The definition ensures that if all classes are removed and all new classes 118 // added, the change will be 100%. 119 // Evaluation of the visibility of elements has already been done when the 120 // XML was written out. 121 // Note that this doesn't count changes in the modifiers of classes and 122 // packages. Other changes in members are counted. 123 Long denom = new Long(oldAPI.packages_.size() + newAPI.packages_.size()); 124 // This should never be zero because an API always has packages? 125 if (denom.intValue() == 0) { 126 System.out.println("Error: no packages found in the APIs."); 127 return; 128 } 129 if (trace) 130 System.out.println("Top level changes: " + differs + "/" + denom.intValue()); 131 differs = (100.0 * differs)/denom.doubleValue(); 132 133 // Some differences such as documentation changes are not tracked in 134 // the difference statistic, so a value of 0.0 does not mean that there 135 // were no differences between the APIs. 136 apiDiff.pdiff = differs; 137 Double percentage = new Double(differs); 138 int approxPercentage = percentage.intValue(); 139 if (approxPercentage == 0) 140 System.out.println(" Approximately " + percentage + "% difference between the APIs"); 141 else 142 System.out.println(" Approximately " + approxPercentage + "% difference between the APIs"); 143 144 Diff.closeDiffFile(); 145 } 146 147 /** 148 * Compare two packages. 149 */ 150 public double comparePackages(PackageAPI oldPkg, PackageAPI newPkg) { 151 if (trace) 152 System.out.println("Comparing old package " + oldPkg.name_ + 153 " and new package " + newPkg.name_); 154 pkgDiff = new PackageDiff(oldPkg.name_); 155 double differs = 0.0; 156 157 Collections.sort(oldPkg.classes_); 158 Collections.sort(newPkg.classes_); 159 160 // Find classes which were removed in the new package 161 Iterator iter = oldPkg.classes_.iterator(); 162 while (iter.hasNext()) { 163 ClassAPI oldClass = (ClassAPI)(iter.next()); 164 // This search is looking for an *exact* match. This is true in 165 // all the *API classes. 166 int idx = Collections.binarySearch(newPkg.classes_, oldClass); 167 if (idx < 0) { 168 // If there an instance of a class with the same name 169 // in both the old and new package, then treat it as changed, 170 // rather than removed and added. There will never be more than 171 // one instance of a class with the same name in a package. 172 int existsNew = newPkg.classes_.indexOf(oldClass); 173 if (existsNew != -1) { 174 // Class by the same name exists in both packages 175 // but there has been some or other change. 176 differs += 2.0 * compareClasses(oldClass, (ClassAPI)(newPkg.classes_.get(existsNew)), pkgDiff); 177 } else { 178 if (trace) 179 System.out.println(" Class " + oldClass.name_ + " was removed"); 180 pkgDiff.classesRemoved.add(oldClass); 181 differs += 1.0; 182 } 183 } else { 184 // The class exists unchanged in name or modifiers, but may 185 // differ in members, so it still needs to be compared. 186 differs += 2.0 * compareClasses(oldClass, (ClassAPI)(newPkg.classes_.get(idx)), pkgDiff); 187 } 188 } // while (iter.hasNext()) 189 190 // Find classes which were added or changed in the new package 191 iter = newPkg.classes_.iterator(); 192 while (iter.hasNext()) { 193 ClassAPI newClass = (ClassAPI)(iter.next()); 194 int idx = Collections.binarySearch(oldPkg.classes_, newClass); 195 if (idx < 0) { 196 // See comments above 197 int existsOld = oldPkg.classes_.indexOf(newClass); 198 if (existsOld != -1) { 199 // Don't mark a class as added or compare it 200 // if it was already marked as changed 201 } else { 202 if (trace) 203 System.out.println(" Class " + newClass.name_ + " was added"); 204 pkgDiff.classesAdded.add(newClass); 205 differs += 1.0; 206 } 207 } else { 208 // It will already have been compared above. 209 } 210 } // while (iter.hasNext()) 211 212 // Check if the only change was in documentation. Bug 472521. 213 boolean differsFlag = false; 214 if (docChanged(oldPkg.doc_, newPkg.doc_)) { 215 String link = "<a href=\"pkg_" + oldPkg.name_ + HTMLReportGenerator.reportFileExt + "\" class=\"hiddenlink\">"; 216 String id = oldPkg.name_ + "!package"; 217 String title = link + "Package <b>" + oldPkg.name_ + "</b></a>"; 218 pkgDiff.documentationChange_ = Diff.saveDocDiffs(pkgDiff.name_, null, oldPkg.doc_, newPkg.doc_, id, title); 219 differsFlag = true; 220 } 221 222 // Only add to the parent Diff object if some difference has been found 223 if (differs != 0.0 || differsFlag) 224 apiDiff.packagesChanged.add(pkgDiff); 225 226 Long denom = new Long(oldPkg.classes_.size() + newPkg.classes_.size()); 227 // This should never be zero because a package always has classes? 228 if (denom.intValue() == 0) { 229 System.out.println("Warning: no classes found in the package " + oldPkg.name_); 230 return 0.0; 231 } 232 if (trace) 233 System.out.println("Package " + pkgDiff.name_ + " had a difference of " + differs + "/" + denom.intValue()); 234 pkgDiff.pdiff = 100.0 * differs/denom.doubleValue(); 235 return differs/denom.doubleValue(); 236 } // comparePackages() 237 238 /** 239 * Compare two classes. 240 * 241 * Need to compare constructors, methods and fields. 242 */ 243 public double compareClasses(ClassAPI oldClass, ClassAPI newClass, PackageDiff pkgDiff) { 244 if (trace) 245 System.out.println(" Comparing old class " + oldClass.name_ + 246 " and new class " + newClass.name_); 247 boolean differsFlag = false; 248 double differs = 0.0; 249 ClassDiff classDiff = new ClassDiff(oldClass.name_); 250 classDiff.isInterface_ = newClass.isInterface_; // Used in the report 251 252 // Track changes in modifiers - class or interface 253 if (oldClass.isInterface_ != newClass.isInterface_) { 254 classDiff.modifiersChange_ = "Changed from "; 255 if (oldClass.isInterface_) 256 classDiff.modifiersChange_ += "an interface to a class."; 257 else 258 classDiff.modifiersChange_ += "a class to an interface."; 259 differsFlag = true; 260 } 261 // Track changes in inheritance 262 String inheritanceChange = ClassDiff.diff(oldClass, newClass); 263 if (inheritanceChange != null) { 264 classDiff.inheritanceChange_ = inheritanceChange; 265 differsFlag = true; 266 } 267 // Abstract or not 268 if (oldClass.isAbstract_ != newClass.isAbstract_) { 269 String changeText = ""; 270 if (oldClass.isAbstract_) 271 changeText += "Changed from abstract to non-abstract."; 272 else 273 changeText += "Changed from non-abstract to abstract."; 274 classDiff.addModifiersChange(changeText); 275 differsFlag = true; 276 } 277 // Track changes in documentation 278 if (docChanged(oldClass.doc_, newClass.doc_)) { 279 String fqName = pkgDiff.name_ + "." + classDiff.name_; 280 String link = "<a href=\"" + fqName + HTMLReportGenerator.reportFileExt + "\" class=\"hiddenlink\">"; 281 String id = pkgDiff.name_ + "." + classDiff.name_ + "!class"; 282 String title = link + "Class <b>" + classDiff.name_ + "</b></a>"; 283 classDiff.documentationChange_ = Diff.saveDocDiffs(pkgDiff.name_, 284 classDiff.name_, oldClass.doc_, newClass.doc_, id, title); 285 differsFlag = true; 286 } 287 // All other modifiers 288 String modifiersChange = oldClass.modifiers_.diff(newClass.modifiers_); 289 if (modifiersChange != null) { 290 differsFlag = true; 291 if (modifiersChange.indexOf("Change from deprecated to undeprecated") != -1) { 292 System.out.println("JDiff: warning: change from deprecated to undeprecated for class " + pkgDiff.name_ + "." + newClass.name_); 293 294 } 295 } 296 classDiff.addModifiersChange(modifiersChange); 297 298 // Track changes in members 299 boolean differsCtors = 300 compareAllCtors(oldClass, newClass, classDiff); 301 boolean differsMethods = 302 compareAllMethods(oldClass, newClass, classDiff); 303 boolean differsFields = 304 compareAllFields(oldClass, newClass, classDiff); 305 if (differsCtors || differsMethods || differsFields) 306 differsFlag = true; 307 308 if (trace) { 309 System.out.println(" Ctors differ? " + differsCtors + 310 ", Methods differ? " + differsMethods + 311 ", Fields differ? " + differsFields); 312 } 313 314 // Only add to the parent if some difference has been found 315 if (differsFlag) 316 pkgDiff.classesChanged.add(classDiff); 317 318 // Get the numbers of affected elements from the classDiff object 319 differs = 320 classDiff.ctorsRemoved.size() + classDiff.ctorsAdded.size() + 321 classDiff.ctorsChanged.size() + 322 classDiff.methodsRemoved.size() + classDiff.methodsAdded.size() + 323 classDiff.methodsChanged.size() + 324 classDiff.fieldsRemoved.size() + classDiff.fieldsAdded.size() + 325 classDiff.fieldsChanged.size(); 326 Long denom = new Long( 327 oldClass.ctors_.size() + 328 numLocalMethods(oldClass.methods_) + 329 numLocalFields(oldClass.fields_) + 330 newClass.ctors_.size() + 331 numLocalMethods(newClass.methods_) + 332 numLocalFields(newClass.fields_)); 333 if (denom.intValue() == 0) { 334 // This is probably a placeholder interface, but documentation 335 // or modifiers etc may have changed 336 if (differsFlag) { 337 classDiff.pdiff = 0.0; // 100.0 is too much 338 return 1.0; 339 } else { 340 return 0.0; 341 } 342 } 343 // Handle the case where the only change is in documentation or 344 // the modifiers 345 if (differsFlag && differs == 0.0) { 346 differs = 1.0; 347 } 348 if (trace) 349 System.out.println(" Class " + classDiff.name_ + " had a difference of " + differs + "/" + denom.intValue()); 350 classDiff.pdiff = 100.0 * differs/denom.doubleValue(); 351 return differs/denom.doubleValue(); 352 } // compareClasses() 353 354 /** 355 * Compare all the constructors in two classes. 356 * 357 * The compareTo method in the ConstructorAPI class acts only upon the type. 358 */ 359 public boolean compareAllCtors(ClassAPI oldClass, ClassAPI newClass, 360 ClassDiff classDiff) { 361 if (trace) 362 System.out.println(" Comparing constructors: #old " + 363 oldClass.ctors_.size() + ", #new " + newClass.ctors_.size()); 364 boolean differs = false; 365 boolean singleCtor = false; // Set if there is only one ctor 366 367 Collections.sort(oldClass.ctors_); 368 Collections.sort(newClass.ctors_); 369 370 // Find ctors which were removed in the new class 371 Iterator iter = oldClass.ctors_.iterator(); 372 while (iter.hasNext()) { 373 ConstructorAPI oldCtor = (ConstructorAPI)(iter.next()); 374 int idx = Collections.binarySearch(newClass.ctors_, oldCtor); 375 if (idx < 0) { 376 int oldSize = oldClass.ctors_.size(); 377 int newSize = newClass.ctors_.size(); 378 if (oldSize == 1 && oldSize == newSize) { 379 // If there is one constructor in the oldClass and one 380 // constructor in the new class, then mark it as changed 381 MemberDiff memberDiff = new MemberDiff(oldClass.name_); 382 memberDiff.oldType_ = oldCtor.type_; 383 memberDiff.oldExceptions_ = oldCtor.exceptions_; 384 ConstructorAPI newCtor = (ConstructorAPI)(newClass.ctors_.get(0)); 385 memberDiff.newType_ = newCtor.type_; 386 memberDiff.newExceptions_ = newCtor.exceptions_; 387 // Track changes in documentation 388 if (docChanged(oldCtor.doc_, newCtor.doc_)) { 389 String type = memberDiff.newType_; 390 if (type.compareTo("void") == 0) 391 type = ""; 392 String fqName = pkgDiff.name_ + "." + classDiff.name_; 393 String link1 = "<a href=\"" + fqName + HTMLReportGenerator.reportFileExt + "\" class=\"hiddenlink\">"; 394 String link2 = "<a href=\"" + fqName + HTMLReportGenerator.reportFileExt + "#" + fqName + ".ctor_changed(" + type + ")\" class=\"hiddenlink\">"; 395 String id = pkgDiff.name_ + "." + classDiff.name_ + ".ctor(" + HTMLReportGenerator.simpleName(type) + ")"; 396 String title = link1 + "Class <b>" + classDiff.name_ + 397 "</b></a>, " + link2 + "constructor <b>" + classDiff.name_ + "(" + HTMLReportGenerator.simpleName(type) + ")</b></a>"; 398 memberDiff.documentationChange_ = Diff.saveDocDiffs( 399 pkgDiff.name_, classDiff.name_, oldCtor.doc_, newCtor.doc_, id, title); 400 } 401 String modifiersChange = oldCtor.modifiers_.diff(newCtor.modifiers_); 402 if (modifiersChange != null && modifiersChange.indexOf("Change from deprecated to undeprecated") != -1) { 403 System.out.println("JDiff: warning: change from deprecated to undeprecated for a constructor in class" + newClass.name_); 404 } 405 memberDiff.addModifiersChange(modifiersChange); 406 if (trace) 407 System.out.println(" The single constructor was changed"); 408 classDiff.ctorsChanged.add(memberDiff); 409 singleCtor = true; 410 } else { 411 if (trace) 412 System.out.println(" Constructor " + oldClass.name_ + " was removed"); 413 classDiff.ctorsRemoved.add(oldCtor); 414 } 415 differs = true; 416 } 417 } // while (iter.hasNext()) 418 419 // Find ctors which were added in the new class 420 iter = newClass.ctors_.iterator(); 421 while (iter.hasNext()) { 422 ConstructorAPI newCtor = (ConstructorAPI)(iter.next()); 423 int idx = Collections.binarySearch(oldClass.ctors_, newCtor); 424 if (idx < 0) { 425 if (!singleCtor) { 426 if (trace) 427 System.out.println(" Constructor " + oldClass.name_ + " was added"); 428 classDiff.ctorsAdded.add(newCtor); 429 differs = true; 430 } 431 } 432 } // while (iter.hasNext()) 433 434 return differs; 435 } // compareAllCtors() 436 437 /** 438 * Compare all the methods in two classes. 439 * 440 * We have to deal with the cases where: 441 * - there is only one method with a given name, but its signature changes 442 * - there is more than one method with the same name, and some of them 443 * may have signature changes 444 * The simplest way to deal with this is to make the MethodAPI comparator 445 * check the params and return type, as well as the name. This means that 446 * changing a parameter's type would cause the method to be seen as 447 * removed and added. To avoid this for the simple case, check for before 448 * recording a method as removed or added. 449 */ 450 public boolean compareAllMethods(ClassAPI oldClass, ClassAPI newClass, ClassDiff classDiff) { 451 if (trace) 452 System.out.println(" Comparing methods: #old " + 453 oldClass.methods_.size() + ", #new " + 454 newClass.methods_.size()); 455 boolean differs = false; 456 457 Collections.sort(oldClass.methods_); 458 Collections.sort(newClass.methods_); 459 460 // Find methods which were removed in the new class 461 Iterator iter = oldClass.methods_.iterator(); 462 while (iter.hasNext()) { 463 MethodAPI oldMethod = (MethodAPI)(iter.next()); 464 int idx = -1; 465 MethodAPI[] methodArr = new MethodAPI[newClass.methods_.size()]; 466 methodArr = (MethodAPI[])newClass.methods_.toArray(methodArr); 467 for (int methodIdx = 0; methodIdx < methodArr.length; methodIdx++) { 468 MethodAPI newMethod = methodArr[methodIdx]; 469 if (oldMethod.compareTo(newMethod) == 0) { 470 idx = methodIdx; 471 break; 472 } 473 } 474 // NOTE: there was a problem with the binarySearch for 475 // java.lang.Byte.toString(byte b) returning -16 when the compareTo method 476 // returned 0 on entry 13. Changed to use arrays instead, so maybe it was 477 // an issue with methods having another List of params used indirectly by 478 // compareTo(), unlike constructors and fields? 479 // int idx = Collections.binarySearch(newClass.methods_, oldMethod); 480 if (idx < 0) { 481 // If there is only one instance of a method with this name 482 // in both the old and new class, then treat it as changed, 483 // rather than removed and added. 484 // Find how many instances of this method name there are in 485 // the old and new class. The equals comparator is just on 486 // the method name. 487 int startOld = oldClass.methods_.indexOf(oldMethod); 488 int endOld = oldClass.methods_.lastIndexOf(oldMethod); 489 int startNew = newClass.methods_.indexOf(oldMethod); 490 int endNew = newClass.methods_.lastIndexOf(oldMethod); 491 492 if (startOld != -1 && startOld == endOld && 493 startNew != -1 && startNew == endNew) { 494 MethodAPI newMethod = (MethodAPI)(newClass.methods_.get(startNew)); 495 // Only one method with that name exists in both packages, 496 // so it is valid to compare the two methods. We know it 497 // has changed, because the binarySearch did not find it. 498 if (oldMethod.inheritedFrom_ == null || 499 newMethod.inheritedFrom_ == null) { 500 // We also know that at least one of the methods is 501 // locally defined. 502 compareMethods(oldMethod, newMethod, classDiff); 503 differs = true; 504 } 505 } else if (oldMethod.inheritedFrom_ == null) { 506 // Only concerned with locally defined methods 507 if (trace) 508 System.out.println(" Method " + oldMethod.name_ + 509 "(" + oldMethod.getSignature() + 510 ") was removed"); 511 classDiff.methodsRemoved.add(oldMethod); 512 differs = true; 513 } 514 } 515 } // while (iter.hasNext()) 516 517 // Find methods which were added in the new class 518 iter = newClass.methods_.iterator(); 519 while (iter.hasNext()) { 520 MethodAPI newMethod = (MethodAPI)(iter.next()); 521 // Only concerned with locally defined methods 522 if (newMethod.inheritedFrom_ != null) 523 continue; 524 int idx = -1; 525 MethodAPI[] methodArr = new MethodAPI[oldClass.methods_.size()]; 526 methodArr = (MethodAPI[])oldClass.methods_.toArray(methodArr); 527 for (int methodIdx = 0; methodIdx < methodArr.length; methodIdx++) { 528 MethodAPI oldMethod = methodArr[methodIdx]; 529 if (newMethod.compareTo(oldMethod) == 0) { 530 idx = methodIdx; 531 break; 532 } 533 } 534 // See note above about searching an array instead of binarySearch 535 // int idx = Collections.binarySearch(oldClass.methods_, newMethod); 536 if (idx < 0) { 537 // See comments above 538 int startOld = oldClass.methods_.indexOf(newMethod); 539 int endOld = oldClass.methods_.lastIndexOf(newMethod); 540 int startNew = newClass.methods_.indexOf(newMethod); 541 int endNew = newClass.methods_.lastIndexOf(newMethod); 542 543 if (startOld != -1 && startOld == endOld && 544 startNew != -1 && startNew == endNew) { 545 // Don't mark a method as added if it was marked as changed 546 // The comparison will have been done just above here. 547 } else { 548 if (trace) 549 System.out.println(" Method " + newMethod.name_ + 550 "(" + newMethod.getSignature() + ") was added"); 551 classDiff.methodsAdded.add(newMethod); 552 differs = true; 553 } 554 } 555 } // while (iter.hasNext()) 556 557 return differs; 558 } // compareAllMethods() 559 560 /** 561 * Compare two methods which have the same name. 562 */ 563 public boolean compareMethods(MethodAPI oldMethod, MethodAPI newMethod, ClassDiff classDiff) { 564 MemberDiff methodDiff = new MemberDiff(oldMethod.name_); 565 boolean differs = false; 566 // Check changes in return type 567 methodDiff.oldType_ = oldMethod.returnType_; 568 methodDiff.newType_ = newMethod.returnType_; 569 if (oldMethod.returnType_.compareTo(newMethod.returnType_) != 0) { 570 differs = true; 571 } 572 // Check changes in signature 573 String oldSig = oldMethod.getSignature(); 574 String newSig = newMethod.getSignature(); 575 methodDiff.oldSignature_ = oldSig; 576 methodDiff.newSignature_ = newSig; 577 if (oldSig.compareTo(newSig) != 0) { 578 differs = true; 579 } 580 // Changes in inheritance 581 int inh = changedInheritance(oldMethod.inheritedFrom_, newMethod.inheritedFrom_); 582 if (inh != 0) 583 differs = true; 584 if (inh == 1) { 585 methodDiff.addModifiersChange("Method was locally defined, but is now inherited from " + linkToClass(newMethod, true) + "."); 586 methodDiff.inheritedFrom_ = newMethod.inheritedFrom_; 587 } else if (inh == 2) { 588 methodDiff.addModifiersChange("Method was inherited from " + linkToClass(oldMethod, false) + ", but is now defined locally."); 589 } else if (inh == 3) { 590 methodDiff.addModifiersChange("Method was inherited from " + 591 linkToClass(oldMethod, false) + ", and is now inherited from " + linkToClass(newMethod, true) + "."); 592 methodDiff.inheritedFrom_ = newMethod.inheritedFrom_; 593 } 594 // Abstract or not 595 if (oldMethod.isAbstract_ != newMethod.isAbstract_) { 596 String changeText = ""; 597 if (oldMethod.isAbstract_) 598 changeText += "Changed from abstract to non-abstract."; 599 else 600 changeText += "Changed from non-abstract to abstract."; 601 methodDiff.addModifiersChange(changeText); 602 differs = true; 603 } 604 // Native or not 605 if (Diff.showAllChanges && 606 oldMethod.isNative_ != newMethod.isNative_) { 607 String changeText = ""; 608 if (oldMethod.isNative_) 609 changeText += "Changed from native to non-native."; 610 else 611 changeText += "Changed from non-native to native."; 612 methodDiff.addModifiersChange(changeText); 613 differs = true; 614 } 615 // Synchronized or not 616 if (Diff.showAllChanges && 617 oldMethod.isSynchronized_ != newMethod.isSynchronized_) { 618 String changeText = ""; 619 if (oldMethod.isSynchronized_) 620 changeText += "Changed from synchronized to non-synchronized."; 621 else 622 changeText += "Changed from non-synchronized to synchronized."; 623 methodDiff.addModifiersChange(changeText); 624 differs = true; 625 } 626 627 // Check changes in exceptions thrown 628 methodDiff.oldExceptions_ = oldMethod.exceptions_; 629 methodDiff.newExceptions_ = newMethod.exceptions_; 630 if (oldMethod.exceptions_.compareTo(newMethod.exceptions_) != 0) { 631 differs = true; 632 } 633 634 // Track changes in documentation 635 if (docChanged(oldMethod.doc_, newMethod.doc_)) { 636 String sig = methodDiff.newSignature_; 637 if (sig.compareTo("void") == 0) 638 sig = ""; 639 String fqName = pkgDiff.name_ + "." + classDiff.name_; 640 String link1 = "<a href=\"" + fqName + HTMLReportGenerator.reportFileExt + "\" class=\"hiddenlink\">"; 641 String link2 = "<a href=\"" + fqName + HTMLReportGenerator.reportFileExt + "#" + fqName + "." + newMethod.name_ + "_changed(" + sig + ")\" class=\"hiddenlink\">"; 642 String id = pkgDiff.name_ + "." + classDiff.name_ + ".dmethod." + newMethod.name_ + "(" + HTMLReportGenerator.simpleName(sig) + ")"; 643 String title = link1 + "Class <b>" + classDiff.name_ + "</b></a>, " + 644 link2 + HTMLReportGenerator.simpleName(methodDiff.newType_) + " <b>" + newMethod.name_ + "(" + HTMLReportGenerator.simpleName(sig) + ")</b></a>"; 645 methodDiff.documentationChange_ = Diff.saveDocDiffs(pkgDiff.name_, classDiff.name_, oldMethod.doc_, newMethod.doc_, id, title); 646 differs = true; 647 } 648 649 // All other modifiers 650 String modifiersChange = oldMethod.modifiers_.diff(newMethod.modifiers_); 651 if (modifiersChange != null) { 652 differs = true; 653 if (modifiersChange.indexOf("Change from deprecated to undeprecated") != -1) { 654 System.out.println("JDiff: warning: change from deprecated to undeprecated for method " + classDiff.name_ + "." + newMethod.name_); 655 656 } 657 } 658 methodDiff.addModifiersChange(modifiersChange); 659 660 // Only add to the parent if some difference has been found 661 if (differs) { 662 if (trace) { 663 System.out.println(" Method " + newMethod.name_ + 664 " was changed: old: " + 665 oldMethod.returnType_ + "(" + oldSig + "), new: " + 666 newMethod.returnType_ + "(" + newSig + ")"); 667 if (methodDiff.modifiersChange_ != null) 668 System.out.println(" Modifier change: " + methodDiff.modifiersChange_); 669 } 670 classDiff.methodsChanged.add(methodDiff); 671 } 672 673 return differs; 674 } // compareMethods() 675 676 /** 677 * Compare all the fields in two classes. 678 */ 679 public boolean compareAllFields(ClassAPI oldClass, ClassAPI newClass, 680 ClassDiff classDiff) { 681 if (trace) 682 System.out.println(" Comparing fields: #old " + 683 oldClass.fields_.size() + ", #new " 684 + newClass.fields_.size()); 685 boolean differs = false; 686 687 Collections.sort(oldClass.fields_); 688 Collections.sort(newClass.fields_); 689 690 // Find fields which were removed in the new class 691 Iterator iter = oldClass.fields_.iterator(); 692 while (iter.hasNext()) { 693 FieldAPI oldField = (FieldAPI)(iter.next()); 694 int idx = Collections.binarySearch(newClass.fields_, oldField); 695 if (idx < 0) { 696 // If there an instance of a field with the same name 697 // in both the old and new class, then treat it as changed, 698 // rather than removed and added. There will never be more than 699 // one instance of a field with the same name in a class. 700 int existsNew = newClass.fields_.indexOf(oldField); 701 if (existsNew != -1) { 702 FieldAPI newField = (FieldAPI)(newClass.fields_.get(existsNew)); 703 if (oldField.inheritedFrom_ == null || 704 newField.inheritedFrom_ == null) { 705 // We also know that one of the fields is locally defined. 706 MemberDiff memberDiff = new MemberDiff(oldField.name_); 707 memberDiff.oldType_ = oldField.type_; 708 memberDiff.newType_ = newField.type_; 709 // Changes in inheritance 710 int inh = changedInheritance(oldField.inheritedFrom_, newField.inheritedFrom_); 711 if (inh != 0) 712 differs = true; 713 if (inh == 1) { 714 memberDiff.addModifiersChange("Field was locally defined, but is now inherited from " + linkToClass(newField, true) + "."); 715 memberDiff.inheritedFrom_ = newField.inheritedFrom_; 716 } else if (inh == 2) { 717 memberDiff.addModifiersChange("Field was inherited from " + linkToClass(oldField, false) + ", but is now defined locally."); 718 } else if (inh == 3) { 719 memberDiff.addModifiersChange("Field was inherited from " + linkToClass(oldField, false) + ", and is now inherited from " + linkToClass(newField, true) + "."); 720 memberDiff.inheritedFrom_ = newField.inheritedFrom_; 721 } 722 // Transient or not 723 if (oldField.isTransient_ != newField.isTransient_) { 724 String changeText = ""; 725 if (oldField.isTransient_) 726 changeText += "Changed from transient to non-transient."; 727 else 728 changeText += "Changed from non-transient to transient."; 729 memberDiff.addModifiersChange(changeText); 730 differs = true; 731 } 732 // Volatile or not 733 if (oldField.isVolatile_ != newField.isVolatile_) { 734 String changeText = ""; 735 if (oldField.isVolatile_) 736 changeText += "Changed from volatile to non-volatile."; 737 else 738 changeText += "Changed from non-volatile to volatile."; 739 memberDiff.addModifiersChange(changeText); 740 differs = true; 741 } 742 // Change in value of the field 743 if (oldField.value_ != null && 744 newField.value_ != null && 745 oldField.value_.compareTo(newField.value_) != 0) { 746 String changeText = "Changed in value from " + oldField.value_ 747 + " to " + newField.value_ +"."; 748 memberDiff.addModifiersChange(changeText); 749 differs = true; 750 } 751 // Track changes in documentation 752 if (docChanged(oldField.doc_, newField.doc_)) { 753 String fqName = pkgDiff.name_ + "." + classDiff.name_; 754 String link1 = "<a href=\"" + fqName + HTMLReportGenerator.reportFileExt + "\" class=\"hiddenlink\">"; 755 String link2 = "<a href=\"" + fqName + HTMLReportGenerator.reportFileExt + "#" + fqName + "." + newField.name_ + "\" class=\"hiddenlink\">"; 756 String id = pkgDiff.name_ + "." + classDiff.name_ + ".field." + newField.name_; 757 String title = link1 + "Class <b>" + classDiff.name_ + "</b></a>, " + 758 link2 + HTMLReportGenerator.simpleName(memberDiff.newType_) + " <b>" + newField.name_ + "</b></a>"; 759 memberDiff.documentationChange_ = Diff.saveDocDiffs(pkgDiff.name_, classDiff.name_, oldField.doc_, newField.doc_, id, title); 760 differs = true; 761 } 762 763 // Other differences 764 String modifiersChange = oldField.modifiers_.diff(newField.modifiers_); 765 memberDiff.addModifiersChange(modifiersChange); 766 if (modifiersChange != null && modifiersChange.indexOf("Change from deprecated to undeprecated") != -1) { 767 System.out.println("JDiff: warning: change from deprecated to undeprecated for class " + newClass.name_ + ", field " + newField.name_); 768 } 769 if (trace) 770 System.out.println(" Field " + newField.name_ + " was changed"); 771 classDiff.fieldsChanged.add(memberDiff); 772 differs = true; 773 } 774 } else if (oldField.inheritedFrom_ == null) { 775 if (trace) 776 System.out.println(" Field " + oldField.name_ + " was removed"); 777 classDiff.fieldsRemoved.add(oldField); 778 differs = true; 779 } 780 } 781 } // while (iter.hasNext()) 782 783 // Find fields which were added in the new class 784 iter = newClass.fields_.iterator(); 785 while (iter.hasNext()) { 786 FieldAPI newField = (FieldAPI)(iter.next()); 787 // Only concerned with locally defined fields 788 if (newField.inheritedFrom_ != null) 789 continue; 790 int idx = Collections.binarySearch(oldClass.fields_, newField); 791 if (idx < 0) { 792 // See comments above 793 int existsOld = oldClass.fields_.indexOf(newField); 794 if (existsOld != -1) { 795 // Don't mark a field as added if it was marked as changed 796 } else { 797 if (trace) 798 System.out.println(" Field " + newField.name_ + " was added"); 799 classDiff.fieldsAdded.add(newField); 800 differs = true; 801 } 802 } 803 } // while (iter.hasNext()) 804 805 return differs; 806 } // compareFields() 807 808 /** 809 * Decide if two blocks of documentation changed. 810 * 811 * @return true if both are non-null and differ, 812 * or if one is null and the other is not. 813 */ 814 public static boolean docChanged(String oldDoc, String newDoc) { 815 if (!HTMLReportGenerator.reportDocChanges) 816 return false; // Don't even count doc changes as changes 817 if (oldDoc == null && newDoc != null) 818 return true; 819 if (oldDoc != null && newDoc == null) 820 return true; 821 if (oldDoc != null && newDoc != null && oldDoc.compareTo(newDoc) != 0) 822 return true; 823 return false; 824 } 825 826 /** 827 * Decide if two elements changed where they were defined. 828 * 829 * @return 0 if both are null, or both are non-null and are the same. 830 * 1 if the oldInherit was null and newInherit is non-null. 831 * 2 if the oldInherit was non-null and newInherit is null. 832 * 3 if the oldInherit was non-null and newInherit is non-null 833 * and they differ. 834 */ 835 public static int changedInheritance(String oldInherit, String newInherit) { 836 if (oldInherit == null && newInherit == null) 837 return 0; 838 if (oldInherit == null && newInherit != null) 839 return 1; 840 if (oldInherit != null && newInherit == null) 841 return 2; 842 if (oldInherit.compareTo(newInherit) == 0) 843 return 0; 844 else 845 return 3; 846 } 847 848 /** 849 * Generate a link to the Javadoc page for the given method. 850 */ 851 public static String linkToClass(MethodAPI m, boolean useNew) { 852 String sig = m.getSignature(); 853 if (sig.compareTo("void") == 0) 854 sig = ""; 855 return linkToClass(m.inheritedFrom_, m.name_, sig, useNew); 856 } 857 858 /** 859 * Generate a link to the Javadoc page for the given field. 860 */ 861 public static String linkToClass(FieldAPI m, boolean useNew) { 862 return linkToClass(m.inheritedFrom_, m.name_, null, useNew); 863 } 864 865 /** 866 * Given the name of the class, generate a link to a relevant page. 867 * This was originally for inheritance changes, so the JDiff page could 868 * be a class changes page, or a section in a removed or added classes 869 * table. Since there was no easy way to tell which type the link 870 * should be, it is now just a link to the relevant Javadoc page. 871 */ 872 public static String linkToClass(String className, String memberName, 873 String memberType, boolean useNew) { 874 if (!useNew && HTMLReportGenerator.oldDocPrefix == null) { 875 return "<tt>" + className + "</tt>"; // No link possible 876 } 877 API api = oldAPI_; 878 String prefix = HTMLReportGenerator.oldDocPrefix; 879 if (useNew) { 880 api = newAPI_; 881 prefix = HTMLReportGenerator.newDocPrefix; 882 } 883 ClassAPI cls = (ClassAPI)api.classes_.get(className); 884 if (cls == null) { 885 if (useNew) 886 System.out.println("Warning: class " + className + " not found in the new API when creating Javadoc link"); 887 else 888 System.out.println("Warning: class " + className + " not found in the old API when creating Javadoc link"); 889 return "<tt>" + className + "</tt>"; 890 } 891 int clsIdx = className.indexOf(cls.name_); 892 if (clsIdx != -1) { 893 String pkgRef = className.substring(0, clsIdx); 894 pkgRef = pkgRef.replace('.', '/'); 895 String res = "<a href=\"" + prefix + pkgRef + cls.name_ + ".html#" + memberName; 896 if (memberType != null) 897 res += "(" + memberType + ")"; 898 res += "\" target=\"_top\">" + "<tt>" + cls.name_ + "</tt></a>"; 899 return res; 900 } 901 return "<tt>" + className + "</tt>"; 902 } 903 904 /** 905 * Return the number of methods which are locally defined. 906 */ 907 public int numLocalMethods(List methods) { 908 int res = 0; 909 Iterator iter = methods.iterator(); 910 while (iter.hasNext()) { 911 MethodAPI m = (MethodAPI)(iter.next()); 912 if (m.inheritedFrom_ == null) 913 res++; 914 } 915 return res; 916 } 917 918 /** 919 * Return the number of fields which are locally defined. 920 */ 921 public int numLocalFields(List fields) { 922 int res = 0; 923 Iterator iter = fields.iterator(); 924 while (iter.hasNext()) { 925 FieldAPI f = (FieldAPI)(iter.next()); 926 if (f.inheritedFrom_ == null) 927 res++; 928 } 929 return res; 930 } 931 932 /** Set to enable increased logging verbosity for debugging. */ 933 private boolean trace = false; 934 } 935