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