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