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