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