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