1 package jdiff; 2 3 import java.util.*; 4 import java.io.*; 5 import java.text.*; 6 7 /** 8 * Emit HTML based on the changes between two sets of APIs. 9 * 10 * See the file LICENSE.txt for copyright details. 11 * @author Matthew Doar, mdoar@pobox.com 12 */ 13 public class HTMLReportGenerator { 14 15 /** Default constructor. */ HTMLReportGenerator()16 public HTMLReportGenerator() { 17 } 18 19 /** The Comments object for existing comments. */ 20 private Comments existingComments_ = null; 21 22 /** 23 * The Comments object for freshly regenerated comments. 24 * This is populated during the generation of the report, 25 * and should be like existingComments_ but with unused comments 26 * marked as such, so that they can be commented out in XML when 27 * the new comments are written out to the comments file. 28 */ 29 private Comments newComments_ = null; 30 31 /** 32 * Accessor method for the freshly generated Comments object. 33 * The list of comments is sorted before the object is returned. 34 */ getNewComments()35 public Comments getNewComments() { 36 Collections.sort(newComments_.commentsList_); 37 return newComments_; 38 } 39 40 /** Generate the report. */ generate(APIComparator comp, Comments existingComments)41 public void generate(APIComparator comp, Comments existingComments) { 42 String fullReportFileName = reportFileName; 43 if (outputDir != null) 44 fullReportFileName = outputDir + JDiff.DIR_SEP + reportFileName; 45 System.out.println("JDiff: generating HTML report into the file '" + fullReportFileName + reportFileExt + "' and the subdirectory '" + fullReportFileName + "'"); 46 // May be null if no comments file exists yet 47 existingComments_ = existingComments; 48 // Where the new comments will be placed 49 newComments_ = new Comments(); 50 // Writing to multiple files, so make sure the subdirectory exists 51 File opdir = new File(fullReportFileName); 52 if (!opdir.mkdir() && !opdir.exists()) { 53 System.out.println("Error: could not create the subdirectory '" + fullReportFileName + "'"); 54 System.exit(3); 55 } 56 57 // Emit the documentation difference files 58 if (!Diff.noDocDiffs) { 59 // Documentation differences, one file per package 60 Diff.emitDocDiffs(fullReportFileName); 61 } 62 63 // This is the top-level summary file, first in the right hand frame 64 // or linked at the start to if no frames are used. 65 String changesSummaryName = fullReportFileName + JDiff.DIR_SEP + 66 reportFileName + "-summary" + reportFileExt; 67 apiDiff = comp.apiDiff; 68 try { 69 FileOutputStream fos = new FileOutputStream(changesSummaryName); 70 reportFile = new PrintWriter(fos); 71 writeStartHTMLHeader(); 72 // Write out the title in he HTML header 73 String oldAPIName = "Old API"; 74 if (apiDiff.oldAPIName_ != null) 75 oldAPIName = apiDiff.oldAPIName_; 76 String newAPIName = "New API"; 77 if (apiDiff.newAPIName_ != null) 78 newAPIName = apiDiff.newAPIName_; 79 if (windowTitle == null) 80 writeHTMLTitle("Android API Differences Report"); 81 else 82 writeHTMLTitle(windowTitle); 83 writeStyleSheetRef(); 84 writeText("</HEAD>"); 85 86 writeText("<body class=\"gc-documentation\">"); 87 88 // writeText("<div class=\"g-section g-tpl-180\">"); 89 // Add the nav bar for the summary page 90 writeNavigationBar(reportFileName + "-summary", null, null, 91 null, 0, true, 92 apiDiff.packagesRemoved.size() != 0, 93 apiDiff.packagesAdded.size() != 0, 94 apiDiff.packagesChanged.size() != 0); 95 96 writeText(" <div id=\"docTitleContainer\">"); 97 // Write the title in the body with some formatting 98 if (docTitle == null) { 99 //writeText("<center>"); 100 writeText("<h1>Android API Differences Report</h1>"); 101 } else { 102 writeText(" <h1>" + docTitle + "</h1>"); 103 } 104 105 writeText("<p>This report details the changes in the core Android framework API between two <a "); writeText("href=\"https://developer.android.com/guide/appendix/api-levels.html\" target=\"_top\">API Level</a> "); 106 writeText("specifications. It shows additions, modifications, and removals for packages, classes, methods, and fields. "); 107 writeText("The report also includes general statistics that characterize the extent and type of the differences.</p>"); 108 109 writeText("<p>This report is based a comparison of the Android API specifications "); 110 writeText("whose API Level identifiers are given in the upper-right corner of this page. It compares a "); 111 writeText("newer \"to\" API to an older \"from\" API, noting all changes relative to the "); 112 writeText("older API. So, for example, API elements marked as removed are no longer present in the \"to\" "); 113 writeText("API specification.</p>"); 114 115 writeText("<p>To navigate the report, use the \"Select a Diffs Index\" and \"Filter the Index\" "); 116 writeText("controls on the left. The report uses text formatting to indicate <em>interface names</em>, "); 117 writeText("<a href= ><code>links to reference documentation</code></a>, and <a href= >links to change "); 118 writeText("description</a>. The statistics are accessible from the \"Statistics\" link in the upper-right corner.</p>"); 119 120 writeText("<p>For more information about the Android framework API and SDK, "); 121 writeText("see the <a href=\"https://developer.android.com/index.html\" target=\"_top\">Android Developers site</a>.</p>"); 122 123 // Write the contents and the other files as well 124 writeReport(apiDiff); 125 writeHTMLFooter(); 126 reportFile.close(); 127 } catch(IOException e) { 128 System.out.println("IO Error while attempting to create " + changesSummaryName); 129 System.out.println("Error: " + e.getMessage()); 130 System.exit(1); 131 } 132 133 // Now generate all the other files for multiple frames. 134 // 135 // The top-level changes.html frames file where everything starts. 136 String tln = fullReportFileName + reportFileExt; 137 // The file for the top-left frame. 138 String tlf = fullReportFileName + JDiff.DIR_SEP + 139 "jdiff_topleftframe" + reportFileExt; 140 // The default file for the bottom-left frame is the one with the 141 // most information in it. 142 String allDiffsIndexName = fullReportFileName + JDiff.DIR_SEP + 143 "alldiffs_index"; 144 // Other indexes for the bottom-left frame. 145 String packagesIndexName = fullReportFileName + JDiff.DIR_SEP + 146 "packages_index"; 147 String classesIndexName = fullReportFileName + JDiff.DIR_SEP + 148 "classes_index"; 149 String constructorsIndexName = fullReportFileName + JDiff.DIR_SEP + 150 "constructors_index"; 151 String methodsIndexName = fullReportFileName + JDiff.DIR_SEP + 152 "methods_index"; 153 String fieldsIndexName = fullReportFileName + JDiff.DIR_SEP + 154 "fields_index"; 155 156 HTMLFiles hf = new HTMLFiles(this); 157 hf.emitTopLevelFile(tln, apiDiff); 158 hf.emitTopLeftFile(tlf); 159 hf.emitHelp(fullReportFileName, apiDiff); 160 hf.emitStylesheet(); 161 162 HTMLIndexes h = new HTMLIndexes(this); 163 h.emitAllBottomLeftFiles(packagesIndexName, classesIndexName, 164 constructorsIndexName, methodsIndexName, 165 fieldsIndexName, allDiffsIndexName, apiDiff); 166 167 if (doStats) { 168 // The file for the statistical report. 169 String sf = fullReportFileName + JDiff.DIR_SEP + 170 "jdiff_statistics" + reportFileExt; 171 HTMLStatistics stats = new HTMLStatistics(this); 172 stats.emitStatistics(sf, apiDiff); 173 } 174 } 175 176 /** 177 * Write the HTML report. 178 * 179 * The top section describes all the packages added (with links) and 180 * removed, and the changed packages section has links which takes you 181 * to a section for each package. This pattern continues for classes and 182 * constructors, methods and fields. 183 */ writeReport(APIDiff apiDiff)184 public void writeReport(APIDiff apiDiff) { 185 186 // Report packages which were removed in the new API 187 if (apiDiff.packagesRemoved.size() != 0) { 188 writeTableStart("Removed Packages", 2); 189 Iterator iter = apiDiff.packagesRemoved.iterator(); 190 while (iter.hasNext()) { 191 PackageAPI pkgAPI = (PackageAPI)(iter.next()); 192 String pkgName = pkgAPI.name_; 193 if (trace) System.out.println("Package " + pkgName + " was removed."); 194 writePackageTableEntry(pkgName, 0, pkgAPI.doc_, false); 195 } 196 writeTableEnd(); 197 } 198 199 // Report packages which were added in the new API 200 if (apiDiff.packagesAdded.size() != 0) { 201 writeTableStart("Added Packages", 2); 202 Iterator iter = apiDiff.packagesAdded.iterator(); 203 while (iter.hasNext()) { 204 PackageAPI pkgAPI = (PackageAPI)(iter.next()); 205 String pkgName = pkgAPI.name_; 206 if (trace) System.out.println("Package " + pkgName + " was added."); 207 writePackageTableEntry(pkgName, 1, pkgAPI.doc_, false); 208 } 209 writeTableEnd(); 210 } 211 212 // Report packages which were changed in the new API 213 if (apiDiff.packagesChanged.size() != 0) { 214 // Emit a table of changed packages, with links to the file 215 // for each package. 216 writeTableStart("Changed Packages", 3); 217 Iterator iter = apiDiff.packagesChanged.iterator(); 218 while (iter.hasNext()) { 219 PackageDiff pkgDiff = (PackageDiff)(iter.next()); 220 String pkgName = pkgDiff.name_; 221 if (trace) System.out.println("Package " + pkgName + " was changed."); 222 writePackageTableEntry(pkgName, 2, null, false); 223 } 224 writeTableEnd(); 225 writeText("<!-- End of API section -->"); 226 227 // Now emit a separate file for each changed package. 228 writeText("<!-- Start of packages section -->"); 229 PackageDiff[] pkgDiffs = new PackageDiff[apiDiff.packagesChanged.size()]; 230 pkgDiffs = (PackageDiff[])apiDiff.packagesChanged.toArray(pkgDiffs); 231 for (int i = 0; i < pkgDiffs.length; i++) { 232 reportChangedPackage(pkgDiffs, i); 233 } 234 } 235 writeText(" </div> "); 236 writeText(" <div id=\"footer\">"); 237 writeText(" <div id=\"copyright\">"); 238 writeText(" Except as noted, this content is licensed under "); 239 writeText(" <a href=\"https://creativecommons.org/licenses/by/2.5/\"> Creative Commons Attribution 2.5</a>."); 240 writeText(" For details and restrictions, see the <a href=\"https://developer.android.com/license.html\">Content License</a>."); 241 writeText(" </div>"); 242 writeText(" <div id=\"footerlinks\">"); 243 writeText(" <p>"); 244 writeText(" <a href=\"https://www.android.com/terms.html\">Site Terms of Service</a> -"); 245 writeText(" <a href=\"https://www.android.com/privacy.html\">Privacy Policy</a> -"); 246 writeText(" <a href=\"https://www.android.com/branding.html\">Brand Guidelines</a>"); 247 writeText(" </p>"); 248 writeText(" </div>"); 249 writeText(" </div> <!-- end footer -->"); 250 writeText(" </div><!-- end doc-content -->"); 251 writeText(" </div> <!-- end body-content --> "); 252 } 253 254 255 256 /** 257 * Write out the details of a changed package in a separate file. 258 */ reportChangedPackage(PackageDiff[] pkgDiffs, int pkgIndex)259 public void reportChangedPackage(PackageDiff[] pkgDiffs, int pkgIndex) { 260 PackageDiff pkgDiff = pkgDiffs[pkgIndex]; 261 String pkgName = pkgDiff.name_; 262 263 PrintWriter oldReportFile = null; 264 oldReportFile = reportFile; 265 String localReportFileName = null; 266 try { 267 // Prefix package files with pkg_ because there may be a class 268 // with the same name. 269 localReportFileName = reportFileName + JDiff.DIR_SEP + "pkg_" + pkgName + reportFileExt; 270 if (outputDir != null) 271 localReportFileName = outputDir + JDiff.DIR_SEP + localReportFileName; 272 FileOutputStream fos = new FileOutputStream(localReportFileName); 273 reportFile = new PrintWriter(fos); 274 writeStartHTMLHeader(); 275 writeHTMLTitle(pkgName); 276 writeStyleSheetRef(); 277 writeText("</HEAD>"); 278 writeText("<BODY>"); 279 } catch(IOException e) { 280 System.out.println("IO Error while attempting to create " + localReportFileName); 281 System.out.println("Error: "+ e.getMessage()); 282 System.exit(1); 283 } 284 285 String pkgRef = pkgName; 286 pkgRef = pkgRef.replace('.', '/'); 287 pkgRef = newDocPrefix + pkgRef + "/package-summary"; 288 // A link to the package in the new API 289 String linkedPkgName = "<A HREF=\"" + pkgRef + ".html\" target=\"_top\"><font size=\"+1\"><code>" + pkgName + "</code></font></A>"; 290 String prevPkgRef = null; 291 if (pkgIndex != 0) { 292 prevPkgRef = "pkg_" + pkgDiffs[pkgIndex-1].name_ + reportFileExt; 293 } 294 // Create the HTML link to the next package 295 String nextPkgRef = null; 296 if (pkgIndex < pkgDiffs.length - 1) { 297 nextPkgRef = "pkg_" + pkgDiffs[pkgIndex+1].name_ + reportFileExt; 298 } 299 300 writeSectionHeader("Package " + linkedPkgName, pkgName, 301 prevPkgRef, nextPkgRef, 302 null, 1, 303 pkgDiff.classesRemoved.size() != 0, 304 pkgDiff.classesAdded.size() != 0, 305 pkgDiff.classesChanged.size() != 0); 306 307 // Report changes in documentation 308 if (reportDocChanges && pkgDiff.documentationChange_ != null) { 309 String pkgDocRef = pkgName + "/package-summary"; 310 pkgDocRef = pkgDocRef.replace('.', '/'); 311 String oldPkgRef = pkgDocRef; 312 String newPkgRef = pkgDocRef; 313 if (oldDocPrefix != null) 314 oldPkgRef = oldDocPrefix + oldPkgRef; 315 else 316 oldPkgRef = null; 317 newPkgRef = newDocPrefix + newPkgRef; 318 if (oldPkgRef != null) 319 pkgDiff.documentationChange_ += "<A HREF=\"" + oldPkgRef + 320 ".html#package_description\" target=\"_self\"><code>old</code></A> to "; 321 else 322 pkgDiff.documentationChange_ += "<font size=\"+1\"><code>old</code></font> to "; 323 pkgDiff.documentationChange_ += "<A HREF=\"" + newPkgRef + 324 ".html#package_description\" target=\"_self\"><code>new</code></A>. "; 325 writeText(pkgDiff.documentationChange_); 326 } 327 328 // Report classes which were removed in the new API 329 if (pkgDiff.classesRemoved.size() != 0) { 330 // Determine the title for this section 331 boolean hasClasses = false; 332 boolean hasInterfaces = false; 333 Iterator iter = pkgDiff.classesRemoved.iterator(); 334 while (iter.hasNext()) { 335 ClassAPI classAPI = (ClassAPI)(iter.next()); 336 if (classAPI.isInterface_) 337 hasInterfaces = true; 338 else 339 hasClasses = true; 340 } 341 if (hasInterfaces && hasClasses) 342 writeTableStart("Removed Classes and Interfaces", 2); 343 else if (!hasInterfaces && hasClasses) 344 writeTableStart("Removed Classes", 2); 345 else if (hasInterfaces && !hasClasses) 346 writeTableStart("Removed Interfaces", 2); 347 // Emit the table entries 348 iter = pkgDiff.classesRemoved.iterator(); 349 while (iter.hasNext()) { 350 ClassAPI classAPI = (ClassAPI)(iter.next()); 351 String className = classAPI.name_; 352 if (trace) System.out.println("Class/Interface " + className + " was removed."); 353 writeClassTableEntry(pkgName, className, 0, classAPI.isInterface_, classAPI.doc_, false); 354 } 355 writeTableEnd(); 356 } 357 358 // Report classes which were added in the new API 359 if (pkgDiff.classesAdded.size() != 0) { 360 // Determine the title for this section 361 boolean hasClasses = false; 362 boolean hasInterfaces = false; 363 Iterator iter = pkgDiff.classesAdded.iterator(); 364 while (iter.hasNext()) { 365 ClassAPI classAPI = (ClassAPI)(iter.next()); 366 if (classAPI.isInterface_) 367 hasInterfaces = true; 368 else 369 hasClasses = true; 370 } 371 if (hasInterfaces && hasClasses) 372 writeTableStart("Added Classes and Interfaces", 2); 373 else if (!hasInterfaces && hasClasses) 374 writeTableStart("Added Classes", 2); 375 else if (hasInterfaces && !hasClasses) 376 writeTableStart("Added Interfaces", 2); 377 // Emit the table entries 378 iter = pkgDiff.classesAdded.iterator(); 379 while (iter.hasNext()) { 380 ClassAPI classAPI = (ClassAPI)(iter.next()); 381 String className = classAPI.name_; 382 if (trace) System.out.println("Class/Interface " + className + " was added."); 383 writeClassTableEntry(pkgName, className, 1, classAPI.isInterface_, classAPI.doc_, false); 384 } 385 writeTableEnd(); 386 } 387 388 // Report classes which were changed in the new API 389 if (pkgDiff.classesChanged.size() != 0) { 390 // Determine the title for this section 391 boolean hasClasses = false; 392 boolean hasInterfaces = false; 393 Iterator iter = pkgDiff.classesChanged.iterator(); 394 while (iter.hasNext()) { 395 ClassDiff classDiff = (ClassDiff)(iter.next()); 396 if (classDiff.isInterface_) 397 hasInterfaces = true; 398 else 399 hasClasses = true; 400 } 401 if (hasInterfaces && hasClasses) 402 writeTableStart("Changed Classes and Interfaces", 2); 403 else if (!hasInterfaces && hasClasses) 404 writeTableStart("Changed Classes", 2); 405 else if (hasInterfaces && !hasClasses) 406 writeTableStart("Changed Interfaces", 2); 407 // Emit a table of changed classes, with links to the file 408 // for each class. 409 iter = pkgDiff.classesChanged.iterator(); 410 while (iter.hasNext()) { 411 ClassDiff classDiff = (ClassDiff)(iter.next()); 412 String className = classDiff.name_; 413 if (trace) System.out.println("Package " + pkgDiff.name_ + ", class/Interface " + className + " was changed."); 414 writeClassTableEntry(pkgName, className, 2, classDiff.isInterface_, null, false); 415 } 416 writeTableEnd(); 417 // Now emit a separate file for each changed class and interface. 418 ClassDiff[] classDiffs = new ClassDiff[pkgDiff.classesChanged.size()]; 419 classDiffs = (ClassDiff[])pkgDiff.classesChanged.toArray(classDiffs); 420 for (int k = 0; k < classDiffs.length; k++) { 421 reportChangedClass(pkgName, classDiffs, k); 422 } 423 } 424 425 writeSectionFooter(pkgName, prevPkgRef, nextPkgRef, null, 1); 426 writeHTMLFooter(); 427 reportFile.close(); 428 reportFile = oldReportFile; 429 } 430 431 /** 432 * Write out the details of a changed class in a separate file. 433 */ reportChangedClass(String pkgName, ClassDiff[] classDiffs, int classIndex)434 public void reportChangedClass(String pkgName, ClassDiff[] classDiffs, int classIndex) { 435 ClassDiff classDiff = classDiffs[classIndex]; 436 String className = classDiff.name_; 437 438 PrintWriter oldReportFile = null; 439 oldReportFile = reportFile; 440 String localReportFileName = null; 441 try { 442 localReportFileName = reportFileName + JDiff.DIR_SEP + pkgName + "." + className + reportFileExt; 443 if (outputDir != null) 444 localReportFileName = outputDir + JDiff.DIR_SEP + localReportFileName; 445 FileOutputStream fos = new FileOutputStream(localReportFileName); 446 reportFile = new PrintWriter(fos); 447 writeStartHTMLHeader(); 448 writeHTMLTitle(pkgName + "." + className); 449 writeStyleSheetRef(); 450 writeText("</HEAD>"); 451 writeText("<BODY>"); 452 } catch(IOException e) { 453 System.out.println("IO Error while attempting to create " + localReportFileName); 454 System.out.println("Error: "+ e.getMessage()); 455 System.exit(1); 456 } 457 458 String classRef = pkgName + "." + className; 459 classRef = classRef.replace('.', '/'); 460 if (className.indexOf('.') != -1) { 461 classRef = pkgName + "."; 462 classRef = classRef.replace('.', '/'); 463 classRef = newDocPrefix + classRef + className; 464 } else { 465 classRef = newDocPrefix + classRef; 466 } 467 // A link to the class in the new API 468 String linkedClassName = "<A HREF=\"" + classRef + ".html\" target=\"_top\"><font size=\"+2\"><code>" + className + "</code></font></A>"; 469 String lcn = pkgName + "." + linkedClassName; 470 //Links to the previous and next classes 471 String prevClassRef = null; 472 if (classIndex != 0) { 473 prevClassRef = pkgName + "." + classDiffs[classIndex-1].name_ + reportFileExt; 474 } 475 // Create the HTML link to the next package 476 String nextClassRef = null; 477 if (classIndex < classDiffs.length - 1) { 478 nextClassRef = pkgName + "." + classDiffs[classIndex+1].name_ + reportFileExt; 479 } 480 481 if (classDiff.isInterface_) 482 lcn = "Interface " + lcn; 483 else 484 lcn = "Class " + lcn; 485 boolean hasCtors = classDiff.ctorsRemoved.size() != 0 || 486 classDiff.ctorsAdded.size() != 0 || 487 classDiff.ctorsChanged.size() != 0; 488 boolean hasMethods = classDiff.methodsRemoved.size() != 0 || 489 classDiff.methodsAdded.size() != 0 || 490 classDiff.methodsChanged.size() != 0; 491 boolean hasFields = classDiff.fieldsRemoved.size() != 0 || 492 classDiff.fieldsAdded.size() != 0 || 493 classDiff.fieldsChanged.size() != 0; 494 writeSectionHeader(lcn, pkgName, prevClassRef, nextClassRef, 495 className, 2, 496 hasCtors, hasMethods, hasFields); 497 498 if (classDiff.inheritanceChange_ != null) 499 writeText("<p><font xsize=\"+1\">" + classDiff.inheritanceChange_ + "</font>"); 500 501 // Report changes in documentation 502 if (reportDocChanges && classDiff.documentationChange_ != null) { 503 String oldClassRef = null; 504 if (oldDocPrefix != null) { 505 oldClassRef = pkgName + "." + className; 506 oldClassRef = oldClassRef.replace('.', '/'); 507 if (className.indexOf('.') != -1) { 508 oldClassRef = pkgName + "."; 509 oldClassRef = oldClassRef.replace('.', '/'); 510 oldClassRef = oldDocPrefix + oldClassRef + className; 511 } else { 512 oldClassRef = oldDocPrefix + oldClassRef; 513 } 514 } 515 if (oldDocPrefix != null) 516 classDiff.documentationChange_ += "<A HREF=\"" + oldClassRef + 517 ".html\" target=\"_self\"><code>old</code></A> to "; 518 else 519 classDiff.documentationChange_ += "<font size=\"+1\"><code>old</code></font> to "; 520 classDiff.documentationChange_ += "<A HREF=\"" + classRef + 521 ".html\" target=\"_self\"><code>new</code></A>. "; 522 writeText(classDiff.documentationChange_); 523 } 524 525 if (classDiff.modifiersChange_ != null) 526 writeText("<p>" + classDiff.modifiersChange_); 527 528 reportAllCtors(pkgName, classDiff); 529 reportAllMethods(pkgName, classDiff); 530 reportAllFields(pkgName, classDiff); 531 532 writeSectionFooter(pkgName, prevClassRef, nextClassRef, className, 2); 533 writeHTMLFooter(); 534 reportFile.close(); 535 reportFile = oldReportFile; 536 } 537 538 /** 539 * Write out the details of constructors in a class. 540 */ reportAllCtors(String pkgName, ClassDiff classDiff)541 public void reportAllCtors(String pkgName, ClassDiff classDiff) { 542 String className = classDiff.name_; 543 writeText("<a NAME=\"constructors\"></a>"); // Named anchor 544 // Report ctors which were removed in the new API 545 if (classDiff.ctorsRemoved.size() != 0) { 546 writeTableStart("Removed Constructors", 2); 547 Iterator iter = classDiff.ctorsRemoved.iterator(); 548 while (iter.hasNext()) { 549 ConstructorAPI ctorAPI = (ConstructorAPI)(iter.next()); 550 String ctorType = ctorAPI.getSignature(); 551 if (ctorType.compareTo("void") == 0) 552 ctorType = ""; 553 String id = className + "(" + ctorType + ")"; 554 if (trace) System.out.println("Constructor " + id + " was removed."); 555 writeCtorTableEntry(pkgName, className, ctorType, 0, ctorAPI.doc_, false); 556 } 557 writeTableEnd(); 558 } 559 560 // Report ctors which were added in the new API 561 if (classDiff.ctorsAdded.size() != 0) { 562 writeTableStart("Added Constructors", 2); 563 Iterator iter = classDiff.ctorsAdded.iterator(); 564 while (iter.hasNext()) { 565 ConstructorAPI ctorAPI = (ConstructorAPI)(iter.next()); 566 String ctorType = ctorAPI.getSignature(); 567 if (ctorType.compareTo("void") == 0) 568 ctorType = ""; 569 String id = className + "(" + ctorType + ")"; 570 if (trace) System.out.println("Constructor " + id + " was added."); 571 writeCtorTableEntry(pkgName, className, ctorType, 1, ctorAPI.doc_, false); 572 } 573 writeTableEnd(); 574 } 575 576 // Report ctors which were changed in the new API 577 if (classDiff.ctorsChanged.size() != 0) { 578 // Emit a table of changed classes, with links to the section 579 // for each class. 580 writeTableStart("Changed Constructors", 3); 581 Iterator iter = classDiff.ctorsChanged.iterator(); 582 while (iter.hasNext()) { 583 MemberDiff memberDiff = (MemberDiff)(iter.next()); 584 if (trace) System.out.println("Constructor for " + className + 585 " was changed from " + memberDiff.oldType_ + " to " + 586 memberDiff.newType_); 587 writeCtorChangedTableEntry(pkgName, className, memberDiff); 588 } 589 writeTableEnd(); 590 } 591 } 592 593 /** 594 * Write out the details of methods in a class. 595 */ reportAllMethods(String pkgName, ClassDiff classDiff)596 public void reportAllMethods(String pkgName, ClassDiff classDiff) { 597 writeText("<a NAME=\"methods\"></a>"); // Named anchor 598 String className = classDiff.name_; 599 // Report methods which were removed in the new API 600 if (classDiff.methodsRemoved.size() != 0) { 601 writeTableStart("Removed Methods", 2); 602 Iterator iter = classDiff.methodsRemoved.iterator(); 603 while (iter.hasNext()) { 604 MethodAPI methodAPI = (MethodAPI)(iter.next()); 605 String methodName = methodAPI.name_ + "(" + methodAPI.getSignature() + ")"; 606 if (trace) System.out.println("Method " + methodName + " was removed."); 607 writeMethodTableEntry(pkgName, className, methodAPI, 0, methodAPI.doc_, false); 608 } 609 writeTableEnd(); 610 } 611 612 // Report methods which were added in the new API 613 if (classDiff.methodsAdded.size() != 0) { 614 writeTableStart("Added Methods", 2); 615 Iterator iter = classDiff.methodsAdded.iterator(); 616 while (iter.hasNext()) { 617 MethodAPI methodAPI = (MethodAPI)(iter.next()); 618 String methodName = methodAPI.name_ + "(" + methodAPI.getSignature() + ")"; 619 if (trace) System.out.println("Method " + methodName + " was added."); 620 writeMethodTableEntry(pkgName, className, methodAPI, 1, methodAPI.doc_, false); 621 } 622 writeTableEnd(); 623 } 624 625 // Report methods which were changed in the new API 626 if (classDiff.methodsChanged.size() != 0) { 627 // Emit a table of changed methods. 628 writeTableStart("Changed Methods", 3); 629 Iterator iter = classDiff.methodsChanged.iterator(); 630 while (iter.hasNext()) { 631 MemberDiff memberDiff = (MemberDiff)(iter.next()); 632 if (trace) System.out.println("Method " + memberDiff.name_ + 633 " was changed."); 634 writeMethodChangedTableEntry(pkgName, className, memberDiff); 635 } 636 writeTableEnd(); 637 } 638 } 639 640 /** 641 * Write out the details of fields in a class. 642 */ reportAllFields(String pkgName, ClassDiff classDiff)643 public void reportAllFields(String pkgName, ClassDiff classDiff) { 644 writeText("<a NAME=\"fields\"></a>"); // Named anchor 645 String className = classDiff.name_; 646 // Report fields which were removed in the new API 647 if (classDiff.fieldsRemoved.size() != 0) { 648 writeTableStart("Removed Fields", 2); 649 Iterator iter = classDiff.fieldsRemoved.iterator(); 650 while (iter.hasNext()) { 651 FieldAPI fieldAPI = (FieldAPI)(iter.next()); 652 String fieldName = fieldAPI.name_; 653 if (trace) System.out.println("Field " + fieldName + " was removed."); 654 writeFieldTableEntry(pkgName, className, fieldAPI, 0, fieldAPI.doc_, false); 655 } 656 writeTableEnd(); 657 } 658 659 // Report fields which were added in the new API 660 if (classDiff.fieldsAdded.size() != 0) { 661 writeTableStart("Added Fields", 2); 662 Iterator iter = classDiff.fieldsAdded.iterator(); 663 while (iter.hasNext()) { 664 FieldAPI fieldAPI = (FieldAPI)(iter.next()); 665 String fieldName = fieldAPI.name_; 666 if (trace) System.out.println("Field " + fieldName + " was added."); 667 writeFieldTableEntry(pkgName, className, fieldAPI, 1, fieldAPI.doc_, false); 668 } 669 writeTableEnd(); 670 } 671 672 // Report fields which were changed in the new API 673 if (classDiff.fieldsChanged.size() != 0) { 674 // Emit a table of changed classes, with links to the section 675 // for each class. 676 writeTableStart("Changed Fields", 3); 677 Iterator iter = classDiff.fieldsChanged.iterator(); 678 while (iter.hasNext()) { 679 MemberDiff memberDiff = (MemberDiff)(iter.next()); 680 if (trace) System.out.println("Field " + pkgName + "." + className + "." + memberDiff.name_ + " was changed from " + memberDiff.oldType_ + " to " + memberDiff.newType_); 681 writeFieldChangedTableEntry(pkgName, className, memberDiff); 682 } 683 writeTableEnd(); 684 } 685 686 } 687 688 /** 689 * Write the start of the HTML header, together with the current 690 * date and time in an HTML comment. 691 */ writeStartHTMLHeaderWithDate()692 public void writeStartHTMLHeaderWithDate() { 693 writeStartHTMLHeader(true); 694 } 695 696 /** Write the start of the HTML header. */ writeStartHTMLHeader()697 public void writeStartHTMLHeader() { 698 writeStartHTMLHeader(false); 699 } 700 701 /** Write the start of the HTML header. */ writeStartHTMLHeader(boolean addDate)702 public void writeStartHTMLHeader(boolean addDate) { 703 writeText("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"https://www.w3.org/TR/html4/strict.dtd\">"); 704 writeText("<HTML style=\"overflow:auto;\">"); 705 writeText("<HEAD>"); 706 writeText("<meta name=\"generator\" content=\"JDiff v" + JDiff.version + "\">"); 707 writeText("<!-- Generated by the JDiff Javadoc doclet -->"); 708 writeText("<!-- (" + JDiff.jDiffLocation + ") -->"); 709 if (addDate) 710 writeText("<!-- on " + new Date() + " -->"); 711 writeText("<meta name=\"description\" content=\"" + JDiff.jDiffDescription + "\">"); 712 writeText("<meta name=\"keywords\" content=\"" + JDiff.jDiffKeywords + "\">"); 713 } 714 715 /** Write the HTML title */ writeHTMLTitle(String title)716 public void writeHTMLTitle(String title) { 717 writeText("<TITLE>"); 718 writeText(title); 719 writeText("</TITLE>"); 720 } 721 722 /** 723 * Write the HTML style sheet reference for files in the subdirectory. 724 */ writeStyleSheetRef()725 public void writeStyleSheetRef() { 726 writeStyleSheetRef(false); 727 } 728 729 /** 730 * Write the HTML style sheet reference. If inSameDir is set, don't add 731 * "../" to the location. 732 */ 733 writeStyleSheetRef(boolean inSameDir)734 public void writeStyleSheetRef(boolean inSameDir) { 735 if (inSameDir) { 736 writeText("<link href=\"../../../assets/android-developer-docs.css\" rel=\"stylesheet\" type=\"text/css\" />"); 737 writeText("<link href=\"stylesheet-jdiff.css\" rel=\"stylesheet\" type=\"text/css\" />"); 738 writeText("<noscript>"); 739 writeText("<style type=\"text/css\">"); 740 writeText("body{overflow:auto;}"); 741 writeText("#body-content{position:relative; top:0;}"); 742 writeText("#doc-content{overflow:visible;border-left:3px solid #666;}"); 743 writeText("#side-nav{padding:0;}"); 744 writeText("#side-nav .toggle-list ul {display:block;}"); 745 writeText("#resize-packages-nav{border-bottom:3px solid #666;}"); 746 writeText("</style>"); 747 writeText("</noscript>"); 748 writeText("<style type=\"text/css\">"); 749 writeText("</style>"); 750 } else { 751 writeText("<link href=\"../../../../assets/android-developer-docs.css\" rel=\"stylesheet\" type=\"text/css\" />"); 752 writeText("<link href=\"../stylesheet-jdiff.css\" rel=\"stylesheet\" type=\"text/css\" />"); 753 writeText("<noscript>"); 754 writeText("<style type=\"text/css\">"); 755 writeText("body{overflow:auto;}"); 756 writeText("#body-content{position:relative; top:0;}"); 757 writeText("#doc-content{overflow:visible;border-left:3px solid #666;}"); 758 writeText("#side-nav{padding:0;}"); 759 writeText("#side-nav .toggle-list ul {display:block;}"); 760 writeText("#resize-packages-nav{border-bottom:3px solid #666;}"); 761 writeText("</style>"); 762 writeText("</noscript>"); 763 writeText("<style type=\"text/css\">"); 764 writeText("</style>"); 765 } 766 } 767 768 /** Write the HTML footer. */ writeHTMLFooter()769 public void writeHTMLFooter() { 770 writeText("<script src=\"https://www.google-analytics.com/ga.js\" type=\"text/javascript\">"); 771 writeText("</script>"); 772 writeText("<script type=\"text/javascript\">"); 773 writeText(" try {"); 774 writeText(" var pageTracker = _gat._getTracker(\"UA-5831155-1\");"); 775 writeText(" pageTracker._setAllowAnchor(true);"); 776 writeText(" pageTracker._initData();"); 777 writeText(" pageTracker._trackPageview();"); 778 writeText(" } catch(e) {}"); 779 writeText("</script>"); 780 writeText("</BODY>"); 781 writeText("</HTML>"); 782 } 783 784 /** 785 * Write a section header, which includes a navigation bar. 786 * 787 * @param title Title of the header. Contains any links necessary. 788 * @param packageName The name of the current package, with no slashes or 789 * links in it. May be null 790 * @param prevElemLink An HTML link to the previous element (a package or 791 * class). May be null. 792 * @param nextElemLink An HTML link to the next element (a package or 793 * class). May be null. 794 * @param className The name of the current class, with no slashes or 795 * links in it. May be null. 796 * @param level 0 = overview, 1 = package, 2 = class/interface 797 */ writeSectionHeader(String title, String packageName, String prevElemLink, String nextElemLink, String className, int level, boolean hasRemovals, boolean hasAdditions, boolean hasChanges)798 public void writeSectionHeader(String title, String packageName, 799 String prevElemLink, String nextElemLink, 800 String className, int level, 801 boolean hasRemovals, 802 boolean hasAdditions, 803 boolean hasChanges) { 804 writeNavigationBar(packageName, prevElemLink, nextElemLink, 805 className, level, true, 806 hasRemovals, hasAdditions, hasChanges); 807 if (level != 0) { 808 reportFile.println("<H2>"); 809 reportFile.println(title); 810 reportFile.println("</H2>"); 811 } 812 } 813 814 /** 815 * Write a section footer, which includes a navigation bar. 816 * 817 * @param packageName The name of the current package, with no slashes or 818 * links in it. may be null 819 * @param prevElemLink An HTML link to the previous element (a package or 820 * class). May be null. 821 * @param nextElemLink An HTML link to the next element (a package or 822 * class). May be null. 823 * @param className The name of the current class, with no slashes or 824 * links in it. May be null 825 * @param level 0 = overview, 1 = package, 2 = class/interface 826 */ writeSectionFooter(String packageName, String prevElemLink, String nextElemLink, String className, int level)827 public void writeSectionFooter(String packageName, 828 String prevElemLink, String nextElemLink, 829 String className, int level) { 830 writeText(" </div> "); 831 writeText(" <div id=\"footer\">"); 832 writeText(" <div id=\"copyright\">"); 833 writeText(" Except as noted, this content is licensed under "); 834 writeText(" <a href=\"https://creativecommons.org/licenses/by/2.5/\"> Creative Commons Attribution 2.5</a>."); 835 writeText(" For details and restrictions, see the <a href=\"https://developer.android.com/license.html\">Content License</a>."); 836 writeText(" </div>"); 837 writeText(" <div id=\"footerlinks\">"); 838 writeText(" <p>"); 839 writeText(" <a href=\"https://www.android.com/terms.html\">Site Terms of Service</a> -"); 840 writeText(" <a href=\"https://www.android.com/privacy.html\">Privacy Policy</a> -"); 841 writeText(" <a href=\"https://www.android.com/branding.html\">Brand Guidelines</a>"); 842 writeText(" </p>"); 843 writeText(" </div>"); 844 writeText(" </div> <!-- end footer -->"); 845 writeText(" </div><!-- end doc-content -->"); 846 writeText(" </div> <!-- end body-content --> "); 847 848 } 849 850 /** 851 * Write a navigation bar section header. 852 * 853 * @param pkgName The name of the current package, with no slashes or 854 * links in it. 855 * @param prevElemLink An HTML link to the previous element (a package or 856 * class). May be null. 857 * @param nextElemLink An HTML link to the next element (a package or 858 * class). May be null. 859 * @param className The name of the current class, with no slashes or 860 * links in it. May be null. 861 * @param level 0 = overview, 1 = package, 2 = class/interface 862 */ 863 writeNavigationBar(String pkgName, String prevElemLink, String nextElemLink, String className, int level, boolean upperNavigationBar, boolean hasRemovals, boolean hasAdditions, boolean hasChanges)864 public void writeNavigationBar(String pkgName, 865 String prevElemLink, String nextElemLink, 866 String className, int level, 867 boolean upperNavigationBar, 868 boolean hasRemovals, boolean hasAdditions, 869 boolean hasChanges) { 870 871 String oldAPIName = "Old API"; 872 if (apiDiff.oldAPIName_ != null) 873 oldAPIName = apiDiff.oldAPIName_; 874 String newAPIName = "New API"; 875 if (apiDiff.newAPIName_ != null) 876 newAPIName = apiDiff.newAPIName_; 877 878 SimpleDateFormat formatter 879 = new SimpleDateFormat ("yyyy.MM.dd HH:mm"); 880 Date day = new Date(); 881 882 reportFile.println("<!-- Start of nav bar -->"); 883 884 reportFile.println("<a name=\"top\"></a>"); 885 reportFile.println("<div id=\"header\" style=\"margin-bottom:0;padding-bottom:0;\">"); 886 reportFile.println("<div id=\"headerLeft\">"); 887 reportFile.println("<a href=\"../../../../index.html\" tabindex=\"-1\" target=\"_top\"><img src=\"../../../../assets/images/bg_logo.png\" alt=\"Android Developers\" /></a>"); 888 reportFile.println("</div>"); 889 reportFile.println(" <div id=\"headerRight\">"); 890 reportFile.println(" <div id=\"headerLinks\">"); 891 reportFile.println("<!-- <img src=\"/assets/images/icon_world.jpg\" alt=\"\" /> -->"); 892 reportFile.println("<span class=\"text\">"); 893 reportFile.println("<!-- <a href=\"#\">English</a> | -->"); 894 reportFile.println("<nobr><a href=\"https://developer.android.com\" target=\"_top\">Android Developers</a> | <a href=\"https://www.android.com\" target=\"_top\">Android.com</a></nobr>"); 895 reportFile.println("</span>"); 896 reportFile.println("</div>"); 897 reportFile.println(" <div class=\"and-diff-id\" style=\"margin-top:6px;margin-right:8px;\">"); 898 reportFile.println(" <table class=\"diffspectable\">"); 899 reportFile.println(" <tr>"); 900 reportFile.println(" <td colspan=\"2\" class=\"diffspechead\">API Diff Specification</td>"); 901 reportFile.println(" </tr>"); 902 reportFile.println(" <tr>"); 903 reportFile.println(" <td class=\"diffspec\" style=\"padding-top:.25em\">To Level:</td>"); 904 reportFile.println(" <td class=\"diffvaluenew\" style=\"padding-top:.25em\">" + newAPIName + "</td>"); 905 reportFile.println(" </tr>"); 906 reportFile.println(" <tr>"); 907 reportFile.println(" <td class=\"diffspec\">From Level:</td>"); 908 reportFile.println(" <td class=\"diffvalueold\">" + oldAPIName + "</td>"); 909 reportFile.println(" </tr>"); 910 reportFile.println(" <tr>"); 911 reportFile.println(" <td class=\"diffspec\">Generated</td>"); 912 reportFile.println(" <td class=\"diffvalue\">" + formatter.format( day ) + "</td>"); 913 reportFile.println(" </tr>"); 914 reportFile.println(" </table>"); 915 reportFile.println(" </div><!-- End and-diff-id -->"); 916 if (doStats) { 917 reportFile.println(" <div class=\"and-diff-id\" style=\"margin-right:8px;\">"); 918 reportFile.println(" <table class=\"diffspectable\">"); 919 reportFile.println(" <tr>"); 920 reportFile.println(" <td class=\"diffspec\" colspan=\"2\"><a href=\"jdiff_statistics.html\">Statistics</a>"); 921 reportFile.println(" </tr>"); 922 reportFile.println(" </table>"); 923 reportFile.println(" </div> <!-- End and-diff-id -->"); 924 } 925 reportFile.println(" </div> <!-- End headerRight -->"); 926 reportFile.println(" </div> <!-- End header -->"); 927 reportFile.println("<div id=\"body-content\" xstyle=\"padding:12px;padding-right:18px;\">"); 928 reportFile.println("<div id=\"doc-content\" style=\"position:relative;\">"); 929 reportFile.println("<div id=\"mainBodyFluid\">"); 930 } 931 932 /** Write the start of a table. */ writeTableStart(String title, int colSpan)933 public void writeTableStart(String title, int colSpan) { 934 reportFile.println("<p>"); 935 // Assumes that the first word of the title categorizes the table type 936 // and that there is a space after the first word in the title 937 int idx = title.indexOf(' '); 938 String namedAnchor = title.substring(0, idx); 939 reportFile.println("<a NAME=\"" + namedAnchor + "\"></a>"); // Named anchor 940 reportFile.println("<TABLE summary=\"" + title+ "\" WIDTH=\"100%\">"); 941 reportFile.println("<TR>"); 942 reportFile.print(" <TH VALIGN=\"TOP\" COLSPAN=" + colSpan + ">"); 943 reportFile.println(title + "</FONT></TD>"); 944 reportFile.println("</TH>"); 945 } 946 947 /** 948 * If a class or package name is considered to be too long for convenient 949 * display, insert <br> in the middle of it at a period. 950 */ makeTwoRows(String name)951 public String makeTwoRows(String name) { 952 if (name.length() < 30) 953 return name; 954 int idx = name.indexOf(".", 20); 955 if (idx == -1) 956 return name; 957 int len = name.length(); 958 String res = name.substring(0, idx+1) + "<br>" + name.substring(idx+1, len); 959 return res; 960 } 961 962 /** 963 * Write a table entry for a package, with support for links to Javadoc 964 * for removed packages. 965 * 966 * linkType: 0 - no link by default, 1 = link to Javadoc HTML file, 2 = link to JDiff file 967 */ writePackageTableEntry(String pkgName, int linkType, String possibleComment, boolean useOld)968 public void writePackageTableEntry(String pkgName, int linkType, 969 String possibleComment, boolean useOld) { 970 if (!useOld) { 971 reportFile.println("<TR BGCOLOR=\"" + bgcolor + "\" CLASS=\"TableRowColor\">"); 972 reportFile.println(" <TD VALIGN=\"TOP\" WIDTH=\"25%\">"); 973 reportFile.println(" <A NAME=\"" + pkgName + "\"></A>"); // Named anchor 974 } 975 //String shownPkgName = makeTwoRows(pkgName); 976 if (linkType == 0) { 977 if (oldDocPrefix == null) { 978 // No link 979 reportFile.print(" " + pkgName); 980 } else { 981 // Call this method again but this time to emit a link to the 982 // old program element. 983 writePackageTableEntry(pkgName, 1, possibleComment, true); 984 } 985 } else if (linkType == 1) { 986 // Link to HTML file for the package 987 String pkgRef = pkgName; 988 pkgRef = pkgRef.replace('.', '/'); 989 if (useOld) 990 pkgRef = oldDocPrefix + pkgRef + "/package-summary"; 991 else 992 pkgRef = newDocPrefix + pkgRef + "/package-summary"; 993 reportFile.println(" <nobr><A HREF=\"" + pkgRef + ".html\" target=\"_top\"><code>" + pkgName + "</code></A></nobr>"); 994 } else if (linkType == 2) { 995 reportFile.println(" <nobr><A HREF=\"pkg_" + pkgName + reportFileExt + "\">" + pkgName + "</A></nobr>"); 996 } 997 if (!useOld) { 998 reportFile.println(" </TD>"); 999 emitComment(pkgName, possibleComment, linkType); 1000 reportFile.println("</TR>"); 1001 } 1002 } 1003 1004 /** 1005 * Write a table entry for a class or interface. 1006 * 1007 * linkType: 0 - no link by default, 1 = link to Javadoc HTML file, 2 = link to JDiff file 1008 */ writeClassTableEntry(String pkgName, String className, int linkType, boolean isInterface, String possibleComment, boolean useOld)1009 public void writeClassTableEntry(String pkgName, String className, 1010 int linkType, boolean isInterface, 1011 String possibleComment, boolean useOld) { 1012 if (!useOld) { 1013 reportFile.println("<TR BGCOLOR=\"" + bgcolor + "\" CLASS=\"TableRowColor\">"); 1014 reportFile.println(" <TD VALIGN=\"TOP\" WIDTH=\"25%\">"); 1015 reportFile.println(" <A NAME=\"" + className + "\"></A>"); // Named anchor 1016 } 1017 String fqName = pkgName + "." + className; 1018 String shownClassName = makeTwoRows(className); 1019 if (linkType == 0) { 1020 if (oldDocPrefix == null) { 1021 // No link 1022 if (isInterface) 1023 reportFile.println(" <I>" + shownClassName + "</I>"); 1024 else 1025 reportFile.println(" " + shownClassName); 1026 } else { 1027 writeClassTableEntry(pkgName, className, 1028 1, isInterface, 1029 possibleComment, true); 1030 } 1031 } else if (linkType == 1) { 1032 // Link to HTML file for the class 1033 String classRef = fqName; 1034 // Deal with inner classes 1035 if (className.indexOf('.') != -1) { 1036 classRef = pkgName + "."; 1037 classRef = classRef.replace('.', '/'); 1038 if (useOld) 1039 classRef = oldDocPrefix + classRef + className; 1040 else 1041 classRef = newDocPrefix + classRef + className; 1042 } else { 1043 classRef = classRef.replace('.', '/'); 1044 if (useOld) 1045 classRef = oldDocPrefix + classRef; 1046 else 1047 classRef = newDocPrefix + classRef; 1048 } 1049 reportFile.print(" <nobr><A HREF=\"" + classRef + ".html\" target=\"_top\"><code>"); 1050 if (isInterface) 1051 reportFile.print("<I>" + shownClassName + "</I>"); 1052 else 1053 reportFile.print(shownClassName); 1054 reportFile.println("</code></A></nobr>"); 1055 } else if (linkType == 2) { 1056 reportFile.print(" <nobr><A HREF=\"" + fqName + reportFileExt + "\">"); 1057 if (isInterface) 1058 reportFile.print("<I>" + shownClassName + "</I>"); 1059 else 1060 reportFile.print(shownClassName); 1061 reportFile.println("</A></nobr>"); 1062 } 1063 if (!useOld) { 1064 reportFile.println(" </TD>"); 1065 emitComment(fqName, possibleComment, linkType); 1066 reportFile.println("</TR>"); 1067 } 1068 } 1069 1070 /** 1071 * Write a table entry for a constructor. 1072 * 1073 * linkType: 0 - no link by default, 1 = link to Javadoc HTML file 1074 */ writeCtorTableEntry(String pkgName, String className, String type, int linkType, String possibleComment, boolean useOld)1075 public void writeCtorTableEntry(String pkgName, String className, 1076 String type, int linkType, 1077 String possibleComment, boolean useOld) { 1078 String fqName = pkgName + "." + className; 1079 String shownClassName = makeTwoRows(className); 1080 String lt = "removed"; 1081 if (linkType ==1) 1082 lt = "added"; 1083 String commentID = fqName + ".ctor_" + lt + "(" + type + ")"; 1084 if (!useOld) { 1085 reportFile.println("<TR BGCOLOR=\"" + bgcolor + "\" CLASS=\"TableRowColor\">"); 1086 reportFile.println(" <TD VALIGN=\"TOP\" WIDTH=\"25%\">"); 1087 reportFile.println(" <A NAME=\"" + commentID + "\"></A>"); // Named anchor 1088 } 1089 String shortType = simpleName(type); 1090 if (linkType == 0) { 1091 if (oldDocPrefix == null) { 1092 // No link 1093 reportFile.print(" <nobr>" + className); 1094 emitTypeWithParens(shortType); 1095 reportFile.println("</nobr>"); 1096 } else { 1097 writeCtorTableEntry(pkgName, className, 1098 type, 1, 1099 possibleComment, true); 1100 } 1101 } else if (linkType == 1) { 1102 // Link to HTML file for the package 1103 String memberRef = fqName.replace('.', '/'); 1104 // Deal with inner classes 1105 if (className.indexOf('.') != -1) { 1106 memberRef = pkgName + "."; 1107 memberRef = memberRef.replace('.', '/'); 1108 if (useOld) { 1109 // oldDocPrefix is non-null at this point 1110 memberRef = oldDocPrefix + memberRef + className; 1111 } else { 1112 memberRef = newDocPrefix + memberRef + className; 1113 } 1114 } else { 1115 if (useOld) { 1116 // oldDocPrefix is non-null at this point 1117 memberRef = oldDocPrefix + memberRef; 1118 } else { 1119 memberRef = newDocPrefix + memberRef; 1120 } 1121 } 1122 reportFile.print(" <nobr><A HREF=\"" + memberRef + ".html#" + className + 1123 "(" + type + ")\" target=\"_top\"><code>" + shownClassName + "</code></A>"); 1124 emitTypeWithParens(shortType); 1125 reportFile.println("</nobr>"); 1126 } 1127 if (!useOld) { 1128 reportFile.println(" </TD>"); 1129 emitComment(commentID, possibleComment, linkType); 1130 reportFile.println("</TR>"); 1131 } 1132 } 1133 1134 /** 1135 * Write a table entry for a changed constructor. 1136 */ writeCtorChangedTableEntry(String pkgName, String className, MemberDiff memberDiff)1137 public void writeCtorChangedTableEntry(String pkgName, String className, 1138 MemberDiff memberDiff) { 1139 String fqName = pkgName + "." + className; 1140 String newSignature = memberDiff.newType_; 1141 if (newSignature.compareTo("void") == 0) 1142 newSignature = ""; 1143 String commentID = fqName + ".ctor_changed(" + newSignature + ")"; 1144 reportFile.println("<TR BGCOLOR=\"" + bgcolor + "\" CLASS=\"TableRowColor\">"); 1145 reportFile.println(" <TD VALIGN=\"TOP\" WIDTH=\"25%\">"); 1146 reportFile.println(" <A NAME=\"" + commentID + "\"></A>"); // Named anchor 1147 String memberRef = fqName.replace('.', '/'); 1148 String shownClassName = makeTwoRows(className); 1149 // Deal with inner classes 1150 if (className.indexOf('.') != -1) { 1151 memberRef = pkgName + "."; 1152 memberRef = memberRef.replace('.', '/'); 1153 memberRef = newDocPrefix + memberRef + className; 1154 } else { 1155 memberRef = newDocPrefix + memberRef; 1156 } 1157 String newType = memberDiff.newType_; 1158 if (newType.compareTo("void") == 0) 1159 newType = ""; 1160 String shortNewType = simpleName(memberDiff.newType_); 1161 // Constructors have the linked name, then the type in parentheses. 1162 reportFile.print(" <nobr><A HREF=\"" + memberRef + ".html#" + className + "(" + newType + ")\" target=\"_top\"><code>"); 1163 reportFile.print(shownClassName); 1164 reportFile.print("</code></A>"); 1165 emitTypeWithParens(shortNewType); 1166 reportFile.println(" </nobr>"); 1167 reportFile.println(" </TD>"); 1168 1169 // Report changes in documentation 1170 if (reportDocChanges && memberDiff.documentationChange_ != null) { 1171 String oldMemberRef = null; 1172 String oldType = null; 1173 if (oldDocPrefix != null) { 1174 oldMemberRef = pkgName + "." + className; 1175 oldMemberRef = oldMemberRef.replace('.', '/'); 1176 if (className.indexOf('.') != -1) { 1177 oldMemberRef = pkgName + "."; 1178 oldMemberRef = oldMemberRef.replace('.', '/'); 1179 oldMemberRef = oldDocPrefix + oldMemberRef + className; 1180 } else { 1181 oldMemberRef = oldDocPrefix + oldMemberRef; 1182 } 1183 oldType = memberDiff.oldType_; 1184 if (oldType.compareTo("void") == 0) 1185 oldType = ""; 1186 } 1187 if (oldDocPrefix != null) 1188 memberDiff.documentationChange_ += "<A HREF=\"" + 1189 oldMemberRef + ".html#" + className + "(" + oldType + 1190 ")\" target=\"_self\"><code>old</code></A> to "; 1191 else 1192 memberDiff.documentationChange_ += "<font size=\"+1\"><code>old</code></font> to "; 1193 memberDiff.documentationChange_ += "<A HREF=\"" + memberRef + 1194 ".html#" + className + "(" + newType + 1195 ")\" target=\"_self\"><code>new</code></A>.<br>"; 1196 } 1197 1198 emitChanges(memberDiff, 0); 1199 emitComment(commentID, null, 2); 1200 1201 reportFile.println("</TR>"); 1202 } 1203 1204 /** 1205 * Write a table entry for a method. 1206 * 1207 * linkType: 0 - no link by default, 1 = link to Javadoc HTML file 1208 */ writeMethodTableEntry(String pkgName, String className, MethodAPI methodAPI, int linkType, String possibleComment, boolean useOld)1209 public void writeMethodTableEntry(String pkgName, String className, 1210 MethodAPI methodAPI, int linkType, 1211 String possibleComment, boolean useOld) { 1212 String fqName = pkgName + "." + className; 1213 String signature = methodAPI.getSignature(); 1214 String methodName = methodAPI.name_; 1215 String lt = "removed"; 1216 if (linkType ==1) 1217 lt = "added"; 1218 String commentID = fqName + "." + methodName + "_" + lt + "(" + signature + ")"; 1219 if (!useOld) { 1220 reportFile.println("<TR BGCOLOR=\"" + bgcolor + "\" CLASS=\"TableRowColor\">"); 1221 reportFile.println(" <TD VALIGN=\"TOP\" WIDTH=\"25%\">"); 1222 reportFile.println(" <A NAME=\"" + commentID + "\"></A>"); // Named anchor 1223 } 1224 if (signature.compareTo("void") == 0) 1225 signature = ""; 1226 String shortSignature = simpleName(signature); 1227 String returnType = methodAPI.returnType_; 1228 String shortReturnType = simpleName(returnType); 1229 if (linkType == 0) { 1230 if (oldDocPrefix == null) { 1231 // No link 1232 reportFile.print(" <nobr>"); 1233 emitType(shortReturnType); 1234 reportFile.print(" " + methodName); 1235 emitTypeWithParens(shortSignature); 1236 reportFile.println("</nobr>"); 1237 } else { 1238 writeMethodTableEntry(pkgName, className, 1239 methodAPI, 1, 1240 possibleComment, true); 1241 } 1242 } else if (linkType == 1) { 1243 // Link to HTML file for the package 1244 String memberRef = fqName.replace('.', '/'); 1245 // Deal with inner classes 1246 if (className.indexOf('.') != -1) { 1247 memberRef = pkgName + "."; 1248 memberRef = memberRef.replace('.', '/'); 1249 if (useOld) { 1250 // oldDocPrefix is non-null at this point 1251 memberRef = oldDocPrefix + memberRef + className; 1252 } else { 1253 memberRef = newDocPrefix + memberRef + className; 1254 } 1255 } else { 1256 if (useOld) { 1257 // oldDocPrefix is non-null at this point 1258 memberRef = oldDocPrefix + memberRef; 1259 } else { 1260 memberRef = newDocPrefix + memberRef; 1261 } 1262 } 1263 reportFile.print(" <nobr>"); 1264 emitType(shortReturnType); 1265 reportFile.print(" <A HREF=\"" + memberRef + ".html#" + methodName + 1266 "(" + signature + ")\" target=\"_top\"><code>" + methodName + "</code></A>"); 1267 emitTypeWithParens(shortSignature); 1268 reportFile.println("</nobr>"); 1269 } 1270 if (!useOld) { 1271 reportFile.println(" </TD>"); 1272 emitComment(commentID, possibleComment, linkType); 1273 reportFile.println("</TR>"); 1274 } 1275 } 1276 1277 /** 1278 * Write a table entry for a changed method. 1279 */ writeMethodChangedTableEntry(String pkgName, String className, MemberDiff memberDiff)1280 public void writeMethodChangedTableEntry(String pkgName, String className, 1281 MemberDiff memberDiff) { 1282 String memberName = memberDiff.name_; 1283 // Generally nowhere to break a member name anyway 1284 // String shownMemberName = makeTwoRows(memberName); 1285 String fqName = pkgName + "." + className; 1286 String newSignature = memberDiff.newSignature_; 1287 String commentID = fqName + "." + memberName + "_changed(" + newSignature + ")"; 1288 reportFile.println("<TR BGCOLOR=\"" + bgcolor + "\" CLASS=\"TableRowColor\">"); 1289 1290 reportFile.println(" <TD VALIGN=\"TOP\" WIDTH=\"25%\">"); 1291 reportFile.println(" <A NAME=\"" + commentID + "\"></A>"); // Named anchor 1292 String memberRef = fqName.replace('.', '/'); 1293 // Deal with inner classes 1294 if (className.indexOf('.') != -1) { 1295 memberRef = pkgName + "."; 1296 memberRef = memberRef.replace('.', '/'); 1297 memberRef = newDocPrefix + memberRef + className; 1298 } else { 1299 memberRef = newDocPrefix + memberRef; 1300 } 1301 // Javadoc generated HTML has no named anchors for methods 1302 // inherited from other classes, so link to the defining class' method. 1303 // Only copes with non-inner classes. 1304 if (className.indexOf('.') == -1 && 1305 memberDiff.modifiersChange_ != null && 1306 memberDiff.modifiersChange_.indexOf("but is now inherited from") != -1) { 1307 memberRef = memberDiff.inheritedFrom_; 1308 memberRef = memberRef.replace('.', '/'); 1309 memberRef = newDocPrefix + memberRef; 1310 } 1311 1312 String newReturnType = memberDiff.newType_; 1313 String shortReturnType = simpleName(newReturnType); 1314 String shortSignature = simpleName(newSignature); 1315 reportFile.print(" <nobr>"); 1316 emitTypeWithNoParens(shortReturnType); 1317 reportFile.print(" <A HREF=\"" + memberRef + ".html#" + 1318 memberName + "(" + newSignature + ")\" target=\"_top\"><code>"); 1319 reportFile.print(memberName); 1320 reportFile.print("</code></A>"); 1321 emitTypeWithParens(shortSignature); 1322 reportFile.println(" </nobr>"); 1323 reportFile.println(" </TD>"); 1324 1325 // Report changes in documentation 1326 if (reportDocChanges && memberDiff.documentationChange_ != null) { 1327 String oldMemberRef = null; 1328 String oldSignature = null; 1329 if (oldDocPrefix != null) { 1330 oldMemberRef = pkgName + "." + className; 1331 oldMemberRef = oldMemberRef.replace('.', '/'); 1332 if (className.indexOf('.') != -1) { 1333 oldMemberRef = pkgName + "."; 1334 oldMemberRef = oldMemberRef.replace('.', '/'); 1335 oldMemberRef = oldDocPrefix + oldMemberRef + className; 1336 } else { 1337 oldMemberRef = oldDocPrefix + oldMemberRef; 1338 } 1339 oldSignature = memberDiff.oldSignature_; 1340 } 1341 if (oldDocPrefix != null) 1342 memberDiff.documentationChange_ += "<A HREF=\"" + 1343 oldMemberRef + ".html#" + memberName + "(" + 1344 oldSignature + ")\" target=\"_self\"><code>old</code></A> to "; 1345 else 1346 memberDiff.documentationChange_ += "<code>old</code> to "; 1347 memberDiff.documentationChange_ += "<A HREF=\"" + memberRef + 1348 ".html#" + memberName + "(" + newSignature + 1349 ")\" target=\"_self\"><code>new</code></A>.<br>"; 1350 } 1351 1352 emitChanges(memberDiff, 1); 1353 // Get the comment from the parent class if more appropriate 1354 if (memberDiff.modifiersChange_ != null) { 1355 int parentIdx = memberDiff.modifiersChange_.indexOf("now inherited from"); 1356 if (parentIdx != -1) { 1357 // Change the commentID to pick up the appropriate method 1358 commentID = memberDiff.inheritedFrom_ + "." + memberName + 1359 "_changed(" + newSignature + ")"; 1360 } 1361 } 1362 emitComment(commentID, null, 2); 1363 1364 reportFile.println("</TR>"); 1365 } 1366 1367 /** 1368 * Write a table entry for a field. 1369 * 1370 * linkType: 0 - no link by default, 1 = link to Javadoc HTML file 1371 */ writeFieldTableEntry(String pkgName, String className, FieldAPI fieldAPI, int linkType, String possibleComment, boolean useOld)1372 public void writeFieldTableEntry(String pkgName, String className, 1373 FieldAPI fieldAPI, int linkType, 1374 String possibleComment, boolean useOld) { 1375 String fqName = pkgName + "." + className; 1376 // Fields can only appear in one table, so no need to specify _added etc 1377 String fieldName = fieldAPI.name_; 1378 // Generally nowhere to break a member name anyway 1379 // String shownFieldName = makeTwoRows(fieldName); 1380 String commentID = fqName + "." + fieldName; 1381 if (!useOld) { 1382 reportFile.println("<TR BGCOLOR=\"" + bgcolor + "\" CLASS=\"TableRowColor\">"); 1383 reportFile.println(" <TD VALIGN=\"TOP\" WIDTH=\"25%\">"); 1384 reportFile.println(" <A NAME=\"" + commentID + "\"></A>"); // Named anchor 1385 } 1386 String fieldType = fieldAPI.type_; 1387 if (fieldType.compareTo("void") == 0) 1388 fieldType = ""; 1389 String shortFieldType = simpleName(fieldType); 1390 if (linkType == 0) { 1391 if (oldDocPrefix == null) { 1392 // No link. 1393 reportFile.print(" "); 1394 emitType(shortFieldType); 1395 reportFile.println(" " + fieldName); 1396 } else { 1397 writeFieldTableEntry(pkgName, className, 1398 fieldAPI, 1, 1399 possibleComment, true); 1400 } 1401 } else if (linkType == 1) { 1402 // Link to HTML file for the package. 1403 String memberRef = fqName.replace('.', '/'); 1404 // Deal with inner classes 1405 if (className.indexOf('.') != -1) { 1406 memberRef = pkgName + "."; 1407 memberRef = memberRef.replace('.', '/'); 1408 if (useOld) 1409 memberRef = oldDocPrefix + memberRef + className; 1410 else 1411 memberRef = newDocPrefix + memberRef + className; 1412 } else { 1413 if (useOld) 1414 memberRef = oldDocPrefix + memberRef; 1415 else 1416 memberRef = newDocPrefix + memberRef; 1417 } 1418 reportFile.print(" <nobr>"); 1419 emitType(shortFieldType); 1420 reportFile.println(" <A HREF=\"" + memberRef + ".html#" + fieldName + 1421 "\" target=\"_top\"><code>" + fieldName + "</code></A></nobr>"); 1422 } 1423 if (!useOld) { 1424 reportFile.println(" </TD>"); 1425 emitComment(commentID, possibleComment, linkType); 1426 reportFile.println("</TR>"); 1427 } 1428 } 1429 1430 /** 1431 * Write a table entry for a changed field. 1432 */ writeFieldChangedTableEntry(String pkgName, String className, MemberDiff memberDiff)1433 public void writeFieldChangedTableEntry(String pkgName, String className, 1434 MemberDiff memberDiff) { 1435 String memberName = memberDiff.name_; 1436 // Generally nowhere to break a member name anyway 1437 // String shownMemberName = makeTwoRows(memberName); 1438 String fqName = pkgName + "." + className; 1439 // Fields have unique names in a class 1440 String commentID = fqName + "." + memberName; 1441 reportFile.println("<TR BGCOLOR=\"" + bgcolor + "\" CLASS=\"TableRowColor\">"); 1442 1443 reportFile.println(" <TD VALIGN=\"TOP\" WIDTH=\"25%\">"); 1444 reportFile.println(" <A NAME=\"" + commentID + "\"></A>"); // Named anchor 1445 String memberRef = fqName.replace('.', '/'); 1446 // Deal with inner classes 1447 if (className.indexOf('.') != -1) { 1448 memberRef = pkgName + "."; 1449 memberRef = memberRef.replace('.', '/'); 1450 memberRef = newDocPrefix + memberRef + className; 1451 } else { 1452 memberRef = newDocPrefix + memberRef; 1453 } 1454 // Javadoc generated HTML has no named anchors for fields 1455 // inherited from other classes, so link to the defining class' field. 1456 // Only copes with non-inner classes. 1457 if (className.indexOf('.') == -1 && 1458 memberDiff.modifiersChange_ != null && 1459 memberDiff.modifiersChange_.indexOf("but is now inherited from") != -1) { 1460 memberRef = memberDiff.inheritedFrom_; 1461 memberRef = memberRef.replace('.', '/'); 1462 memberRef = newDocPrefix + memberRef; 1463 } 1464 1465 String newType = memberDiff.newType_; 1466 String shortNewType = simpleName(newType); 1467 reportFile.print(" <nobr>"); 1468 emitTypeWithNoParens(shortNewType); 1469 reportFile.print(" <A HREF=\"" + memberRef + ".html#" + 1470 memberName + "\" target=\"_top\"><code>"); 1471 reportFile.print(memberName); 1472 reportFile.print("</code></font></A></nobr>"); 1473 reportFile.println(" </TD>"); 1474 1475 // Report changes in documentation 1476 if (reportDocChanges && memberDiff.documentationChange_ != null) { 1477 String oldMemberRef = null; 1478 if (oldDocPrefix != null) { 1479 oldMemberRef = pkgName + "." + className; 1480 oldMemberRef = oldMemberRef.replace('.', '/'); 1481 if (className.indexOf('.') != -1) { 1482 oldMemberRef = pkgName + "."; 1483 oldMemberRef = oldMemberRef.replace('.', '/'); 1484 oldMemberRef = oldDocPrefix + oldMemberRef + className; 1485 } else { 1486 oldMemberRef = oldDocPrefix + oldMemberRef; 1487 } 1488 } 1489 if (oldDocPrefix != null) 1490 memberDiff.documentationChange_ += "<A HREF=\"" + 1491 oldMemberRef + ".html#" + memberName + "\" target=\"_self\"><code>old</code></A> to "; 1492 else 1493 memberDiff.documentationChange_ += "<code>old</code> to "; 1494 memberDiff.documentationChange_ += "<A HREF=\"" + memberRef + 1495 ".html#" + memberName + "\" target=\"_self\"><code>new</code></A>.<br>"; 1496 } 1497 1498 emitChanges(memberDiff, 2); 1499 // Get the comment from the parent class if more appropriate 1500 if (memberDiff.modifiersChange_ != null) { 1501 int parentIdx = memberDiff.modifiersChange_.indexOf("now inherited from"); 1502 if (parentIdx != -1) { 1503 // Change the commentID to pick up the appropriate method 1504 commentID = memberDiff.inheritedFrom_ + "." + memberName; 1505 } 1506 } 1507 emitComment(commentID, null, 2); 1508 1509 reportFile.println("</TR>"); 1510 } 1511 1512 /** 1513 * Emit all changes associated with a MemberDiff as an entry in a table. 1514 * 1515 * @param memberType 0 = ctor, 1 = method, 2 = field 1516 */ emitChanges(MemberDiff memberDiff, int memberType)1517 public void emitChanges(MemberDiff memberDiff, int memberType){ 1518 reportFile.println(" <TD VALIGN=\"TOP\" WIDTH=\"30%\">"); 1519 boolean hasContent = false; 1520 // The type or return type changed 1521 if (memberDiff.oldType_.compareTo(memberDiff.newType_) != 0) { 1522 String shortOldType = simpleName(memberDiff.oldType_); 1523 String shortNewType = simpleName(memberDiff.newType_); 1524 if (memberType == 1) { 1525 reportFile.print("Change in return type from "); 1526 } else { 1527 reportFile.print("Change in type from "); 1528 } 1529 if (shortOldType.compareTo(shortNewType) == 0) { 1530 // The types differ in package name, so use the full name 1531 shortOldType = memberDiff.oldType_; 1532 shortNewType = memberDiff.newType_; 1533 } 1534 emitType(shortOldType); 1535 reportFile.print(" to "); 1536 emitType(shortNewType); 1537 reportFile.println(".<br>"); 1538 hasContent = true; 1539 } 1540 // The signatures changed - only used by methods 1541 if (memberType == 1 && 1542 memberDiff.oldSignature_ != null && 1543 memberDiff.newSignature_ != null && 1544 memberDiff.oldSignature_.compareTo(memberDiff.newSignature_) != 0) { 1545 String shortOldSignature = simpleName(memberDiff.oldSignature_); 1546 String shortNewSignature = simpleName(memberDiff.newSignature_); 1547 if (shortOldSignature.compareTo(shortNewSignature) == 0) { 1548 // The signatures differ in package names, so use the full form 1549 shortOldSignature = memberDiff.oldSignature_; 1550 shortNewSignature = memberDiff.newSignature_; 1551 } 1552 if (hasContent) 1553 reportFile.print(" "); 1554 reportFile.print("Change in signature from "); 1555 if (shortOldSignature.compareTo("") == 0) 1556 shortOldSignature = "void"; 1557 emitType(shortOldSignature); 1558 reportFile.print(" to "); 1559 if (shortNewSignature.compareTo("") == 0) 1560 shortNewSignature = "void"; 1561 emitType(shortNewSignature); 1562 reportFile.println(".<br>"); 1563 hasContent = true; 1564 } 1565 // The exceptions are only non-null in methods and constructors 1566 if (memberType != 2 && 1567 memberDiff.oldExceptions_ != null && 1568 memberDiff.newExceptions_ != null && 1569 memberDiff.oldExceptions_.compareTo(memberDiff.newExceptions_) != 0) { 1570 if (hasContent) 1571 reportFile.print(" "); 1572 // If either one of the exceptions has no spaces in it, or is 1573 // equal to "no exceptions", then just display the whole 1574 // exceptions texts. 1575 int spaceInOld = memberDiff.oldExceptions_.indexOf(" "); 1576 if (memberDiff.oldExceptions_.compareTo("no exceptions") == 0) 1577 spaceInOld = -1; 1578 int spaceInNew = memberDiff.newExceptions_.indexOf(" "); 1579 if (memberDiff.newExceptions_.compareTo("no exceptions") == 0) 1580 spaceInNew = -1; 1581 if (spaceInOld == -1 || spaceInNew == -1) { 1582 reportFile.print("Change in exceptions thrown from "); 1583 emitException(memberDiff.oldExceptions_); 1584 reportFile.print(" to " ); 1585 emitException(memberDiff.newExceptions_); 1586 reportFile.println(".<br>"); 1587 } else { 1588 // Too many exceptions become unreadable, so just show the 1589 // individual changes. Catch the case where exceptions are 1590 // just reordered. 1591 boolean firstChange = true; 1592 int numRemoved = 0; 1593 StringTokenizer stOld = new StringTokenizer(memberDiff.oldExceptions_, ", "); 1594 while (stOld.hasMoreTokens()) { 1595 String oldException = stOld.nextToken(); 1596 if (!memberDiff.newExceptions_.startsWith(oldException) && 1597 !(memberDiff.newExceptions_.indexOf(", " + oldException) != -1)) { 1598 if (firstChange) { 1599 reportFile.print("Change in exceptions: "); 1600 firstChange = false; 1601 } 1602 if (numRemoved != 0) 1603 reportFile.print(", "); 1604 emitException(oldException); 1605 numRemoved++; 1606 } 1607 } 1608 if (numRemoved == 1) 1609 reportFile.print(" was removed."); 1610 else if (numRemoved > 1) 1611 reportFile.print(" were removed."); 1612 1613 int numAdded = 0; 1614 StringTokenizer stNew = new StringTokenizer(memberDiff.newExceptions_, ", "); 1615 while (stNew.hasMoreTokens()) { 1616 String newException = stNew.nextToken(); 1617 if (!memberDiff.oldExceptions_.startsWith(newException) && 1618 !(memberDiff.oldExceptions_.indexOf(", " + newException) != -1)) { 1619 if (firstChange) { 1620 reportFile.print("Change in exceptions: "); 1621 firstChange = false; 1622 } 1623 if (numAdded != 0) 1624 reportFile.println(", "); 1625 else 1626 reportFile.println(" "); 1627 emitException(newException); 1628 numAdded++; 1629 } 1630 } 1631 if (numAdded == 1) 1632 reportFile.print(" was added"); 1633 else if (numAdded > 1) 1634 reportFile.print(" were added"); 1635 else if (numAdded == 0 && numRemoved == 0 && firstChange) 1636 reportFile.print("Exceptions were reordered"); 1637 reportFile.println(".<br>"); 1638 } 1639 // Note the changes between a comma-separated list of Strings 1640 hasContent = true; 1641 } 1642 1643 if (memberDiff.documentationChange_ != null) { 1644 if (hasContent) 1645 reportFile.print(" "); 1646 reportFile.print(memberDiff.documentationChange_); 1647 hasContent = true; 1648 } 1649 1650 // Last, so no need for a <br> 1651 if (memberDiff.modifiersChange_ != null) { 1652 if (hasContent) 1653 reportFile.print(" "); 1654 reportFile.println(memberDiff.modifiersChange_); 1655 hasContent = true; 1656 } 1657 reportFile.println(" </TD>"); 1658 } 1659 1660 /** 1661 * Emit a string which is an exception by surrounding it with 1662 * <code> tags. 1663 * If there is a space in the type, e.g. "String, File", then 1664 * surround it with parentheses too. Do not add <code> tags or 1665 * parentheses if the String is "no exceptions". 1666 */ emitException(String ex)1667 public void emitException(String ex) { 1668 if (ex.compareTo("no exceptions") == 0) { 1669 reportFile.print(ex); 1670 } else { 1671 if (ex.indexOf(' ') != -1) { 1672 reportFile.print("(<code>" + ex + "</code>)"); 1673 } else { 1674 reportFile.print("<code>" + ex + "</code>"); 1675 } 1676 } 1677 } 1678 1679 /** 1680 * Emit a string which is a type by surrounding it with <code> tags. 1681 * If there is a space in the type, e.g. "String, File", then 1682 * surround it with parentheses too. 1683 */ emitType(String type)1684 public void emitType(String type) { 1685 if (type.compareTo("") == 0) 1686 return; 1687 if (type.indexOf(' ') != -1) { 1688 reportFile.print("(<code>" + type + "</code>)"); 1689 } else { 1690 reportFile.print("<code>" + type + "</code>"); 1691 } 1692 } 1693 1694 /** 1695 * Emit a string which is a type by surrounding it with <code> tags. 1696 * Also surround it with parentheses too. Used to display methods' 1697 * parameters. 1698 * Suggestions for where a browser should break the 1699 * text are provided with <br> and <nobr> tags. 1700 */ emitTypeWithParens(String type)1701 public static void emitTypeWithParens(String type) { 1702 emitTypeWithParens(type, true); 1703 } 1704 1705 /** 1706 * Emit a string which is a type by surrounding it with <code> tags. 1707 * Also surround it with parentheses too. Used to display methods' 1708 * parameters. 1709 */ emitTypeWithParens(String type, boolean addBreaks)1710 public static void emitTypeWithParens(String type, boolean addBreaks) { 1711 if (type.compareTo("") == 0) 1712 reportFile.print("()"); 1713 else { 1714 int idx = type.indexOf(", "); 1715 if (!addBreaks || idx == -1) { 1716 reportFile.print("(<code>" + type + "</code>)"); 1717 } else { 1718 // Make the browser break text at reasonable places 1719 String sepType = null; 1720 StringTokenizer st = new StringTokenizer(type, ", "); 1721 while (st.hasMoreTokens()) { 1722 String p = st.nextToken(); 1723 if (sepType == null) 1724 sepType = p; 1725 else 1726 sepType += ",</nobr> " + p + "<nobr>"; 1727 } 1728 reportFile.print("(<code>" + sepType + "<nobr></code>)"); 1729 } 1730 } 1731 } 1732 1733 /** 1734 * Emit a string which is a type by surrounding it with <code> tags. 1735 * Do not surround it with parentheses. Used to display methods' return 1736 * types and field types. 1737 */ emitTypeWithNoParens(String type)1738 public static void emitTypeWithNoParens(String type) { 1739 if (type.compareTo("") != 0) 1740 reportFile.print("<code>" + type + "</code>"); 1741 } 1742 1743 /** 1744 * Return a String with the simple names of the classes in fqName. 1745 * "java.lang.String" becomes "String", 1746 * "java.lang.String, java.io.File" becomes "String, File" 1747 * and so on. If fqName is null, return null. If fqName is "", 1748 * return "". 1749 */ simpleName(String fqNames)1750 public static String simpleName(String fqNames) { 1751 if (fqNames == null) 1752 return null; 1753 String res = ""; 1754 boolean hasContent = false; 1755 // We parse the string step by step to ensure we take 1756 // fqNames that contains generics parameter in a whole. 1757 ArrayList<String> fqNamesList = new ArrayList<String>(); 1758 int genericParametersDepth = 0; 1759 StringBuffer buffer = new StringBuffer(); 1760 for (int i=0; i<fqNames.length(); i++) { 1761 char c = fqNames.charAt(i); 1762 if ('<' == c) { 1763 genericParametersDepth++; 1764 } 1765 if ('>' == c) { 1766 genericParametersDepth--; 1767 } 1768 if (',' != c || genericParametersDepth > 0) { 1769 buffer.append(c); 1770 } else if (',' == c) { 1771 fqNamesList.add(buffer.toString().trim()); 1772 buffer = new StringBuffer(buffer.length()); 1773 } 1774 } 1775 fqNamesList.add(buffer.toString().trim()); 1776 for (String fqName : fqNamesList) { 1777 // Assume this will be used inside a <nobr> </nobr> set of tags. 1778 if (hasContent) 1779 res += ", "; 1780 hasContent = true; 1781 // Look for text within '<' and '>' in case this is a invocation of a generic 1782 1783 int firstBracket = fqName.indexOf('<'); 1784 int lastBracket = fqName.lastIndexOf('>'); 1785 String genericParameter = null; 1786 if (firstBracket != -1 && lastBracket != -1) { 1787 genericParameter = simpleName(fqName.substring(firstBracket + 1, lastBracket)); 1788 fqName = fqName.substring(0, firstBracket); 1789 } 1790 1791 int lastDot = fqName.lastIndexOf('.'); 1792 if (lastDot < 0) { 1793 res += fqName; // Already as simple as possible 1794 } else { 1795 res += fqName.substring(lastDot+1); 1796 } 1797 if (genericParameter != null) 1798 res += "<" + genericParameter + ">"; 1799 } 1800 return res; 1801 } 1802 1803 /** 1804 * Find any existing comment and emit it. Add the new comment to the 1805 * list of new comments. The first instance of the string "@first" in 1806 * a hand-written comment will be replaced by the first sentence from 1807 * the associated doc block, if such exists. Also replace @link by 1808 * an HTML link. 1809 * 1810 * @param commentID The identifier for this comment. 1811 * @param possibleComment A possible comment from another source. 1812 * @param linkType 0 = remove, 1 = add, 2 = change 1813 */ emitComment(String commentID, String possibleComment, int linkType)1814 public void emitComment(String commentID, String possibleComment, 1815 int linkType) { 1816 if (noCommentsOnRemovals && linkType == 0) { 1817 reportFile.println(" <TD> </TD>"); 1818 return; 1819 } 1820 if (noCommentsOnAdditions && linkType == 1) { 1821 reportFile.println(" <TD> </TD>"); 1822 return; 1823 } 1824 if (noCommentsOnChanges && linkType == 2) { 1825 reportFile.println(" <TD> </TD>"); 1826 return; 1827 } 1828 1829 // We have to use this global hash table because the *Diff classes 1830 // do not store the possible comment from the new *API object. 1831 if (!noCommentsOnChanges && possibleComment == null) { 1832 possibleComment = (String)Comments.allPossibleComments.get(commentID); 1833 } 1834 // Just use the first sentence of the possible comment. 1835 if (possibleComment != null) { 1836 int fsidx = RootDocToXML.endOfFirstSentence(possibleComment, false); 1837 if (fsidx != -1 && fsidx != 0) 1838 possibleComment = possibleComment.substring(0, fsidx+1); 1839 } 1840 1841 String comment = Comments.getComment(existingComments_, commentID); 1842 if (comment.compareTo(Comments.placeHolderText) == 0) { 1843 if (possibleComment != null && 1844 possibleComment.indexOf("InsertOtherCommentsHere") == -1) 1845 reportFile.println(" <TD VALIGN=\"TOP\">" + possibleComment + "</TD>"); 1846 else 1847 reportFile.println(" <TD> </TD>"); 1848 } else { 1849 int idx = comment.indexOf("@first"); 1850 if (idx == -1) { 1851 reportFile.println(" <TD VALIGN=\"TOP\">" + Comments.convertAtLinks(comment, "", null, null) + "</TD>"); 1852 } else { 1853 reportFile.print(" <TD VALIGN=\"TOP\">" + comment.substring(0, idx)); 1854 if (possibleComment != null && 1855 possibleComment.indexOf("InsertOtherCommentsHere") == -1) 1856 reportFile.print(possibleComment); 1857 reportFile.println(comment.substring(idx + 6) + "</TD>"); 1858 } 1859 } 1860 SingleComment newComment = new SingleComment(commentID, comment); 1861 newComments_.addComment(newComment); 1862 } 1863 1864 /** Write the end of a table. */ writeTableEnd()1865 public void writeTableEnd() { 1866 reportFile.println("</TABLE>"); 1867 reportFile.println(" "); 1868 } 1869 1870 /** Write a newline out. */ writeText()1871 public void writeText() { 1872 reportFile.println(); 1873 } 1874 1875 /** Write some text out. */ writeText(String text)1876 public void writeText(String text) { 1877 reportFile.println(text); 1878 } 1879 1880 /** Emit some non-breaking space for indentation. */ indent(int indent)1881 public void indent(int indent) { 1882 for (int i = 0; i < indent; i++) 1883 reportFile.print(" "); 1884 } 1885 1886 /** 1887 * The name of the file to which the top-level HTML file is written, 1888 * and also the name of the subdirectory where most of the HTML appears, 1889 * and also a prefix for the names of some of the files in that 1890 * subdirectory. 1891 */ 1892 static String reportFileName = "changes"; 1893 1894 /** 1895 * The suffix of the file to which the HTML output is currently being 1896 * written. 1897 */ 1898 static String reportFileExt = ".html"; 1899 1900 /** 1901 * The file to which the HTML output is currently being written. 1902 */ 1903 static PrintWriter reportFile = null; 1904 1905 /** 1906 * The object which represents the top of the tree of differences 1907 * between two APIs. It is only used indirectly when emitting a 1908 * navigation bar. 1909 */ 1910 static APIDiff apiDiff = null; 1911 1912 /** 1913 * If set, then do not suggest comments for removals from the first 1914 * sentence of the doc block of the old API. 1915 */ 1916 public static boolean noCommentsOnRemovals = false; 1917 1918 /** 1919 * If set, then do not suggest comments for additions from the first 1920 * sentence of the doc block of the new API. 1921 */ 1922 public static boolean noCommentsOnAdditions = false; 1923 1924 /** 1925 * If set, then do not suggest comments for changes from the first 1926 * sentence of the doc block of the new API. 1927 */ 1928 public static boolean noCommentsOnChanges = false; 1929 1930 /** 1931 * If set, then report changes in documentation (Javadoc comments) 1932 * between the old and the new API. The default is that this is not set. 1933 */ 1934 public static boolean reportDocChanges = false; 1935 1936 /** 1937 * Define the prefix for HTML links to the existing set of Javadoc- 1938 * generated documentation for the new API. E.g. For J2SE1.3.x, use 1939 * "https://java.sun.com/j2se/1.3/docs/api/" 1940 */ 1941 public static String newDocPrefix = "../"; 1942 1943 /** 1944 * Define the prefix for HTML links to the existing set of Javadoc- 1945 * generated documentation for the old API. 1946 */ 1947 public static String oldDocPrefix = null; 1948 1949 /** To generate statistical output, set this to true. */ 1950 public static boolean doStats = false; 1951 1952 /** 1953 * The destination directory for output files. 1954 */ 1955 public static String outputDir = null; 1956 1957 /** 1958 * The destination directory for comments files (if not specified, uses outputDir) 1959 */ 1960 public static String commentsDir = null; 1961 1962 /** 1963 * The title used on the first page of the report. By default, this is 1964 * "API Differences Between <name of old API> and 1965 * <name of new API>". It can be 1966 * set by using the -doctitle option. 1967 */ 1968 public static String docTitle = null; 1969 1970 /** 1971 * The browser window title for the report. By default, this is 1972 * "API Differences Between <name of old API> and 1973 * <name of new API>". It can be 1974 * set by using the -windowtitle option. 1975 */ 1976 public static String windowTitle = null; 1977 1978 /** The desired background color for JDiff tables. */ 1979 static final String bgcolor = "#FFFFFF"; 1980 1981 /** Set to enable debugging output. */ 1982 private static final boolean trace = false; 1983 1984 } 1985