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