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