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