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.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 */ reportAddedPackage(PackageAPI pkgAPI)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 */ reportChangedPackage(PackageDiff pkgDiff)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 */ reportAddedClass(String pkgName, ClassAPI classApi)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 */ reportChangedClass(String pkgName, ClassDiff[] classDiffs, int classIndex)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 */ reportAllCtors(String pkgName, ClassDiff classDiff)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 */ reportAllMethods(String pkgName, ClassDiff classDiff)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 */ reportAllFields(String pkgName, ClassDiff classDiff)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 */ writeStartHTMLHeaderWithDate()741 public void writeStartHTMLHeaderWithDate() { 742 writeStartHTMLHeader(true); 743 } 744 745 /** Write the start of the HTML header. */ writeStartHTMLHeader()746 public void writeStartHTMLHeader() { 747 writeStartHTMLHeader(false); 748 } 749 750 /** Write the start of the HTML header. */ writeStartHTMLHeader(boolean addDate)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 */ writeHTMLTitle(String 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 */ writeStyleSheetRef()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 writeStyleSheetRef(boolean inSameDir)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. */ writeHTMLFooter()820 public void writeHTMLFooter() { 821 writeText("</BODY>"); 822 writeText("</HTML>"); 823 } 824 825 /** 826 * Write a section header, which includes a navigation bar. 827 * 828 * @param title Title of the header. Contains any links necessary. 829 * @param packageName The name of the current package, with no slashes or 830 * links in it. May be null 831 * @param prevElemLink An HTML link to the previous element (a package or 832 * class). May be null. 833 * @param nextElemLink An HTML link to the next element (a package or 834 * class). May be null. 835 * @param className The name of the current class, with no slashes or 836 * links in it. May be null. 837 * @param level 0 = overview, 1 = package, 2 = class/interface 838 */ writeSectionHeader(String title, String packageName, String prevElemLink, String nextElemLink, String className, int level, boolean hasRemovals, boolean hasAdditions, boolean hasChanges)839 public void writeSectionHeader(String title, String packageName, 840 String prevElemLink, String nextElemLink, 841 String className, int level, 842 boolean hasRemovals, 843 boolean hasAdditions, 844 boolean hasChanges) { 845 writeNavigationBar(packageName, prevElemLink, nextElemLink, 846 className, level, true, 847 hasRemovals, hasAdditions, hasChanges); 848 if (level != 0) { 849 reportFile.println("<H2>"); 850 reportFile.println(title); 851 reportFile.println("</H2>"); 852 } 853 } 854 855 /** 856 * Write a section footer, which includes a navigation bar. 857 * 858 * @param packageName The name of the current package, with no slashes or 859 * links in it. may be null 860 * @param prevElemLink An HTML link to the previous element (a package or 861 * class). May be null. 862 * @param nextElemLink An HTML link to the next element (a package or 863 * class). May be null. 864 * @param className The name of the current class, with no slashes or 865 * links in it. May be null 866 * @param level 0 = overview, 1 = package, 2 = class/interface 867 */ writeSectionFooter(String packageName, String prevElemLink, String nextElemLink, String className, int level)868 public void writeSectionFooter(String packageName, 869 String prevElemLink, String nextElemLink, 870 String className, int level) { 871 writeText(" </div> "); 872 writeText(" <div id=\"footer\">"); 873 writeText(" <div id=\"copyright\">"); 874 writeText(" Except as noted, this content is licensed under "); 875 writeText(" <a href=\"https://creativecommons.org/licenses/by/2.5/\"> Creative Commons Attribution 2.5</a>."); 876 writeText(" For details and restrictions, see the <a href=\"https://developer.android.com/license.html\">Content License</a>."); 877 writeText(" </div>"); 878 writeText(" <div id=\"footerlinks\">"); 879 writeText(" <p>"); 880 writeText(" <a href=\"https://www.android.com/terms.html\">Site Terms of Service</a> -"); 881 writeText(" <a href=\"https://www.android.com/privacy.html\">Privacy Policy</a> -"); 882 writeText(" <a href=\"https://www.android.com/branding.html\">Brand Guidelines</a>"); 883 writeText(" </p>"); 884 writeText(" </div>"); 885 writeText(" </div> <!-- end footer -->"); 886 writeText(" </div><!-- end doc-content -->"); 887 writeText(" </div> <!-- end body-content --> "); 888 889 } 890 891 /** 892 * Write a navigation bar section header. 893 * 894 * @param pkgName The name of the current package, with no slashes or 895 * links in it. 896 * @param prevElemLink An HTML link to the previous element (a package or 897 * class). May be null. 898 * @param nextElemLink An HTML link to the next element (a package or 899 * class). May be null. 900 * @param className The name of the current class, with no slashes or 901 * links in it. May be null. 902 * @param level 0 = overview, 1 = package, 2 = class/interface 903 */ 904 writeNavigationBar(String pkgName, String prevElemLink, String nextElemLink, String className, int level, boolean upperNavigationBar, boolean hasRemovals, boolean hasAdditions, boolean hasChanges)905 public void writeNavigationBar(String pkgName, 906 String prevElemLink, String nextElemLink, 907 String className, int level, 908 boolean upperNavigationBar, 909 boolean hasRemovals, boolean hasAdditions, 910 boolean hasChanges) { 911 912 String oldAPIName = "Old API"; 913 if (apiDiff.oldAPIName_ != null) 914 oldAPIName = apiDiff.oldAPIName_; 915 String newAPIName = "New API"; 916 if (apiDiff.newAPIName_ != null) 917 newAPIName = apiDiff.newAPIName_; 918 919 SimpleDateFormat formatter 920 = new SimpleDateFormat ("yyyy.MM.dd HH:mm"); 921 Date day = new Date(); 922 923 reportFile.println("<!-- Start of nav bar -->"); 924 925 reportFile.println("<a name=\"top\"></a>"); 926 reportFile.println("<div id=\"header\" style=\"margin-bottom:0;padding-bottom:0;\">"); 927 reportFile.println("<div id=\"headerLeft\">"); 928 reportFile.println("<a href=\"../../../../index.html\" tabindex=\"-1\" target=\"_top\"><img src=\"../../../../assets/images/bg_logo.png\" alt=\"Android Developers\" /></a>"); 929 reportFile.println("</div>"); 930 reportFile.println(" <div id=\"headerRight\">"); 931 reportFile.println(" <div id=\"headerLinks\">"); 932 reportFile.println("<!-- <img src=\"/assets/images/icon_world.jpg\" alt=\"\" /> -->"); 933 reportFile.println("<span class=\"text\">"); 934 reportFile.println("<!-- <a href=\"#\">English</a> | -->"); 935 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>"); 936 reportFile.println("</span>"); 937 reportFile.println("</div>"); 938 reportFile.println(" <div class=\"and-diff-id\" style=\"margin-top:6px;margin-right:8px;\">"); 939 reportFile.println(" <table class=\"diffspectable\">"); 940 reportFile.println(" <tr>"); 941 reportFile.println(" <td colspan=\"2\" class=\"diffspechead\">API Diff Specification</td>"); 942 reportFile.println(" </tr>"); 943 reportFile.println(" <tr>"); 944 reportFile.println(" <td class=\"diffspec\" style=\"padding-top:.25em\">To Level:</td>"); 945 reportFile.println(" <td class=\"diffvaluenew\" style=\"padding-top:.25em\">" + newAPIName + "</td>"); 946 reportFile.println(" </tr>"); 947 reportFile.println(" <tr>"); 948 reportFile.println(" <td class=\"diffspec\">From Level:</td>"); 949 reportFile.println(" <td class=\"diffvalueold\">" + oldAPIName + "</td>"); 950 reportFile.println(" </tr>"); 951 reportFile.println(" <tr>"); 952 reportFile.println(" <td class=\"diffspec\">Generated</td>"); 953 reportFile.println(" <td class=\"diffvalue\">" + formatter.format( day ) + "</td>"); 954 reportFile.println(" </tr>"); 955 reportFile.println(" </table>"); 956 reportFile.println(" </div><!-- End and-diff-id -->"); 957 if (doStats) { 958 reportFile.println(" <div class=\"and-diff-id\" style=\"margin-right:8px;\">"); 959 reportFile.println(" <table class=\"diffspectable\">"); 960 reportFile.println(" <tr>"); 961 reportFile.println(" <td class=\"diffspec\" colspan=\"2\"><a href=\"jdiff_statistics.html\">Statistics</a>"); 962 reportFile.println(" </tr>"); 963 reportFile.println(" </table>"); 964 reportFile.println(" </div> <!-- End and-diff-id -->"); 965 } 966 reportFile.println(" </div> <!-- End headerRight -->"); 967 reportFile.println(" </div> <!-- End header -->"); 968 reportFile.println("<div id=\"body-content\" xstyle=\"padding:12px;padding-right:18px;\">"); 969 reportFile.println("<div id=\"doc-content\" style=\"position:relative;\">"); 970 reportFile.println("<div id=\"mainBodyFluid\">"); 971 } 972 973 /** Write the start of a table. */ writeTableStart(String title, int colSpan)974 public void writeTableStart(String title, int colSpan) { 975 reportFile.println("<p>"); 976 // Assumes that the first word of the title categorizes the table type 977 // and that there is a space after the first word in the title 978 int idx = title.indexOf(' '); 979 String namedAnchor = title.substring(0, idx); 980 reportFile.println("<a NAME=\"" + namedAnchor + "\"></a>"); // Named anchor 981 reportFile.println("<TABLE summary=\"" + title+ "\" WIDTH=\"100%\">"); 982 reportFile.println("<TR>"); 983 reportFile.print(" <TH VALIGN=\"TOP\" COLSPAN=" + colSpan + ">"); 984 reportFile.println(title + "</FONT></TD>"); 985 reportFile.println("</TH>"); 986 } 987 988 /** 989 * If a class or package name is considered to be too long for convenient 990 * display, insert <br> in the middle of it at a period. 991 */ makeTwoRows(String name)992 public String makeTwoRows(String name) { 993 if (name.length() < 30) 994 return name; 995 int idx = name.indexOf(".", 20); 996 if (idx == -1) 997 return name; 998 int len = name.length(); 999 String res = name.substring(0, idx+1) + "<br>" + name.substring(idx+1, len); 1000 return res; 1001 } 1002 1003 /** 1004 * Write a table entry for a package, with support for links to Javadoc 1005 * for removed packages. 1006 * 1007 * linkType: 0 - no link by default, 1 = link to Javadoc HTML file, 2 = link to JDiff file 1008 */ writePackageTableEntry(String pkgName, int linkType, String possibleComment, boolean useOld)1009 public void writePackageTableEntry(String pkgName, int linkType, 1010 String possibleComment, boolean useOld) { 1011 if (!useOld) { 1012 reportFile.println("<TR BGCOLOR=\"" + bgcolor + "\" CLASS=\"TableRowColor\">"); 1013 reportFile.println(" <TD VALIGN=\"TOP\" WIDTH=\"25%\">"); 1014 reportFile.println(" <A NAME=\"" + pkgName + "\"></A>"); // Named anchor 1015 } 1016 //String shownPkgName = makeTwoRows(pkgName); 1017 if (linkType == 0) { 1018 if (oldDocPrefix == null) { 1019 // No link 1020 reportFile.print(" " + pkgName); 1021 } else { 1022 // Call this method again but this time to emit a link to the 1023 // old program element. 1024 writePackageTableEntry(pkgName, 1, possibleComment, true); 1025 } 1026 } else if (linkType == 1) { 1027 // Link to HTML file for the package 1028 String pkgRef = pkgName; 1029 pkgRef = pkgRef.replace('.', '/'); 1030 if (useOld) 1031 pkgRef = oldDocPrefix + pkgRef + "/package-summary"; 1032 else 1033 pkgRef = newDocPrefix + pkgRef + "/package-summary"; 1034 reportFile.println(" <nobr><A HREF=\"" + pkgRef + ".html\" target=\"_top\"><code>" + pkgName + "</code></A></nobr>"); 1035 } else if (linkType == 2) { 1036 reportFile.println(" <nobr><A HREF=\"pkg_" + pkgName + reportFileExt + "\">" + pkgName + "</A></nobr>"); 1037 } 1038 if (!useOld) { 1039 reportFile.println(" </TD>"); 1040 emitComment(pkgName, possibleComment, linkType); 1041 reportFile.println("</TR>"); 1042 } 1043 } 1044 1045 /** 1046 * Write a table entry for a class or interface. 1047 * 1048 * linkType: 0 - no link by default, 1 = link to Javadoc HTML file, 2 = link to JDiff file 1049 */ writeClassTableEntry(String pkgName, String className, int linkType, boolean isInterface, String possibleComment, boolean useOld)1050 public void writeClassTableEntry(String pkgName, String className, 1051 int linkType, boolean isInterface, 1052 String possibleComment, boolean useOld) { 1053 if (!useOld) { 1054 reportFile.println("<TR BGCOLOR=\"" + bgcolor + "\" CLASS=\"TableRowColor\">"); 1055 reportFile.println(" <TD VALIGN=\"TOP\" WIDTH=\"25%\">"); 1056 reportFile.println(" <A NAME=\"" + className + "\"></A>"); // Named anchor 1057 } 1058 String fqName = pkgName + "." + className; 1059 String shownClassName = makeTwoRows(className); 1060 if (linkType == 0) { 1061 if (oldDocPrefix == null) { 1062 // No link 1063 if (isInterface) 1064 reportFile.println(" <I>" + shownClassName + "</I>"); 1065 else 1066 reportFile.println(" " + shownClassName); 1067 } else { 1068 writeClassTableEntry(pkgName, className, 1069 1, isInterface, 1070 possibleComment, true); 1071 } 1072 } else if (linkType == 1) { 1073 // Link to HTML file for the class 1074 String classRef = fqName; 1075 // Deal with inner classes 1076 if (className.indexOf('.') != -1) { 1077 classRef = pkgName + "."; 1078 classRef = classRef.replace('.', '/'); 1079 if (useOld) 1080 classRef = oldDocPrefix + classRef + className; 1081 else 1082 classRef = newDocPrefix + classRef + className; 1083 } else { 1084 classRef = classRef.replace('.', '/'); 1085 if (useOld) 1086 classRef = oldDocPrefix + classRef; 1087 else 1088 classRef = newDocPrefix + classRef; 1089 } 1090 reportFile.print(" <nobr><A HREF=\"" + classRef + ".html\" target=\"_top\"><code>"); 1091 if (isInterface) 1092 reportFile.print("<I>" + shownClassName + "</I>"); 1093 else 1094 reportFile.print(shownClassName); 1095 reportFile.println("</code></A></nobr>"); 1096 } else if (linkType == 2) { 1097 reportFile.print(" <nobr><A HREF=\"" + fqName + reportFileExt + "\">"); 1098 if (isInterface) 1099 reportFile.print("<I>" + shownClassName + "</I>"); 1100 else 1101 reportFile.print(shownClassName); 1102 reportFile.println("</A></nobr>"); 1103 } 1104 if (!useOld) { 1105 reportFile.println(" </TD>"); 1106 emitComment(fqName, possibleComment, linkType); 1107 reportFile.println("</TR>"); 1108 } 1109 } 1110 1111 /** 1112 * Write a table entry for a constructor. 1113 * 1114 * linkType: 0 - no link by default, 1 = link to Javadoc HTML file 1115 */ writeCtorTableEntry(String pkgName, String className, String type, int linkType, String possibleComment, boolean useOld)1116 public void writeCtorTableEntry(String pkgName, String className, 1117 String type, int linkType, 1118 String possibleComment, boolean useOld) { 1119 String fqName = pkgName + "." + className; 1120 String shownClassName = makeTwoRows(className); 1121 String lt = "removed"; 1122 if (linkType ==1) 1123 lt = "added"; 1124 String commentID = fqName + ".ctor_" + lt + "(" + type + ")"; 1125 if (!useOld) { 1126 reportFile.println("<TR BGCOLOR=\"" + bgcolor + "\" CLASS=\"TableRowColor\">"); 1127 reportFile.println(" <TD VALIGN=\"TOP\" WIDTH=\"25%\">"); 1128 reportFile.println(" <A NAME=\"" + commentID + "\"></A>"); // Named anchor 1129 } 1130 String shortType = simpleName(type); 1131 if (linkType == 0) { 1132 if (oldDocPrefix == null) { 1133 // No link 1134 reportFile.print(" <nobr>" + className); 1135 emitTypeWithParens(shortType); 1136 reportFile.println("</nobr>"); 1137 } else { 1138 writeCtorTableEntry(pkgName, className, 1139 type, 1, 1140 possibleComment, true); 1141 } 1142 } else if (linkType == 1) { 1143 // Link to HTML file for the package 1144 String memberRef = fqName.replace('.', '/'); 1145 // Deal with inner classes 1146 if (className.indexOf('.') != -1) { 1147 memberRef = pkgName + "."; 1148 memberRef = memberRef.replace('.', '/'); 1149 if (useOld) { 1150 // oldDocPrefix is non-null at this point 1151 memberRef = oldDocPrefix + memberRef + className; 1152 } else { 1153 memberRef = newDocPrefix + memberRef + className; 1154 } 1155 } else { 1156 if (useOld) { 1157 // oldDocPrefix is non-null at this point 1158 memberRef = oldDocPrefix + memberRef; 1159 } else { 1160 memberRef = newDocPrefix + memberRef; 1161 } 1162 } 1163 reportFile.print(" <nobr><A HREF=\"" + memberRef + ".html#" + className + 1164 "(" + type + ")\" target=\"_top\"><code>" + shownClassName + "</code></A>"); 1165 emitTypeWithParens(shortType); 1166 reportFile.println("</nobr>"); 1167 } 1168 if (!useOld) { 1169 reportFile.println(" </TD>"); 1170 emitComment(commentID, possibleComment, linkType); 1171 reportFile.println("</TR>"); 1172 } 1173 } 1174 1175 /** 1176 * Write a table entry for a changed constructor. 1177 */ writeCtorChangedTableEntry(String pkgName, String className, MemberDiff memberDiff)1178 public void writeCtorChangedTableEntry(String pkgName, String className, 1179 MemberDiff memberDiff) { 1180 String fqName = pkgName + "." + className; 1181 String newSignature = memberDiff.newType_; 1182 if (newSignature.compareTo("void") == 0) 1183 newSignature = ""; 1184 String commentID = fqName + ".ctor_changed(" + newSignature + ")"; 1185 reportFile.println("<TR BGCOLOR=\"" + bgcolor + "\" CLASS=\"TableRowColor\">"); 1186 reportFile.println(" <TD VALIGN=\"TOP\" WIDTH=\"25%\">"); 1187 reportFile.println(" <A NAME=\"" + commentID + "\"></A>"); // Named anchor 1188 String memberRef = fqName.replace('.', '/'); 1189 String shownClassName = makeTwoRows(className); 1190 // Deal with inner classes 1191 if (className.indexOf('.') != -1) { 1192 memberRef = pkgName + "."; 1193 memberRef = memberRef.replace('.', '/'); 1194 memberRef = newDocPrefix + memberRef + className; 1195 } else { 1196 memberRef = newDocPrefix + memberRef; 1197 } 1198 String newType = memberDiff.newType_; 1199 if (newType.compareTo("void") == 0) 1200 newType = ""; 1201 String shortNewType = simpleName(memberDiff.newType_); 1202 // Constructors have the linked name, then the type in parentheses. 1203 reportFile.print(" <nobr><A HREF=\"" + memberRef + ".html#" + className + "(" + newType + ")\" target=\"_top\"><code>"); 1204 reportFile.print(shownClassName); 1205 reportFile.print("</code></A>"); 1206 emitTypeWithParens(shortNewType); 1207 reportFile.println(" </nobr>"); 1208 reportFile.println(" </TD>"); 1209 1210 // Report changes in documentation 1211 if (reportDocChanges && memberDiff.documentationChange_ != null) { 1212 String oldMemberRef = null; 1213 String oldType = null; 1214 if (oldDocPrefix != null) { 1215 oldMemberRef = pkgName + "." + className; 1216 oldMemberRef = oldMemberRef.replace('.', '/'); 1217 if (className.indexOf('.') != -1) { 1218 oldMemberRef = pkgName + "."; 1219 oldMemberRef = oldMemberRef.replace('.', '/'); 1220 oldMemberRef = oldDocPrefix + oldMemberRef + className; 1221 } else { 1222 oldMemberRef = oldDocPrefix + oldMemberRef; 1223 } 1224 oldType = memberDiff.oldType_; 1225 if (oldType.compareTo("void") == 0) 1226 oldType = ""; 1227 } 1228 if (oldDocPrefix != null) 1229 memberDiff.documentationChange_ += "<A HREF=\"" + 1230 oldMemberRef + ".html#" + className + "(" + oldType + 1231 ")\" target=\"_self\"><code>old</code></A> to "; 1232 else 1233 memberDiff.documentationChange_ += "<font size=\"+1\"><code>old</code></font> to "; 1234 memberDiff.documentationChange_ += "<A HREF=\"" + memberRef + 1235 ".html#" + className + "(" + newType + 1236 ")\" target=\"_self\"><code>new</code></A>.<br>"; 1237 } 1238 1239 emitChanges(memberDiff, 0); 1240 emitComment(commentID, null, 2); 1241 1242 reportFile.println("</TR>"); 1243 } 1244 1245 /** 1246 * Write a table entry for a method. 1247 * 1248 * linkType: 0 - no link by default, 1 = link to Javadoc HTML file 1249 */ writeMethodTableEntry(String pkgName, String className, MethodAPI methodAPI, int linkType, String possibleComment, boolean useOld)1250 public void writeMethodTableEntry(String pkgName, String className, 1251 MethodAPI methodAPI, int linkType, 1252 String possibleComment, boolean useOld) { 1253 String fqName = pkgName + "." + className; 1254 String signature = methodAPI.getSignature(); 1255 String methodName = methodAPI.name_; 1256 String lt = "removed"; 1257 if (linkType ==1) 1258 lt = "added"; 1259 String commentID = fqName + "." + methodName + "_" + lt + "(" + signature + ")"; 1260 if (!useOld) { 1261 reportFile.println("<TR BGCOLOR=\"" + bgcolor + "\" CLASS=\"TableRowColor\">"); 1262 reportFile.println(" <TD VALIGN=\"TOP\" WIDTH=\"25%\">"); 1263 reportFile.println(" <A NAME=\"" + commentID + "\"></A>"); // Named anchor 1264 } 1265 if (signature.compareTo("void") == 0) 1266 signature = ""; 1267 String shortSignature = simpleName(signature); 1268 String returnType = methodAPI.returnType_; 1269 String shortReturnType = simpleName(returnType); 1270 if (linkType == 0) { 1271 if (oldDocPrefix == null) { 1272 // No link 1273 reportFile.print(" <nobr>"); 1274 emitType(shortReturnType); 1275 reportFile.print(" " + methodName); 1276 emitTypeWithParens(shortSignature); 1277 reportFile.println("</nobr>"); 1278 } else { 1279 writeMethodTableEntry(pkgName, className, 1280 methodAPI, 1, 1281 possibleComment, true); 1282 } 1283 } else if (linkType == 1) { 1284 // Link to HTML file for the package 1285 String memberRef = fqName.replace('.', '/'); 1286 // Deal with inner classes 1287 if (className.indexOf('.') != -1) { 1288 memberRef = pkgName + "."; 1289 memberRef = memberRef.replace('.', '/'); 1290 if (useOld) { 1291 // oldDocPrefix is non-null at this point 1292 memberRef = oldDocPrefix + memberRef + className; 1293 } else { 1294 memberRef = newDocPrefix + memberRef + className; 1295 } 1296 } else { 1297 if (useOld) { 1298 // oldDocPrefix is non-null at this point 1299 memberRef = oldDocPrefix + memberRef; 1300 } else { 1301 memberRef = newDocPrefix + memberRef; 1302 } 1303 } 1304 reportFile.print(" <nobr>"); 1305 emitType(shortReturnType); 1306 reportFile.print(" <A HREF=\"" + memberRef + ".html#" + methodName + 1307 "(" + signature + ")\" target=\"_top\"><code>" + methodName + "</code></A>"); 1308 emitTypeWithParens(shortSignature); 1309 reportFile.println("</nobr>"); 1310 } 1311 if (!useOld) { 1312 reportFile.println(" </TD>"); 1313 emitComment(commentID, possibleComment, linkType); 1314 reportFile.println("</TR>"); 1315 } 1316 } 1317 1318 /** 1319 * Write a table entry for a changed method. 1320 */ writeMethodChangedTableEntry(String pkgName, String className, MemberDiff memberDiff)1321 public void writeMethodChangedTableEntry(String pkgName, String className, 1322 MemberDiff memberDiff) { 1323 String memberName = memberDiff.name_; 1324 // Generally nowhere to break a member name anyway 1325 // String shownMemberName = makeTwoRows(memberName); 1326 String fqName = pkgName + "." + className; 1327 String newSignature = memberDiff.newSignature_; 1328 String commentID = fqName + "." + memberName + "_changed(" + newSignature + ")"; 1329 reportFile.println("<TR BGCOLOR=\"" + bgcolor + "\" CLASS=\"TableRowColor\">"); 1330 1331 reportFile.println(" <TD VALIGN=\"TOP\" WIDTH=\"25%\">"); 1332 reportFile.println(" <A NAME=\"" + commentID + "\"></A>"); // Named anchor 1333 String memberRef = fqName.replace('.', '/'); 1334 // Deal with inner classes 1335 if (className.indexOf('.') != -1) { 1336 memberRef = pkgName + "."; 1337 memberRef = memberRef.replace('.', '/'); 1338 memberRef = newDocPrefix + memberRef + className; 1339 } else { 1340 memberRef = newDocPrefix + memberRef; 1341 } 1342 // Javadoc generated HTML has no named anchors for methods 1343 // inherited from other classes, so link to the defining class' method. 1344 // Only copes with non-inner classes. 1345 if (className.indexOf('.') == -1 && 1346 memberDiff.modifiersChange_ != null && 1347 memberDiff.modifiersChange_.indexOf("but is now inherited from") != -1) { 1348 memberRef = memberDiff.inheritedFrom_; 1349 memberRef = memberRef.replace('.', '/'); 1350 memberRef = newDocPrefix + memberRef; 1351 } 1352 1353 String newReturnType = memberDiff.newType_; 1354 String shortReturnType = simpleName(newReturnType); 1355 String shortSignature = simpleName(newSignature); 1356 reportFile.print(" <nobr>"); 1357 emitTypeWithNoParens(shortReturnType); 1358 reportFile.print(" <A HREF=\"" + memberRef + ".html#" + 1359 memberName + "(" + newSignature + ")\" target=\"_top\"><code>"); 1360 reportFile.print(memberName); 1361 reportFile.print("</code></A>"); 1362 emitTypeWithParens(shortSignature); 1363 reportFile.println(" </nobr>"); 1364 reportFile.println(" </TD>"); 1365 1366 // Report changes in documentation 1367 if (reportDocChanges && memberDiff.documentationChange_ != null) { 1368 String oldMemberRef = null; 1369 String oldSignature = null; 1370 if (oldDocPrefix != null) { 1371 oldMemberRef = pkgName + "." + className; 1372 oldMemberRef = oldMemberRef.replace('.', '/'); 1373 if (className.indexOf('.') != -1) { 1374 oldMemberRef = pkgName + "."; 1375 oldMemberRef = oldMemberRef.replace('.', '/'); 1376 oldMemberRef = oldDocPrefix + oldMemberRef + className; 1377 } else { 1378 oldMemberRef = oldDocPrefix + oldMemberRef; 1379 } 1380 oldSignature = memberDiff.oldSignature_; 1381 } 1382 if (oldDocPrefix != null) 1383 memberDiff.documentationChange_ += "<A HREF=\"" + 1384 oldMemberRef + ".html#" + memberName + "(" + 1385 oldSignature + ")\" target=\"_self\"><code>old</code></A> to "; 1386 else 1387 memberDiff.documentationChange_ += "<code>old</code> to "; 1388 memberDiff.documentationChange_ += "<A HREF=\"" + memberRef + 1389 ".html#" + memberName + "(" + newSignature + 1390 ")\" target=\"_self\"><code>new</code></A>.<br>"; 1391 } 1392 1393 emitChanges(memberDiff, 1); 1394 // Get the comment from the parent class if more appropriate 1395 if (memberDiff.modifiersChange_ != null) { 1396 int parentIdx = memberDiff.modifiersChange_.indexOf("now inherited from"); 1397 if (parentIdx != -1) { 1398 // Change the commentID to pick up the appropriate method 1399 commentID = memberDiff.inheritedFrom_ + "." + memberName + 1400 "_changed(" + newSignature + ")"; 1401 } 1402 } 1403 emitComment(commentID, null, 2); 1404 1405 reportFile.println("</TR>"); 1406 } 1407 1408 /** 1409 * Write a table entry for a field. 1410 * 1411 * linkType: 0 - no link by default, 1 = link to Javadoc HTML file 1412 */ writeFieldTableEntry(String pkgName, String className, FieldAPI fieldAPI, int linkType, String possibleComment, boolean useOld)1413 public void writeFieldTableEntry(String pkgName, String className, 1414 FieldAPI fieldAPI, int linkType, 1415 String possibleComment, boolean useOld) { 1416 String fqName = pkgName + "." + className; 1417 // Fields can only appear in one table, so no need to specify _added etc 1418 String fieldName = fieldAPI.name_; 1419 // Generally nowhere to break a member name anyway 1420 // String shownFieldName = makeTwoRows(fieldName); 1421 String commentID = fqName + "." + fieldName; 1422 if (!useOld) { 1423 reportFile.println("<TR BGCOLOR=\"" + bgcolor + "\" CLASS=\"TableRowColor\">"); 1424 reportFile.println(" <TD VALIGN=\"TOP\" WIDTH=\"25%\">"); 1425 reportFile.println(" <A NAME=\"" + commentID + "\"></A>"); // Named anchor 1426 } 1427 String fieldType = fieldAPI.type_; 1428 if (fieldType.compareTo("void") == 0) 1429 fieldType = ""; 1430 String shortFieldType = simpleName(fieldType); 1431 if (linkType == 0) { 1432 if (oldDocPrefix == null) { 1433 // No link. 1434 reportFile.print(" "); 1435 emitType(shortFieldType); 1436 reportFile.println(" " + fieldName); 1437 } else { 1438 writeFieldTableEntry(pkgName, className, 1439 fieldAPI, 1, 1440 possibleComment, true); 1441 } 1442 } else if (linkType == 1) { 1443 // Link to HTML file for the package. 1444 String memberRef = fqName.replace('.', '/'); 1445 // Deal with inner classes 1446 if (className.indexOf('.') != -1) { 1447 memberRef = pkgName + "."; 1448 memberRef = memberRef.replace('.', '/'); 1449 if (useOld) 1450 memberRef = oldDocPrefix + memberRef + className; 1451 else 1452 memberRef = newDocPrefix + memberRef + className; 1453 } else { 1454 if (useOld) 1455 memberRef = oldDocPrefix + memberRef; 1456 else 1457 memberRef = newDocPrefix + memberRef; 1458 } 1459 reportFile.print(" <nobr>"); 1460 emitType(shortFieldType); 1461 reportFile.println(" <A HREF=\"" + memberRef + ".html#" + fieldName + 1462 "\" target=\"_top\"><code>" + fieldName + "</code></A></nobr>"); 1463 } 1464 if (!useOld) { 1465 reportFile.println(" </TD>"); 1466 emitComment(commentID, possibleComment, linkType); 1467 reportFile.println("</TR>"); 1468 } 1469 } 1470 1471 /** 1472 * Write a table entry for a changed field. 1473 */ writeFieldChangedTableEntry(String pkgName, String className, MemberDiff memberDiff)1474 public void writeFieldChangedTableEntry(String pkgName, String className, 1475 MemberDiff memberDiff) { 1476 String memberName = memberDiff.name_; 1477 // Generally nowhere to break a member name anyway 1478 // String shownMemberName = makeTwoRows(memberName); 1479 String fqName = pkgName + "." + className; 1480 // Fields have unique names in a class 1481 String commentID = fqName + "." + memberName; 1482 reportFile.println("<TR BGCOLOR=\"" + bgcolor + "\" CLASS=\"TableRowColor\">"); 1483 1484 reportFile.println(" <TD VALIGN=\"TOP\" WIDTH=\"25%\">"); 1485 reportFile.println(" <A NAME=\"" + commentID + "\"></A>"); // Named anchor 1486 String memberRef = fqName.replace('.', '/'); 1487 // Deal with inner classes 1488 if (className.indexOf('.') != -1) { 1489 memberRef = pkgName + "."; 1490 memberRef = memberRef.replace('.', '/'); 1491 memberRef = newDocPrefix + memberRef + className; 1492 } else { 1493 memberRef = newDocPrefix + memberRef; 1494 } 1495 // Javadoc generated HTML has no named anchors for fields 1496 // inherited from other classes, so link to the defining class' field. 1497 // Only copes with non-inner classes. 1498 if (className.indexOf('.') == -1 && 1499 memberDiff.modifiersChange_ != null && 1500 memberDiff.modifiersChange_.indexOf("but is now inherited from") != -1) { 1501 memberRef = memberDiff.inheritedFrom_; 1502 memberRef = memberRef.replace('.', '/'); 1503 memberRef = newDocPrefix + memberRef; 1504 } 1505 1506 String newType = memberDiff.newType_; 1507 String shortNewType = simpleName(newType); 1508 reportFile.print(" <nobr>"); 1509 emitTypeWithNoParens(shortNewType); 1510 reportFile.print(" <A HREF=\"" + memberRef + ".html#" + 1511 memberName + "\" target=\"_top\"><code>"); 1512 reportFile.print(memberName); 1513 reportFile.print("</code></font></A></nobr>"); 1514 reportFile.println(" </TD>"); 1515 1516 // Report changes in documentation 1517 if (reportDocChanges && memberDiff.documentationChange_ != null) { 1518 String oldMemberRef = null; 1519 if (oldDocPrefix != null) { 1520 oldMemberRef = pkgName + "." + className; 1521 oldMemberRef = oldMemberRef.replace('.', '/'); 1522 if (className.indexOf('.') != -1) { 1523 oldMemberRef = pkgName + "."; 1524 oldMemberRef = oldMemberRef.replace('.', '/'); 1525 oldMemberRef = oldDocPrefix + oldMemberRef + className; 1526 } else { 1527 oldMemberRef = oldDocPrefix + oldMemberRef; 1528 } 1529 } 1530 if (oldDocPrefix != null) 1531 memberDiff.documentationChange_ += "<A HREF=\"" + 1532 oldMemberRef + ".html#" + memberName + "\" target=\"_self\"><code>old</code></A> to "; 1533 else 1534 memberDiff.documentationChange_ += "<code>old</code> to "; 1535 memberDiff.documentationChange_ += "<A HREF=\"" + memberRef + 1536 ".html#" + memberName + "\" target=\"_self\"><code>new</code></A>.<br>"; 1537 } 1538 1539 emitChanges(memberDiff, 2); 1540 // Get the comment from the parent class if more appropriate 1541 if (memberDiff.modifiersChange_ != null) { 1542 int parentIdx = memberDiff.modifiersChange_.indexOf("now inherited from"); 1543 if (parentIdx != -1) { 1544 // Change the commentID to pick up the appropriate method 1545 commentID = memberDiff.inheritedFrom_ + "." + memberName; 1546 } 1547 } 1548 emitComment(commentID, null, 2); 1549 1550 reportFile.println("</TR>"); 1551 } 1552 1553 /** 1554 * Emit all changes associated with a MemberDiff as an entry in a table. 1555 * 1556 * @param memberType 0 = ctor, 1 = method, 2 = field 1557 */ emitChanges(MemberDiff memberDiff, int memberType)1558 public void emitChanges(MemberDiff memberDiff, int memberType){ 1559 reportFile.println(" <TD VALIGN=\"TOP\" WIDTH=\"30%\">"); 1560 boolean hasContent = false; 1561 // The type or return type changed 1562 if (memberDiff.oldType_.compareTo(memberDiff.newType_) != 0) { 1563 String shortOldType = simpleName(memberDiff.oldType_); 1564 String shortNewType = simpleName(memberDiff.newType_); 1565 if (memberType == 1) { 1566 reportFile.print("Change in return type from "); 1567 } else { 1568 reportFile.print("Change in type from "); 1569 } 1570 if (shortOldType.compareTo(shortNewType) == 0) { 1571 // The types differ in package name, so use the full name 1572 shortOldType = memberDiff.oldType_; 1573 shortNewType = memberDiff.newType_; 1574 } 1575 emitType(shortOldType); 1576 reportFile.print(" to "); 1577 emitType(shortNewType); 1578 reportFile.println(".<br>"); 1579 hasContent = true; 1580 } 1581 // The signatures changed - only used by methods 1582 if (memberType == 1 && 1583 memberDiff.oldSignature_ != null && 1584 memberDiff.newSignature_ != null && 1585 memberDiff.oldSignature_.compareTo(memberDiff.newSignature_) != 0) { 1586 String shortOldSignature = simpleName(memberDiff.oldSignature_); 1587 String shortNewSignature = simpleName(memberDiff.newSignature_); 1588 if (shortOldSignature.compareTo(shortNewSignature) == 0) { 1589 // The signatures differ in package names, so use the full form 1590 shortOldSignature = memberDiff.oldSignature_; 1591 shortNewSignature = memberDiff.newSignature_; 1592 } 1593 if (hasContent) 1594 reportFile.print(" "); 1595 reportFile.print("Change in signature from "); 1596 if (shortOldSignature.compareTo("") == 0) 1597 shortOldSignature = "void"; 1598 emitType(shortOldSignature); 1599 reportFile.print(" to "); 1600 if (shortNewSignature.compareTo("") == 0) 1601 shortNewSignature = "void"; 1602 emitType(shortNewSignature); 1603 reportFile.println(".<br>"); 1604 hasContent = true; 1605 } 1606 // The exceptions are only non-null in methods and constructors 1607 if (memberType != 2 && 1608 memberDiff.oldExceptions_ != null && 1609 memberDiff.newExceptions_ != null && 1610 memberDiff.oldExceptions_.compareTo(memberDiff.newExceptions_) != 0) { 1611 if (hasContent) 1612 reportFile.print(" "); 1613 // If either one of the exceptions has no spaces in it, or is 1614 // equal to "no exceptions", then just display the whole 1615 // exceptions texts. 1616 int spaceInOld = memberDiff.oldExceptions_.indexOf(" "); 1617 if (memberDiff.oldExceptions_.compareTo("no exceptions") == 0) 1618 spaceInOld = -1; 1619 int spaceInNew = memberDiff.newExceptions_.indexOf(" "); 1620 if (memberDiff.newExceptions_.compareTo("no exceptions") == 0) 1621 spaceInNew = -1; 1622 if (spaceInOld == -1 || spaceInNew == -1) { 1623 reportFile.print("Change in exceptions thrown from "); 1624 emitException(memberDiff.oldExceptions_); 1625 reportFile.print(" to " ); 1626 emitException(memberDiff.newExceptions_); 1627 reportFile.println(".<br>"); 1628 } else { 1629 // Too many exceptions become unreadable, so just show the 1630 // individual changes. Catch the case where exceptions are 1631 // just reordered. 1632 boolean firstChange = true; 1633 int numRemoved = 0; 1634 StringTokenizer stOld = new StringTokenizer(memberDiff.oldExceptions_, ", "); 1635 while (stOld.hasMoreTokens()) { 1636 String oldException = stOld.nextToken(); 1637 if (!memberDiff.newExceptions_.startsWith(oldException) && 1638 !(memberDiff.newExceptions_.indexOf(", " + oldException) != -1)) { 1639 if (firstChange) { 1640 reportFile.print("Change in exceptions: "); 1641 firstChange = false; 1642 } 1643 if (numRemoved != 0) 1644 reportFile.print(", "); 1645 emitException(oldException); 1646 numRemoved++; 1647 } 1648 } 1649 if (numRemoved == 1) 1650 reportFile.print(" was removed."); 1651 else if (numRemoved > 1) 1652 reportFile.print(" were removed."); 1653 1654 int numAdded = 0; 1655 StringTokenizer stNew = new StringTokenizer(memberDiff.newExceptions_, ", "); 1656 while (stNew.hasMoreTokens()) { 1657 String newException = stNew.nextToken(); 1658 if (!memberDiff.oldExceptions_.startsWith(newException) && 1659 !(memberDiff.oldExceptions_.indexOf(", " + newException) != -1)) { 1660 if (firstChange) { 1661 reportFile.print("Change in exceptions: "); 1662 firstChange = false; 1663 } 1664 if (numAdded != 0) 1665 reportFile.println(", "); 1666 else 1667 reportFile.println(" "); 1668 emitException(newException); 1669 numAdded++; 1670 } 1671 } 1672 if (numAdded == 1) 1673 reportFile.print(" was added"); 1674 else if (numAdded > 1) 1675 reportFile.print(" were added"); 1676 else if (numAdded == 0 && numRemoved == 0 && firstChange) 1677 reportFile.print("Exceptions were reordered"); 1678 reportFile.println(".<br>"); 1679 } 1680 // Note the changes between a comma-separated list of Strings 1681 hasContent = true; 1682 } 1683 1684 if (memberDiff.documentationChange_ != null) { 1685 if (hasContent) 1686 reportFile.print(" "); 1687 reportFile.print(memberDiff.documentationChange_); 1688 hasContent = true; 1689 } 1690 1691 // Last, so no need for a <br> 1692 if (memberDiff.modifiersChange_ != null) { 1693 if (hasContent) 1694 reportFile.print(" "); 1695 reportFile.println(memberDiff.modifiersChange_); 1696 hasContent = true; 1697 } 1698 reportFile.println(" </TD>"); 1699 } 1700 1701 /** 1702 * Emit a string which is an exception by surrounding it with 1703 * <code> tags. 1704 * If there is a space in the type, e.g. "String, File", then 1705 * surround it with parentheses too. Do not add <code> tags or 1706 * parentheses if the String is "no exceptions". 1707 */ emitException(String ex)1708 public void emitException(String ex) { 1709 if (ex.compareTo("no exceptions") == 0) { 1710 reportFile.print(ex); 1711 } else { 1712 if (ex.indexOf(' ') != -1) { 1713 reportFile.print("(<code>" + ex + "</code>)"); 1714 } else { 1715 reportFile.print("<code>" + ex + "</code>"); 1716 } 1717 } 1718 } 1719 1720 /** 1721 * Emit a string which is a type by surrounding it with <code> tags. 1722 * If there is a space in the type, e.g. "String, File", then 1723 * surround it with parentheses too. 1724 */ emitType(String type)1725 public void emitType(String type) { 1726 if (type.compareTo("") == 0) 1727 return; 1728 if (type.indexOf(' ') != -1) { 1729 reportFile.print("(<code>" + type + "</code>)"); 1730 } else { 1731 reportFile.print("<code>" + type + "</code>"); 1732 } 1733 } 1734 1735 /** 1736 * Emit a string which is a type by surrounding it with <code> tags. 1737 * Also surround it with parentheses too. Used to display methods' 1738 * parameters. 1739 * Suggestions for where a browser should break the 1740 * text are provided with <br> and <nobr> tags. 1741 */ emitTypeWithParens(String type)1742 public static void emitTypeWithParens(String type) { 1743 emitTypeWithParens(type, true); 1744 } 1745 1746 /** 1747 * Emit a string which is a type by surrounding it with <code> tags. 1748 * Also surround it with parentheses too. Used to display methods' 1749 * parameters. 1750 */ emitTypeWithParens(String type, boolean addBreaks)1751 public static void emitTypeWithParens(String type, boolean addBreaks) { 1752 if (type.compareTo("") == 0) 1753 reportFile.print("()"); 1754 else { 1755 int idx = type.indexOf(", "); 1756 if (!addBreaks || idx == -1) { 1757 reportFile.print("(<code>" + type + "</code>)"); 1758 } else { 1759 // Make the browser break text at reasonable places 1760 String sepType = null; 1761 StringTokenizer st = new StringTokenizer(type, ", "); 1762 while (st.hasMoreTokens()) { 1763 String p = st.nextToken(); 1764 if (sepType == null) 1765 sepType = p; 1766 else 1767 sepType += ",</nobr> " + p + "<nobr>"; 1768 } 1769 reportFile.print("(<code>" + sepType + "<nobr></code>)"); 1770 } 1771 } 1772 } 1773 1774 /** 1775 * Emit a string which is a type by surrounding it with <code> tags. 1776 * Do not surround it with parentheses. Used to display methods' return 1777 * types and field types. 1778 */ emitTypeWithNoParens(String type)1779 public static void emitTypeWithNoParens(String type) { 1780 if (type.compareTo("") != 0) 1781 reportFile.print("<code>" + type + "</code>"); 1782 } 1783 1784 /** 1785 * Return a String with the simple names of the classes in fqName. 1786 * "java.lang.String" becomes "String", 1787 * "java.lang.String, java.io.File" becomes "String, File" 1788 * and so on. If fqName is null, return null. If fqName is "", 1789 * return "". 1790 */ simpleName(String fqNames)1791 public static String simpleName(String fqNames) { 1792 if (fqNames == null) 1793 return null; 1794 String res = ""; 1795 boolean hasContent = false; 1796 // We parse the string step by step to ensure we take 1797 // fqNames that contains generics parameter in a whole. 1798 ArrayList<String> fqNamesList = new ArrayList<String>(); 1799 int genericParametersDepth = 0; 1800 StringBuffer buffer = new StringBuffer(); 1801 for (int i=0; i<fqNames.length(); i++) { 1802 char c = fqNames.charAt(i); 1803 if ('<' == c) { 1804 genericParametersDepth++; 1805 } 1806 if ('>' == c) { 1807 genericParametersDepth--; 1808 } 1809 if (',' != c || genericParametersDepth > 0) { 1810 buffer.append(c); 1811 } else if (',' == c) { 1812 fqNamesList.add(buffer.toString().trim()); 1813 buffer = new StringBuffer(buffer.length()); 1814 } 1815 } 1816 fqNamesList.add(buffer.toString().trim()); 1817 for (String fqName : fqNamesList) { 1818 // Assume this will be used inside a <nobr> </nobr> set of tags. 1819 if (hasContent) 1820 res += ", "; 1821 hasContent = true; 1822 // Look for text within '<' and '>' in case this is a invocation of a generic 1823 1824 int firstBracket = fqName.indexOf('<'); 1825 int lastBracket = fqName.lastIndexOf('>'); 1826 String genericParameter = null; 1827 if (firstBracket != -1 && lastBracket != -1) { 1828 genericParameter = simpleName(fqName.substring(firstBracket + 1, lastBracket)); 1829 fqName = fqName.substring(0, firstBracket); 1830 } 1831 1832 int lastDot = fqName.lastIndexOf('.'); 1833 if (lastDot < 0) { 1834 res += fqName; // Already as simple as possible 1835 } else { 1836 res += fqName.substring(lastDot+1); 1837 } 1838 if (genericParameter != null) 1839 res += "<" + genericParameter + ">"; 1840 } 1841 return res; 1842 } 1843 1844 /** 1845 * Find any existing comment and emit it. Add the new comment to the 1846 * list of new comments. The first instance of the string "@first" in 1847 * a hand-written comment will be replaced by the first sentence from 1848 * the associated doc block, if such exists. Also replace @link by 1849 * an HTML link. 1850 * 1851 * @param commentID The identifier for this comment. 1852 * @param possibleComment A possible comment from another source. 1853 * @param linkType 0 = remove, 1 = add, 2 = change 1854 */ emitComment(String commentID, String possibleComment, int linkType)1855 public void emitComment(String commentID, String possibleComment, 1856 int linkType) { 1857 if (noCommentsOnRemovals && linkType == 0) { 1858 reportFile.println(" <TD> </TD>"); 1859 return; 1860 } 1861 if (noCommentsOnAdditions && linkType == 1) { 1862 reportFile.println(" <TD> </TD>"); 1863 return; 1864 } 1865 if (noCommentsOnChanges && linkType == 2) { 1866 reportFile.println(" <TD> </TD>"); 1867 return; 1868 } 1869 1870 // We have to use this global hash table because the *Diff classes 1871 // do not store the possible comment from the new *API object. 1872 if (!noCommentsOnChanges && possibleComment == null) { 1873 possibleComment = (String)Comments.allPossibleComments.get(commentID); 1874 } 1875 // Just use the first sentence of the possible comment. 1876 if (possibleComment != null) { 1877 int fsidx = RootDocToXML.endOfFirstSentence(possibleComment, false); 1878 if (fsidx != -1 && fsidx != 0) 1879 possibleComment = possibleComment.substring(0, fsidx+1); 1880 } 1881 1882 String comment = Comments.getComment(existingComments_, commentID); 1883 if (comment.compareTo(Comments.placeHolderText) == 0) { 1884 if (possibleComment != null && 1885 possibleComment.indexOf("InsertOtherCommentsHere") == -1) 1886 reportFile.println(" <TD VALIGN=\"TOP\">" + possibleComment + "</TD>"); 1887 else 1888 reportFile.println(" <TD> </TD>"); 1889 } else { 1890 int idx = comment.indexOf("@first"); 1891 if (idx == -1) { 1892 reportFile.println(" <TD VALIGN=\"TOP\">" + Comments.convertAtLinks(comment, "", null, null) + "</TD>"); 1893 } else { 1894 reportFile.print(" <TD VALIGN=\"TOP\">" + comment.substring(0, idx)); 1895 if (possibleComment != null && 1896 possibleComment.indexOf("InsertOtherCommentsHere") == -1) 1897 reportFile.print(possibleComment); 1898 reportFile.println(comment.substring(idx + 6) + "</TD>"); 1899 } 1900 } 1901 SingleComment newComment = new SingleComment(commentID, comment); 1902 newComments_.addComment(newComment); 1903 } 1904 1905 /** Write the end of a table. */ writeTableEnd()1906 public void writeTableEnd() { 1907 reportFile.println("</TABLE>"); 1908 reportFile.println(" "); 1909 } 1910 1911 /** Write a newline out. */ writeText()1912 public void writeText() { 1913 reportFile.println(); 1914 } 1915 1916 /** Write some text out. */ writeText(String text)1917 public void writeText(String text) { 1918 reportFile.println(text); 1919 } 1920 1921 /** Emit some non-breaking space for indentation. */ indent(int indent)1922 public void indent(int indent) { 1923 for (int i = 0; i < indent; i++) 1924 reportFile.print(" "); 1925 } 1926 1927 /** 1928 * The name of the file to which the top-level HTML file is written, 1929 * and also the name of the subdirectory where most of the HTML appears, 1930 * and also a prefix for the names of some of the files in that 1931 * subdirectory. 1932 */ 1933 static String reportFileName = "changes"; 1934 1935 /** 1936 * The suffix of the file to which the HTML output is currently being 1937 * written. 1938 */ 1939 static String reportFileExt = ".html"; 1940 1941 /** 1942 * The file to which the HTML output is currently being written. 1943 */ 1944 static PrintWriter reportFile = null; 1945 1946 /** 1947 * The object which represents the top of the tree of differences 1948 * between two APIs. It is only used indirectly when emitting a 1949 * navigation bar. 1950 */ 1951 static APIDiff apiDiff = null; 1952 1953 /** 1954 * If set, then do not suggest comments for removals from the first 1955 * sentence of the doc block of the old API. 1956 */ 1957 public static boolean noCommentsOnRemovals = false; 1958 1959 /** 1960 * If set, then do not suggest comments for additions from the first 1961 * sentence of the doc block of the new API. 1962 */ 1963 public static boolean noCommentsOnAdditions = false; 1964 1965 /** 1966 * If set, then do not suggest comments for changes from the first 1967 * sentence of the doc block of the new API. 1968 */ 1969 public static boolean noCommentsOnChanges = false; 1970 1971 /** 1972 * If set, then report changes in documentation (Javadoc comments) 1973 * between the old and the new API. The default is that this is not set. 1974 */ 1975 public static boolean reportDocChanges = false; 1976 1977 /** 1978 * Define the prefix for HTML links to the existing set of Javadoc- 1979 * generated documentation for the new API. E.g. For J2SE1.3.x, use 1980 * "https://java.sun.com/j2se/1.3/docs/api/" 1981 */ 1982 public static String newDocPrefix = "../"; 1983 1984 /** 1985 * Define the prefix for HTML links to the existing set of Javadoc- 1986 * generated documentation for the old API. 1987 */ 1988 public static String oldDocPrefix = null; 1989 1990 /** To generate statistical output, set this to true. */ 1991 public static boolean doStats = false; 1992 1993 /** 1994 * The destination directory for output files. 1995 */ 1996 public static String outputDir = null; 1997 1998 /** 1999 * The destination directory for comments files (if not specified, uses outputDir) 2000 */ 2001 public static String commentsDir = null; 2002 2003 /** 2004 * The title used on the first page of the report. By default, this is 2005 * "API Differences Between <name of old API> and 2006 * <name of new API>". It can be 2007 * set by using the -doctitle option. 2008 */ 2009 public static String docTitle = null; 2010 2011 /** 2012 * The browser window title for the report. By default, this is 2013 * "API Differences Between <name of old API> and 2014 * <name of new API>". It can be 2015 * set by using the -windowtitle option. 2016 */ 2017 public static String windowTitle = null; 2018 2019 /** The desired background color for JDiff tables. */ 2020 static final String bgcolor = "#FFFFFF"; 2021 2022 /** Set to enable debugging output. */ 2023 private static final boolean trace = false; 2024 2025 } 2026