Home | History | Annotate | Download | only in jdiff
      1 package jdiff;
      2 
      3 import java.util.*;
      4 import java.io.*;
      5 
      6 /**
      7  * Emit HTML indexes which appear in the bottom left frame in the report.
      8  * All indexes are links to JDiff-generated pages.
      9  *
     10  * See the file LICENSE.txt for copyright details.
     11  * @author Matthew Doar, mdoar (at) pobox.com
     12  */
     13 public class HTMLIndexes {
     14 
     15     /** Constructor. */
     16     public HTMLIndexes(HTMLReportGenerator h) {
     17         h_ = h;
     18     }
     19 
     20     /** The HTMLReportGenerator instance used to write HTML. */
     21     private HTMLReportGenerator h_ = null;
     22 
     23     /** Emit all the bottom left frame index files. */
     24     public void emitAllBottomLeftFiles(String packagesIndexName,
     25                                        String classesIndexName,
     26                                        String constructorsIndexName,
     27                                        String methodsIndexName,
     28                                        String fieldsIndexName,
     29                                        String allDiffsIndexName,
     30                                        APIDiff apiDiff) {
     31 
     32         // indexType values: 0 = removals only, 1 = additions only,
     33         // 2 = changes only. 3 = all differences. Run all differences
     34         // first for all program element types so we know whether there
     35         // are any removals etc for the allDiffs index.
     36         emitBottomLeftFile(packagesIndexName, apiDiff, 3, "Package");
     37         emitBottomLeftFile(classesIndexName, apiDiff, 3, "Class");
     38         emitBottomLeftFile(constructorsIndexName, apiDiff, 3, "Constructor");
     39         emitBottomLeftFile(methodsIndexName, apiDiff, 3, "Method");
     40         emitBottomLeftFile(fieldsIndexName, apiDiff, 3, "Field");
     41         // The allindex must be done last, since it uses the results from
     42         // the previous ones
     43         emitBottomLeftFile(allDiffsIndexName, apiDiff, 3, "All");
     44         // Now generate the other indexes
     45         for (int indexType = 0; indexType < 3; indexType++) {
     46             emitBottomLeftFile(packagesIndexName, apiDiff, indexType, "Package");
     47             emitBottomLeftFile(classesIndexName, apiDiff, indexType, "Class");
     48             emitBottomLeftFile(constructorsIndexName, apiDiff, indexType, "Constructor");
     49             emitBottomLeftFile(methodsIndexName, apiDiff, indexType, "Method");
     50             emitBottomLeftFile(fieldsIndexName, apiDiff, indexType, "Field");
     51             emitBottomLeftFile(allDiffsIndexName, apiDiff, indexType, "All");
     52         }
     53         if (missingSincesFile != null)
     54             missingSincesFile.close();
     55     }
     56 
     57     /**
     58      * Emit a single bottom left frame with the given kind of differences for
     59      * the given program element type in an alphabetical index.
     60      *
     61      * @param indexBaseName The base name of the index file.
     62      * @param apiDiff The root element containing all the API differences.
     63      * @param indexType 0 = removals only, 1 = additions only,
     64      *                  2 = changes only, 3 = all differences,
     65      * @param programElementType "Package", "Class", "Constructor",
     66      *                           "Method", "Field" or "All".
     67      */
     68     public void emitBottomLeftFile(String indexBaseName,
     69                                    APIDiff apiDiff, int indexType,
     70                                    String programElementType) {
     71         String filename = indexBaseName;
     72         try {
     73             String title = "Indexes";
     74             if (indexType == 0) {
     75                 filename += "_removals" + h_.reportFileExt;
     76                 title = programElementType + " Removals Index";
     77             } else if (indexType == 1) {
     78                 filename += "_additions" + h_.reportFileExt;
     79                 title = programElementType + " Additions Index";
     80             } else if (indexType == 2) {
     81                 filename += "_changes" + h_.reportFileExt;
     82                 title = programElementType + " Changes Index";
     83             } else if (indexType == 3) {
     84                 filename += "_all" + h_.reportFileExt;
     85                 title = programElementType + " Differences Index";
     86             }
     87 
     88             FileOutputStream fos = new FileOutputStream(filename);
     89             h_.reportFile = new PrintWriter(fos);
     90             h_.writeStartHTMLHeader();
     91             h_.writeHTMLTitle(title);
     92             h_.writeStyleSheetRef();
     93             h_.writeText("</HEAD>");
     94             h_.writeText("<BODY>");
     95 
     96             if (programElementType.compareTo("Package") == 0) {
     97                 emitPackagesIndex(apiDiff, indexType);
     98             } else if (programElementType.compareTo("Class") == 0) {
     99                 emitClassesIndex(apiDiff, indexType);
    100             } else if (programElementType.compareTo("Constructor") == 0) {
    101                 emitConstructorsIndex(apiDiff, indexType);
    102             } else if (programElementType.compareTo("Method") == 0) {
    103                 emitMethodsIndex(apiDiff, indexType);
    104             } else if (programElementType.compareTo("Field") == 0) {
    105                 emitFieldsIndex(apiDiff, indexType);
    106             } else if (programElementType.compareTo("All") == 0) {
    107                 emitAllDiffsIndex(apiDiff, indexType);
    108             } else{
    109                 System.out.println("Error: unknown program element type.");
    110                 System.exit(3);
    111             }
    112 
    113             h_.writeHTMLFooter();
    114             h_.reportFile.close();
    115         } catch(IOException e) {
    116             System.out.println("IO Error while attempting to create " + filename);
    117             System.out.println("Error: " + e.getMessage());
    118             System.exit(1);
    119         }
    120     }
    121 
    122     /**
    123      * Generate a small header of letters which link to each section, but
    124      * do not emit a linked letter for the current section. Finish the list off
    125      * with a link to the top of the index.
    126      * Caching the results of this function would save about 10s with large APIs.
    127      */
    128     private void generateLetterIndex(List list, char currChar, boolean larger) {
    129         if (larger)
    130             return; // Currently not using the larger functionality
    131         int size = -2;
    132 	if (larger)
    133             size = -1;
    134         Iterator iter = null;
    135         if (isAllNames)
    136             iter = allNames.iterator();
    137         else
    138             iter = list.iterator();
    139         char oldsw = '\0';
    140         while (iter.hasNext()) {
    141             Index entry = (Index)(iter.next());
    142             char sw = entry.name_.charAt(0);
    143             char swu = Character.toUpperCase(sw);
    144             if (swu != Character.toUpperCase(oldsw)) {
    145                 // Don't emit a reference to the current letter
    146                 if (Character.toUpperCase(sw) != Character.toUpperCase(currChar)) {
    147                     if (swu == '_') {
    148                         h_.writeText("<a href=\"#" + swu + "\"><font size=\"" + size + "\">" + "underscore" + "</font></a> ");
    149                     } else {
    150                         h_.writeText("<a href=\"#" + swu + "\"><font size=\"" + size + "\">" + swu + "</font></a> ");
    151                     }
    152                 }
    153                 oldsw = sw;
    154             }
    155         }
    156         h_.writeText(" <a href=\"#topheader\"><font size=\"" + size + "\">TOP</font></a>");
    157         h_.writeText("<p><div style=\"line-height:1.5em;color:black\">");
    158     }
    159 
    160     /**
    161      * Emit a header for an index, including suitable links for removed,
    162      * added and changes sub-indexes.
    163      */
    164     private void emitIndexHeader(String indexName, int indexType,
    165                                  boolean hasRemovals,
    166                                  boolean hasAdditions, boolean hasChanges) {
    167         String linkIndexName = indexName.toLowerCase();
    168         boolean isAllDiffs = false;
    169         if (indexName.compareTo("All Differences") == 0) {
    170             linkIndexName = "alldiffs";
    171             isAllDiffs = true;
    172         }
    173         h_.writeText("<a NAME=\"topheader\"></a>"); // Named anchor
    174         h_.writeText("<table summary=\"Index for " +  indexName + "\" width=\"100%\" class=\"index\" border=\"0\" cellspacing=\"0\" cellpadding=\"0\">");
    175         h_.writeText("  <tr>");
    176         h_.writeText("  <th class=\"indexHeader\">");
    177         h_.writeText("    Filter the Index:");
    178         h_.writeText("  </th>");
    179         h_.writeText("  </tr>");
    180         h_.writeText("  <tr>");
    181         h_.writeText("  <td class=\"indexText\" style=\"line-height:1.5em;padding-left:2em;\">");
    182 //        h_.writeText("  <div style=\"line-height:1.25em;padding-left:1em;>\">");
    183 //        h_.writeText("  <FONT SIZE=\"-1\">");
    184         // The index name is also a hidden link to the *index_all page
    185         if (indexType == 3) {
    186              h_.writeText("<b>" + indexName + "</b>"); }
    187         else if (isAllDiffs) {
    188             h_.writeText("<a href=\"" + linkIndexName + "_index_all" + h_.reportFileExt + "\" class=\"hiddenlink\">" + indexName + "</a>");
    189         }
    190         else {
    191             h_.writeText("<a href=\"" + linkIndexName + "_index_all" + h_.reportFileExt + "\" class=\"staysblack\">All " + indexName + "</a>");
    192         }
    193 //        h_.writeText("  </FONT>");
    194 
    195         h_.writeText("  <br>");
    196 //        h_.writeText("  <FONT SIZE=\"-1\">");
    197         if (hasRemovals) {
    198           if (indexType == 0) {
    199             h_.writeText("<b>Removals</b>");
    200           } else {
    201             h_.writeText("<A HREF=\"" + linkIndexName + "_index_removals" + h_.reportFileExt + "\" class=\"hiddenlink\">Removals</A>");
    202           }
    203         } else {
    204             h_.writeText("<font color=\"#999999\">Removals</font>");
    205         }
    206 //        h_.writeText("  </FONT>");
    207 
    208         h_.writeText("  <br>");
    209 //      h_.writeText("  <FONT SIZE=\"-1\">");
    210         if (hasAdditions) {
    211           if (indexType == 1) {
    212             h_.writeText("<b>Additions</b>");
    213           } else {
    214             h_.writeText("<A HREF=\"" + linkIndexName + "_index_additions" + h_.reportFileExt + "\"class=\"hiddenlink\">Additions</A>");
    215           }
    216         } else {
    217             h_.writeText("<font color=\"#999999\">Additions</font>");
    218         }
    219 //        h_.writeText("  </FONT>");
    220 
    221         h_.writeText("  <br>");
    222 //         h_.writeText("  <FONT SIZE=\"-1\">");
    223         if (hasChanges) {
    224           if (indexType == 2) {
    225             h_.writeText("<b>Changes</b>");
    226           } else {
    227             h_.writeText("<A HREF=\"" + linkIndexName + "_index_changes" + h_.reportFileExt + "\"class=\"hiddenlink\">Changes</A>");
    228           }
    229         } else {
    230             h_.writeText("<font color=\"#999999\">Changes</font>");
    231         }
    232 //        h_.writeText("  </FONT>");
    233 //        h_.writeText("  </div>");
    234         h_.writeText("  </td>");
    235         h_.writeText("  </tr>");
    236         h_.writeText("</table>");
    237         h_.writeText("<font size=\"-2\"><strong>Bold</strong>&nbsp;indicates&nbsp;New;&nbsp;<strike>Strike</strike>&nbsp;indicates&nbsp;deleted</font>");
    238         h_.writeText("  </br>");
    239 
    240     }
    241 
    242     /** Emit the index of packages, which appears in the bottom left frame. */
    243     public void emitPackagesIndex(APIDiff apiDiff, int indexType) {
    244         // Add all the names of packages to a new list, to be sorted later
    245         packageNames = new ArrayList(); // Index[]
    246         boolean hasRemovals = false;
    247         if (apiDiff.packagesRemoved.size() != 0)
    248             hasRemovals = true;
    249         boolean hasAdditions = false;
    250         if (apiDiff.packagesAdded.size() != 0)
    251             hasAdditions = true;
    252         boolean hasChanges = false;
    253         if (apiDiff.packagesChanged.size() != 0)
    254             hasChanges = true;
    255         recordDiffs(hasRemovals, hasAdditions, hasChanges);
    256         Iterator iter = apiDiff.packagesRemoved.iterator();
    257         while ((indexType == 3 || indexType == 0) && iter.hasNext()) {
    258             PackageAPI pkg = (PackageAPI)(iter.next());
    259             packageNames.add(new Index(pkg.name_, 0));
    260         }
    261         iter = apiDiff.packagesAdded.iterator();
    262         while ((indexType == 3 || indexType == 1) && iter.hasNext()) {
    263             PackageAPI pkg = (PackageAPI)(iter.next());
    264             packageNames.add(new Index(pkg.name_, 1));
    265         }
    266         iter = apiDiff.packagesChanged.iterator();
    267         while ((indexType == 3 || indexType == 2) && iter.hasNext()) {
    268             PackageDiff pkg = (PackageDiff)(iter.next());
    269             packageNames.add(new Index(pkg.name_, 2));
    270         }
    271         Collections.sort(packageNames);
    272 
    273         // No letter index needed for packages
    274 
    275         // Now emit all the package names and links to their respective files
    276         emitIndexHeader("Packages", indexType, hasRemovals, hasAdditions, hasChanges);
    277 
    278         // Extra line because no index is emitted
    279         h_.writeText("<br>");
    280 
    281         // Package names are unique, so no need to check for duplicates.
    282         iter = packageNames.iterator();
    283         char oldsw = '\0';
    284         while (iter.hasNext()) {
    285             Index pkg = (Index)(iter.next());
    286             oldsw = emitPackageIndexEntry(pkg, oldsw);
    287         }
    288     }
    289 
    290     /**
    291      * Emit an index entry for a package.
    292      * Package names are unique, so no need to check for duplicates.
    293      */
    294     public char emitPackageIndexEntry(Index pkg, char oldsw) {
    295         char res = oldsw;
    296         // See if we are in a new section of the alphabet
    297         char sw = pkg.name_.charAt(0);
    298         if (Character.toUpperCase(sw) != Character.toUpperCase(oldsw)) {
    299             // No need to emit section letters for packages
    300             res = sw;
    301             // Add the named anchor for this new letter
    302             h_.writeText("<A NAME=\"" + Character.toUpperCase(res) + "\"></A>");
    303         }
    304         // Package names are unique, so no need to check for duplicates.
    305         if (pkg.changeType_ == 0) {
    306             h_.writeText("<A HREF=\"" + h_.reportFileName + "-summary" + h_.reportFileExt + "#" + pkg.name_  + "\" class=\"hiddenlink\" target=\"rightframe\"><strike>" + pkg.name_ + "</strike></A><br>");
    307         } else if (pkg.changeType_ == 1) {
    308             h_.writeText("<A HREF=\"" + h_.reportFileName + "-summary" + h_.reportFileExt + "#" + pkg.name_  + "\" class=\"hiddenlink\" target=\"rightframe\"><b>" + pkg.name_ + "</b></A><br>");
    309         } else if (pkg.changeType_ == 2) {
    310             h_.writeText("<A HREF=\"pkg_" + pkg.name_ + h_.reportFileExt + "\" class=\"hiddenlink\" target=\"rightframe\">" + pkg.name_ + "</A><br>");
    311         }
    312         return res;
    313     }
    314 
    315     /**
    316      * Emit all the entries and links for the given iterator
    317      * to their respective files.
    318      */
    319     public void emitIndexEntries(Iterator iter) {
    320         char oldsw = '\0';
    321         int multipleMarker = 0;
    322         Index currIndex = null; // The entry which is emitted
    323         while (iter.hasNext()) {
    324             // The next entry after the current one
    325             Index nextIndex = (Index)(iter.next());
    326             if (currIndex == null) {
    327                 currIndex = nextIndex; // Prime the pump
    328             } else {
    329                 if (nextIndex.name_.compareTo(currIndex.name_) == 0) {
    330                     // It's a duplicate index, so emit the name and then
    331                     // the indented entries
    332                     if (multipleMarker == 0)
    333                         multipleMarker = 1; // Start of a duplicate index
    334                     else if (multipleMarker == 1)
    335                         multipleMarker = 2; // Inside a duplicate index
    336                     oldsw = emitIndexEntry(currIndex, oldsw, multipleMarker);
    337                 } else {
    338                     if (multipleMarker == 1)
    339                         multipleMarker = 2; // Inside a duplicate index
    340                     oldsw = emitIndexEntry(currIndex, oldsw, multipleMarker);
    341                     multipleMarker = 0; // Not in a duplicate index any more
    342                 }
    343                 currIndex = nextIndex;
    344             }
    345         }
    346         // Emit the last entry left in currIndex
    347         if (multipleMarker == 1)
    348             multipleMarker = 2; // Inside a duplicate index
    349         if (currIndex != null)
    350             oldsw = emitIndexEntry(currIndex, oldsw, multipleMarker);
    351     }
    352 
    353     /**
    354      * Whether to log all missing @since tags to a file or not.
    355      * If false, just warn the user.
    356      */
    357     public static boolean logMissingSinces = true;
    358 
    359     /** The file used to output details of missing @since tags. */
    360     public static PrintWriter missingSincesFile = null;
    361 
    362     /**
    363      * Emit elements in the given iterator which were added and
    364      * missing @since tags.
    365      */
    366     public void emitMissingSinces(Iterator iter) {
    367 //        if (!logMissingSinces)
    368 //            return;
    369         if (missingSincesFile == null) {
    370             String sinceFileName = h_.outputDir + JDiff.DIR_SEP + "missingSinces.txt";
    371             try {
    372                 FileOutputStream fos = new FileOutputStream(sinceFileName);
    373                 missingSincesFile = new PrintWriter(fos);
    374             } catch (IOException e) {
    375                 System.out.println("IO Error while attempting to create " + sinceFileName);
    376                 System.out.println("Error: " + e.getMessage());
    377                 System.exit(1);
    378             }
    379         }
    380         while (iter.hasNext()) {
    381             Index currIndex = (Index)(iter.next());
    382             // Only display information about added elements
    383             if (currIndex.changeType_ != 1)
    384                 continue;
    385             String programElementType = currIndex.ename_;
    386             String details = null;
    387             if (programElementType.compareTo("class") == 0) {
    388                 details = currIndex.pkgName_ + "." + currIndex.name_;
    389                 if (currIndex.isInterface_)
    390                     details = details + " Interface";
    391                 else
    392                     details = details + " Class";
    393             } else if (programElementType.compareTo("constructor") == 0) {
    394                 details = currIndex.pkgName_ + "." + currIndex.name_ + " Constructor (" + currIndex.type_ + ")";
    395             } else if (programElementType.compareTo("method") == 0) {
    396                 details = currIndex.pkgName_ + "." + currIndex.className_ + " " + "Method " + currIndex.name_ + "(" + currIndex.type_ + ")";
    397             } else if (programElementType.compareTo("field") == 0) {
    398                 details = currIndex.pkgName_ + "." + currIndex.className_ + " " + "Field " + currIndex.name_;
    399             } else {
    400                 System.out.println("Error: unknown program element type");
    401                 System.exit(3);
    402             }
    403             if (currIndex.doc_ == null) {
    404                 if (logMissingSinces)
    405                     missingSincesFile.println("NO DOC BLOCK: " + details);
    406                 else
    407                     System.out.println("Warning: the doc block for the new element: " + details + " is missing, so there is no @since tag");
    408             } else if (currIndex.doc_.indexOf("@since") != -1) {
    409                 if (logMissingSinces)
    410                     missingSincesFile.println("OK: " + details);
    411             } else {
    412                 if (logMissingSinces)
    413                     missingSincesFile.println("MISSING @SINCE TAG: " + details);
    414                 else
    415                     System.out.println("Warning: the doc block for the new element: " + details + " is missing an @since tag");
    416             }
    417         }
    418     }
    419 
    420     /**
    421      * Emit a single entry and the link to its file.
    422      *
    423      * @param programElementType "Class", "Constructor",
    424      *                           "Method", or "Field".
    425      */
    426     public char emitIndexEntry(Index currIndex, char oldsw, int multipleMarker) {
    427         String programElementType = currIndex.ename_;
    428         if (programElementType.compareTo("class") == 0) {
    429             return emitClassIndexEntry(currIndex, oldsw, multipleMarker);
    430         } else if (programElementType.compareTo("constructor") == 0) {
    431             return emitCtorIndexEntry(currIndex, oldsw, multipleMarker);
    432         } else if (programElementType.compareTo("method") == 0) {
    433             return emitMethodIndexEntry(currIndex, oldsw, multipleMarker);
    434         } else if (programElementType.compareTo("field") == 0) {
    435             return emitFieldIndexEntry(currIndex, oldsw, multipleMarker);
    436         } else {
    437             System.out.println("Error: unknown program element type");
    438             System.exit(3);
    439         }
    440         return '\0';
    441     }
    442 
    443     /** Emit the index of classes, which appears in the bottom left frame. */
    444     public void emitClassesIndex(APIDiff apiDiff, int indexType) {
    445         // Add all the names of classes to a new list, to be sorted later
    446         classNames = new ArrayList(); // Index[]
    447         boolean hasRemovals = false;
    448         boolean hasAdditions = false;
    449         boolean hasChanges = false;
    450         Iterator iter = apiDiff.packagesChanged.iterator();
    451         while (iter.hasNext()) {
    452             PackageDiff pkgDiff = (PackageDiff)(iter.next());
    453             if (pkgDiff.classesRemoved.size() != 0)
    454                 hasRemovals = true;
    455             if (pkgDiff.classesAdded.size() != 0)
    456                 hasAdditions = true;
    457             if (pkgDiff.classesChanged.size() != 0)
    458                 hasChanges = true;
    459             recordDiffs(hasRemovals, hasAdditions, hasChanges);
    460             String pkgName = pkgDiff.name_;
    461             Iterator iterClass = pkgDiff.classesRemoved.iterator();
    462             while ((indexType == 3 || indexType == 0) && iterClass.hasNext()) {
    463                 ClassAPI cls = (ClassAPI)(iterClass.next());
    464                 classNames.add(new Index(cls.name_, 0, pkgName, cls.isInterface_));
    465             }
    466             iterClass = pkgDiff.classesAdded.iterator();
    467             while ((indexType == 3 || indexType == 1) && iterClass.hasNext()) {
    468                 ClassAPI cls = (ClassAPI)(iterClass.next());
    469                 Index idx = new Index(cls.name_, 1, pkgName, cls.isInterface_);
    470                 idx.doc_ = cls.doc_; // Used for checking @since
    471                 classNames.add(idx);
    472             }
    473             iterClass = pkgDiff.classesChanged.iterator();
    474             while ((indexType == 3 || indexType == 2) && iterClass.hasNext()) {
    475                 ClassDiff cls = (ClassDiff)(iterClass.next());
    476                 classNames.add(new Index(cls.name_, 2, pkgName, cls.isInterface_));
    477             }
    478         }
    479         Collections.sort(classNames);
    480         emitIndexHeader("Classes", indexType, hasRemovals, hasAdditions, hasChanges);
    481         emitIndexEntries(classNames.iterator());
    482         if (indexType == 1)
    483             emitMissingSinces(classNames.iterator());
    484     }
    485 
    486     /** Emit an index entry for a class. */
    487     public char emitClassIndexEntry(Index cls, char oldsw,
    488                                     int multipleMarker) {
    489         char res = oldsw;
    490         String className = cls.pkgName_ + "." + cls.name_;
    491         String classRef = cls.pkgName_ + "." + cls.name_;
    492         boolean isInterface = cls.isInterface_;
    493         // See if we are in a new section of the alphabet
    494         char sw = cls.name_.charAt(0);
    495         if (Character.toUpperCase(sw) != Character.toUpperCase(oldsw)) {
    496             res = sw;
    497             // Add the named anchor for this new letter
    498             h_.writeText("<A NAME=\"" + Character.toUpperCase(res) + "\"></A>");
    499             if (sw == '_')
    500                 h_.writeText("<br><b>underscore</b>&nbsp;");
    501             else
    502                 h_.writeText("<br><font size=\"+2\">" + Character.toUpperCase(sw) + "</font>&nbsp;");
    503             generateLetterIndex(classNames, sw, false);
    504         }
    505         // Deal with displaying duplicate indexes
    506         if (multipleMarker == 1) {
    507             h_.writeText("<i>" + cls.name_ + "</i><br>");
    508         }
    509         if (multipleMarker != 0)
    510             h_.indent(INDENT_SIZE);
    511         if (cls.changeType_ == 0) {
    512             // Emit a reference to the correct place for the class in the
    513             // JDiff page for the package
    514             h_.writeText("<A HREF=\"pkg_" + cls.pkgName_ + h_.reportFileExt +
    515                          "#" + cls.name_ + "\" class=\"hiddenlink\" target=\"rightframe\"><strike>" + cls.name_ + "</strike></A><br>");
    516         } else if (cls.changeType_ == 1) {
    517             String cn = cls.name_;
    518             if (multipleMarker != 0)
    519                 cn = cls.pkgName_;
    520             if (isInterface)
    521                 h_.writeText("<A HREF=\"pkg_" + cls.pkgName_ + h_.reportFileExt + "#" + cls.name_ + "\" class=\"hiddenlink\" target=\"rightframe\"><b><i>" + cn + "</i></b></A><br>");
    522             else
    523                 h_.writeText("<A HREF=\"pkg_" + cls.pkgName_ + h_.reportFileExt + "#" + cls.name_ + "\" class=\"hiddenlink\" target=\"rightframe\"><b>" + cn + "</b></A><br>");
    524         } else if (cls.changeType_ == 2) {
    525             String cn = cls.name_;
    526             if (multipleMarker != 0)
    527                 cn = cls.pkgName_;
    528             if (isInterface)
    529                 h_.writeText("<A HREF=\"" + classRef + h_.reportFileExt + "\" class=\"hiddenlink\" target=\"rightframe\"><i>" + cn + "</i></A><br>");
    530             else
    531                 h_.writeText("<A HREF=\"" + classRef + h_.reportFileExt + "\" class=\"hiddenlink\" target=\"rightframe\">" + cn + "</A><br>");
    532         }
    533         return res;
    534     }
    535 
    536     /**
    537      * Emit the index of all constructors, which appears in the bottom left
    538      * frame.
    539      */
    540     public void emitConstructorsIndex(APIDiff apiDiff, int indexType) {
    541         // Add all the names of constructors to a new list, to be sorted later
    542         ctorNames = new ArrayList(); // Index[]
    543         boolean hasRemovals = false;
    544         boolean hasAdditions = false;
    545         boolean hasChanges = false;
    546         Iterator iter = apiDiff.packagesChanged.iterator();
    547         while (iter.hasNext()) {
    548             PackageDiff pkgDiff = (PackageDiff)(iter.next());
    549             String pkgName = pkgDiff.name_;
    550             Iterator iterClass = pkgDiff.classesChanged.iterator();
    551             while (iterClass.hasNext()) {
    552                 ClassDiff classDiff = (ClassDiff)(iterClass.next());
    553                 if (classDiff.ctorsRemoved.size() != 0)
    554                     hasRemovals = true;
    555                 if (classDiff.ctorsAdded.size() != 0)
    556                     hasAdditions = true;
    557                 if (classDiff.ctorsChanged.size() != 0)
    558                     hasChanges = true;
    559                 recordDiffs(hasRemovals, hasAdditions, hasChanges);
    560                 String className = classDiff.name_;
    561                 Iterator iterCtor = classDiff.ctorsRemoved.iterator();
    562                 while ((indexType == 3 || indexType == 0) && iterCtor.hasNext()) {
    563                     ConstructorAPI ctor = (ConstructorAPI)(iterCtor.next());
    564                     ctorNames.add(new Index(className, 0, pkgName, ctor.type_));
    565                 }
    566                 iterCtor = classDiff.ctorsAdded.iterator();
    567                 while ((indexType == 3 || indexType == 1) && iterCtor.hasNext()) {
    568                     ConstructorAPI ctor = (ConstructorAPI)(iterCtor.next());
    569                     Index idx = new Index(className, 1, pkgName, ctor.type_);
    570                     idx.doc_ = ctor.doc_; // Used for checking @since
    571                     ctorNames.add(idx);
    572                 }
    573                 iterCtor = classDiff.ctorsChanged.iterator();
    574                 while ((indexType == 3 || indexType == 2) && iterCtor.hasNext()) {
    575                     MemberDiff ctor = (MemberDiff)(iterCtor.next());
    576                     ctorNames.add(new Index(className, 2, pkgName, ctor.newType_));
    577                 }
    578             }
    579         }
    580         Collections.sort(ctorNames);
    581         emitIndexHeader("Constructors", indexType, hasRemovals, hasAdditions, hasChanges);
    582         emitIndexEntries(ctorNames.iterator());
    583         if (indexType == 1)
    584             emitMissingSinces(ctorNames.iterator());
    585     }
    586 
    587     /** Emit an index entry for a constructor. */
    588     public char emitCtorIndexEntry(Index ctor, char oldsw, int multipleMarker) {
    589         char res = oldsw;
    590         String className = ctor.pkgName_ + "." + ctor.name_;
    591         String memberRef = ctor.pkgName_ + "." + ctor.name_;
    592         String type = ctor.type_;
    593         if (type.compareTo("void") == 0)
    594             type = "";
    595         String shownType = HTMLReportGenerator.simpleName(type);
    596         // See if we are in a new section of the alphabet
    597         char sw = ctor.name_.charAt(0);
    598         if (Character.toUpperCase(sw) != Character.toUpperCase(oldsw)) {
    599             res = sw;
    600             // Add the named anchor for this new letter
    601             h_.writeText("<A NAME=\"" + Character.toUpperCase(res) + "\"></A>");
    602             if (sw == '_')
    603                 h_.writeText("<br><b>underscore</b>&nbsp;");
    604             else
    605                 h_.writeText("<br><font size=\"+2\">" + Character.toUpperCase(sw) + "</font>&nbsp;");
    606             generateLetterIndex(ctorNames, sw, false);
    607         }
    608         // Deal with displaying duplicate indexes
    609         if (multipleMarker == 1) {
    610             h_.writeText("<i>" + ctor.name_ + "</i><br>");
    611         }
    612         if (multipleMarker != 0)
    613             h_.indent(INDENT_SIZE);
    614         // Deal with each type of difference
    615         // The output displayed for unique or duplicate entries is the same
    616         // for constructors.
    617         if (ctor.changeType_ == 0) {
    618             String commentID = className + ".ctor_removed(" + type + ")";
    619             h_.writeText("<nobr><A HREF=\"" + memberRef + h_.reportFileExt + "#" + commentID + "\" class=\"hiddenlink\" target=\"rightframe\"><strike>" + ctor.name_ + "</strike>");
    620             h_.emitTypeWithParens(shownType, false);
    621             h_.writeText("</A></nobr>&nbsp;constructor<br>");
    622         } else if (ctor.changeType_ == 1) {
    623             String commentID = className + ".ctor_added(" + type + ")";
    624             h_.writeText("<nobr><A HREF=\"" + memberRef + h_.reportFileExt + "#" + commentID + "\" class=\"hiddenlink\" target=\"rightframe\"><b>" + ctor.name_ + "</b>");
    625             h_.emitTypeWithParens(shownType, false);
    626             h_.writeText("</A></nobr>&nbsp;constructor<br>");
    627         } else if (ctor.changeType_ == 2) {
    628             String commentID = className + ".ctor_changed(" + type + ")";
    629             h_.writeText("<nobr><A HREF=\"" + memberRef + h_.reportFileExt + "#" + commentID + "\" class=\"hiddenlink\" target=\"rightframe\">" + ctor.name_);
    630             h_.emitTypeWithParens(shownType, false);
    631             h_.writeText("</A></nobr>&nbsp;constructor<br>");
    632         }
    633         return res;
    634     }
    635 
    636     /**
    637      * Emit the index of all methods, which appears in the bottom left frame.
    638      */
    639     public void emitMethodsIndex(APIDiff apiDiff, int indexType) {
    640         // Add all the names of methods to a new list, to be sorted later
    641         methNames = new ArrayList(); // Index[]
    642         boolean hasRemovals = false;
    643         boolean hasAdditions = false;
    644         boolean hasChanges = false;
    645         Iterator iter = apiDiff.packagesChanged.iterator();
    646         while (iter.hasNext()) {
    647             PackageDiff pkgDiff = (PackageDiff)(iter.next());
    648             String pkgName = pkgDiff.name_;
    649             Iterator iterClass = pkgDiff.classesChanged.iterator();
    650             while (iterClass.hasNext()) {
    651                 ClassDiff classDiff = (ClassDiff)(iterClass.next());
    652                 if (classDiff.methodsRemoved.size() != 0)
    653                     hasRemovals = true;
    654                 if (classDiff.methodsAdded.size() != 0)
    655                     hasAdditions = true;
    656                 if (classDiff.methodsChanged.size() != 0)
    657                     hasChanges = true;
    658                 recordDiffs(hasRemovals, hasAdditions, hasChanges);
    659                 String className = classDiff.name_;
    660                 Iterator iterMeth = classDiff.methodsRemoved.iterator();
    661                 while ((indexType == 3 || indexType == 0) && iterMeth.hasNext()) {
    662                     MethodAPI meth = (MethodAPI)(iterMeth.next());
    663                     methNames.add(new Index(meth.name_, 0, pkgName, className, meth.getSignature()));
    664                 }
    665                 iterMeth = classDiff.methodsAdded.iterator();
    666                 while ((indexType == 3 || indexType == 1) && iterMeth.hasNext()) {
    667                     MethodAPI meth = (MethodAPI)(iterMeth.next());
    668                     Index idx = new Index(meth.name_, 1, pkgName, className, meth.getSignature());
    669                     idx.doc_ = meth.doc_; // Used for checking @since
    670                     methNames.add(idx);
    671                 }
    672                 iterMeth = classDiff.methodsChanged.iterator();
    673                 while ((indexType == 3 || indexType == 2) && iterMeth.hasNext()) {
    674                     MemberDiff meth = (MemberDiff)(iterMeth.next());
    675                     methNames.add(new Index(meth.name_, 2, pkgName, className, meth.newSignature_));
    676                 }
    677             }
    678         }
    679         Collections.sort(methNames);
    680         emitIndexHeader("Methods", indexType, hasRemovals, hasAdditions, hasChanges);
    681         emitIndexEntries(methNames.iterator());
    682         if (indexType == 1)
    683             emitMissingSinces(methNames.iterator());
    684     }
    685 
    686     /** Emit an index entry for a method. */
    687     public char emitMethodIndexEntry(Index meth, char oldsw,
    688                                      int multipleMarker) {
    689         char res = oldsw;
    690         String className = meth.pkgName_ + "." + meth.className_;
    691         String memberRef = meth.pkgName_ + "." + meth.className_;
    692         String type = meth.type_;
    693         if (type.compareTo("void") == 0)
    694             type = "";
    695         String shownType = HTMLReportGenerator.simpleName(type);
    696         // See if we are in a new section of the alphabet
    697         char sw = meth.name_.charAt(0);
    698         if (Character.toUpperCase(sw) != Character.toUpperCase(oldsw)) {
    699             res = sw;
    700             // Add the named anchor for this new letter
    701             h_.writeText("<A NAME=\"" + Character.toUpperCase(res) + "\"></A>");
    702             if (sw == '_')
    703                 h_.writeText("<br><b>underscore</b>&nbsp;");
    704             else
    705                 h_.writeText("<br><font size=\"+2\">" + Character.toUpperCase(sw) + "</font>&nbsp;");
    706             generateLetterIndex(methNames, sw, false);
    707         }
    708         // Deal with displaying duplicate indexes
    709         if (multipleMarker == 1) {
    710             h_.writeText("<i>" + meth.name_ + "</i><br>");
    711         }
    712         if (multipleMarker != 0)
    713             h_.indent(INDENT_SIZE);
    714         // Deal with each type of difference
    715         if (meth.changeType_ == 0) {
    716             String commentID = className + "." + meth.name_ + "_removed(" + type + ")";
    717             if (multipleMarker == 0) {
    718                 h_.writeText("<nobr><A HREF=\"" + memberRef + h_.reportFileExt + "#" + commentID + "\" class=\"hiddenlink\" target=\"rightframe\"><strike>" + meth.name_ + "</strike>");
    719                 h_.emitTypeWithParens(shownType, false);
    720             } else {
    721                 h_.writeText("<nobr><A HREF=\"" + memberRef + h_.reportFileExt + "#" + commentID + "\" class=\"hiddenlink\" target=\"rightframe\">type&nbsp;<strike>");
    722                 h_.emitTypeWithParens(shownType, false);
    723                 h_.writeText("</strike>&nbsp;in&nbsp;" + className);
    724             }
    725             h_.writeText("</A></nobr><br>");
    726         } else if (meth.changeType_ == 1) {
    727             String commentID = className + "." + meth.name_ + "_added(" + type + ")";
    728             if (multipleMarker == 0) {
    729                 h_.writeText("<nobr><A HREF=\"" + memberRef + h_.reportFileExt + "#" + commentID + "\" class=\"hiddenlink\" target=\"rightframe\"><b>" + meth.name_ + "</b>");
    730                 h_.emitTypeWithParens(shownType, false);
    731             } else {
    732                 h_.writeText("<nobr><A HREF=\"" + memberRef + h_.reportFileExt + "#" + commentID + "\" class=\"hiddenlink\" target=\"rightframe\">type&nbsp;<b>");
    733                 h_.emitTypeWithParens(shownType, false);
    734                 h_.writeText("</b>&nbsp;in&nbsp;" + className);
    735             }
    736             h_.writeText("</A></nobr><br>");
    737         } else if (meth.changeType_ == 2) {
    738             String commentID = className + "." + meth.name_ + "_changed(" + type + ")";
    739             if (multipleMarker == 0) {
    740                 h_.writeText("<nobr><A HREF=\"" + memberRef + h_.reportFileExt + "#" + commentID + "\" class=\"hiddenlink\" target=\"rightframe\">" + meth.name_);
    741                 h_.emitTypeWithParens(shownType, false);
    742             } else {
    743                 h_.writeText("<nobr><A HREF=\"" + memberRef + h_.reportFileExt + "#" + commentID + "\" class=\"hiddenlink\" target=\"rightframe\">type&nbsp;");
    744                 h_.emitTypeWithParens(shownType, false);
    745                 h_.writeText("&nbsp;in&nbsp;" + className);
    746             }
    747             h_.writeText("</A></nobr><br>");
    748         }
    749         return res;
    750     }
    751 
    752     /**
    753      * Emit the index of all fields, which appears in the bottom left frame.
    754      */
    755     public void emitFieldsIndex(APIDiff apiDiff, int indexType) {
    756         // Add all the names of fields to a new list, to be sorted later
    757         fieldNames = new ArrayList(); // Index[]
    758         boolean hasRemovals = false;
    759         boolean hasAdditions = false;
    760         boolean hasChanges = false;
    761         Iterator iter = apiDiff.packagesChanged.iterator();
    762         while (iter.hasNext()) {
    763             PackageDiff pkgDiff = (PackageDiff)(iter.next());
    764             String pkgName = pkgDiff.name_;
    765             Iterator iterClass = pkgDiff.classesChanged.iterator();
    766             while (iterClass.hasNext()) {
    767                 ClassDiff classDiff = (ClassDiff)(iterClass.next());
    768                 if (classDiff.fieldsRemoved.size() != 0)
    769                     hasRemovals = true;
    770                 if (classDiff.fieldsAdded.size() != 0)
    771                     hasAdditions = true;
    772                 if (classDiff.fieldsChanged.size() != 0)
    773                     hasChanges = true;
    774                 recordDiffs(hasRemovals, hasAdditions, hasChanges);
    775                 String className = classDiff.name_;
    776                 Iterator iterField = classDiff.fieldsRemoved.iterator();
    777                 while ((indexType == 3 || indexType == 0) && iterField.hasNext()) {
    778                     FieldAPI fld = (FieldAPI)(iterField.next());
    779                     fieldNames.add(new Index(fld.name_, 0, pkgName, className, fld.type_, true));
    780                 }
    781                 iterField = classDiff.fieldsAdded.iterator();
    782                 while ((indexType == 3 || indexType == 1) && iterField.hasNext()) {
    783                     FieldAPI fld = (FieldAPI)(iterField.next());
    784                     Index idx = new Index(fld.name_, 1, pkgName, className, fld.type_, true);
    785                     idx.doc_ = fld.doc_; // Used for checking @since
    786                     fieldNames.add(idx);
    787                 }
    788                 iterField = classDiff.fieldsChanged.iterator();
    789                 while ((indexType == 3 || indexType == 2) && iterField.hasNext()) {
    790                     MemberDiff fld = (MemberDiff)(iterField.next());
    791                     fieldNames.add(new Index(fld.name_, 2, pkgName, className, fld.newType_, true));
    792                 }
    793             }
    794         }
    795         Collections.sort(fieldNames);
    796         emitIndexHeader("Fields", indexType, hasRemovals, hasAdditions, hasChanges);
    797         emitIndexEntries(fieldNames.iterator());
    798         if (indexType == 1)
    799             emitMissingSinces(fieldNames.iterator());
    800     }
    801 
    802     /** Emit an index entry for a field. */
    803     public char emitFieldIndexEntry(Index fld, char oldsw,
    804                                     int multipleMarker) {
    805         char res = oldsw;
    806         String className = fld.pkgName_ + "." + fld.className_;
    807         String memberRef = fld.pkgName_ + "." + fld.className_;
    808         String type = fld.type_;
    809         if (type.compareTo("void") == 0)
    810             type = "";
    811         String shownType = HTMLReportGenerator.simpleName(type);
    812         // See if we are in a new section of the alphabet
    813         char sw = fld.name_.charAt(0);
    814         if (Character.toUpperCase(sw) != Character.toUpperCase(oldsw)) {
    815             res = sw;
    816             // Add the named anchor for this new letter
    817             h_.writeText("<A NAME=\"" + Character.toUpperCase(res) + "\"></A>");
    818             if (sw == '_')
    819                 h_.writeText("<br><b>underscore</b>&nbsp;");
    820             else
    821                 h_.writeText("<br><font size=\"+2\">" + Character.toUpperCase(sw) + "</font>&nbsp;");
    822             generateLetterIndex(fieldNames, sw, false);
    823         }
    824         // Deal with displaying duplicate indexes
    825         if (multipleMarker == 1) {
    826             h_.writeText("<i>" + fld.name_ + "</i><br>");
    827         }
    828         if (multipleMarker != 0) {
    829 // More context than this is helpful here: h_.indent(INDENT_SIZE);
    830             h_.writeText("&nbsp;in&nbsp;");
    831         }
    832         // Deal with each type of difference
    833         if (fld.changeType_ == 0) {
    834             String commentID = className + "." + fld.name_;
    835             if (multipleMarker == 0) {
    836                 h_.writeText("<nobr><A HREF=\"" + memberRef + h_.reportFileExt + "#" + commentID + "\" class=\"hiddenlink\" target=\"rightframe\"><strike>" + fld.name_ + "</strike></A>");
    837                 h_.writeText("</nobr><br>");
    838             } else {
    839                 h_.writeText("<nobr><A HREF=\"" + memberRef + h_.reportFileExt + "#" + commentID + "\" class=\"hiddenlink\" target=\"rightframe\"><strike>" + className + "</strike></A>");
    840                 h_.writeText("</nobr><br>");
    841             }
    842         } else if (fld.changeType_ == 1) {
    843             String commentID = className + "." + fld.name_;
    844             if (multipleMarker == 0) {
    845                 h_.writeText("<nobr><A HREF=\"" + memberRef + h_.reportFileExt + "#" + commentID + "\" class=\"hiddenlink\" target=\"rightframe\">" + fld.name_ + "</A>");
    846                 h_.writeText("</nobr><br>");
    847             } else {
    848                 h_.writeText("<nobr><A HREF=\"" + memberRef + h_.reportFileExt + "#" + commentID + "\" class=\"hiddenlink\" target=\"rightframe\">" + className + "</A>");
    849                 h_.writeText("</nobr><br>");
    850             }
    851         } else if (fld.changeType_ == 2) {
    852             String commentID = className + "." + fld.name_;
    853             if (multipleMarker == 0) {
    854                 h_.writeText("<nobr><A HREF=\"" + memberRef + h_.reportFileExt + "#" + commentID + "\" class=\"hiddenlink\" target=\"rightframe\">" + fld.name_ + "</A>");
    855                 h_.writeText("</nobr><br>");
    856             } else {
    857                 h_.writeText("<nobr><A HREF=\"" + memberRef + h_.reportFileExt + "#" + commentID + "\" class=\"hiddenlink\" target=\"rightframe\">" + className + "</A>");
    858                 h_.writeText("</nobr><br>");
    859             }
    860         }
    861         return res;
    862     }
    863 
    864     /**
    865      * Emit the index of all changes, which appears in the bottom left frame.
    866      * Has to be run after all the other indexes have been written, since it
    867      * uses data from when they are generated.
    868      */
    869     public void emitAllDiffsIndex(APIDiff apiDiff, int indexType) {
    870         allNames = new ArrayList(); // Index[]
    871         // Add all the changes into one big list, and sort it by name,
    872         // ignoring case
    873         allNames.addAll(packageNames);
    874         allNames.addAll(classNames);
    875         allNames.addAll(ctorNames);
    876         allNames.addAll(methNames);
    877         allNames.addAll(fieldNames);
    878         // Compares two Index objects' names, ignoring case differences.
    879         Collections.sort(allNames);
    880 
    881         emitIndexHeader("All Differences", indexType, atLeastOneRemoval,
    882                         atLeastOneAddition, atLeastOneChange);
    883 
    884         // Tell generateLetterIndex to use allNames as the list when
    885         // using the other methods to generate the indexes.
    886         isAllNames = true;
    887 
    888         // Now emit a line for each entry in the list in the appropriate
    889         // format for each program element
    890         Iterator iter = allNames.iterator();
    891         char oldsw = '\0';
    892         int multipleMarker = 0;
    893         Index currIndex = null; // The entry which is emitted
    894         while (iter.hasNext()) {
    895             // The next entry after the current one
    896             Index nextIndex = (Index)(iter.next());
    897             if (currIndex == null) {
    898                 currIndex = nextIndex; // Prime the pump
    899             } else {
    900                 if (nextIndex.name_.compareTo(currIndex.name_) == 0) {
    901                     // It's a duplicate index, so emit the name and then
    902                     // the indented entries
    903                     if (multipleMarker == 0)
    904                         multipleMarker = 1; // Start of a duplicate index
    905                     else if (multipleMarker == 1)
    906                         multipleMarker = 2; // Inside a duplicate index
    907                     oldsw = emitIndexEntryForAny(currIndex, oldsw, multipleMarker);
    908                 } else {
    909                     if (multipleMarker == 1)
    910                         multipleMarker = 2; // Inside a duplicate index
    911                     oldsw = emitIndexEntryForAny(currIndex, oldsw, multipleMarker);
    912                     multipleMarker = 0; // Not in a duplicate index any more
    913                 }
    914                 currIndex = nextIndex;
    915             }
    916         }
    917         // Emit the last entry left in currIndex
    918         if (multipleMarker == 1)
    919             multipleMarker = 2; // Inside a duplicate index
    920         if (currIndex != null)
    921             oldsw = emitIndexEntryForAny(currIndex, oldsw, multipleMarker);
    922 
    923         // Tell generateLetterIndex to stop using allNames as the list when
    924         // using the other methods to generate the indexes.
    925         isAllNames = false;
    926     }
    927 
    928     /** Call the appropriate *IndexEntry method for each entry. */
    929     public char emitIndexEntryForAny(Index currIndex, char oldsw,
    930                                      int multipleMarker) {
    931         if (currIndex.ename_.compareTo("package") == 0) {
    932             h_.writeText("<!-- Package " + currIndex.name_ + " -->");
    933             return emitPackageIndexEntry(currIndex, oldsw);
    934         } else if (currIndex.ename_.compareTo("class") == 0) {
    935             h_.writeText("<!-- Class " + currIndex.name_ + " -->");
    936             return emitClassIndexEntry(currIndex, oldsw, multipleMarker);
    937         } else if (currIndex.ename_.compareTo("constructor") == 0) {
    938             h_.writeText("<!-- Constructor " + currIndex.name_ + " -->");
    939             return emitCtorIndexEntry(currIndex, oldsw, multipleMarker);
    940         } else if (currIndex.ename_.compareTo("method") == 0) {
    941             h_.writeText("<!-- Method " + currIndex.name_ + " -->");
    942             return emitMethodIndexEntry(currIndex, oldsw, multipleMarker);
    943         } else if (currIndex.ename_.compareTo("field") == 0) {
    944             h_.writeText("<!-- Field " + currIndex.name_ + " -->");
    945             return emitFieldIndexEntry(currIndex, oldsw, multipleMarker);
    946         }
    947         return '\0';
    948     }
    949 
    950     /** The list of all changes for all program elements. */
    951     private List allNames = null; // Index[]
    952 
    953     /** The list of all package changes. */
    954     private List packageNames = null; // Index[]
    955 
    956     /** The list of all class changes. */
    957     private List classNames = null; // Index[]
    958 
    959     /** The list of all constructor changes. */
    960     private List ctorNames = null; // Index[]
    961 
    962     /** The list of all method changes. */
    963     private List methNames = null; // Index[]
    964 
    965     /** The list of all field changes. */
    966     private List fieldNames = null; // Index[]
    967 
    968     /** If set, then use allNames to generate the letter indexes. */
    969     private boolean isAllNames = false;
    970 
    971     /**
    972      * If any of the parameters are set, then set the respective atLeastOne
    973      * variable, used to generate the links at the top of the allDiffs index.
    974      * Never unset an atLeastOne variable.
    975      */
    976     private void recordDiffs(boolean hasRemovals, boolean hasAdditions,
    977                         boolean hasChanges) {
    978         if (hasRemovals)
    979             atLeastOneRemoval = true;
    980         if (hasAdditions)
    981             atLeastOneAddition = true;
    982         if (hasChanges)
    983             atLeastOneChange = true;
    984     }
    985 
    986     /** Set if there was at least one removal in the entire API. */
    987     private boolean atLeastOneRemoval = false;
    988 
    989     /** Set if there was at least one addition in the entire API. */
    990     private boolean atLeastOneAddition = false;
    991 
    992     /** Set if there was at least one change in the entire API. */
    993     private boolean atLeastOneChange = false;
    994 
    995     /**
    996      * The number of non-breaking spaces to indent a duplicate indexes'
    997      * entries by.
    998      */
    999     private final int INDENT_SIZE = 2;
   1000 }
   1001 
   1002 /**
   1003  * Class used to produce indexes of packages and classes.
   1004  *
   1005  * See the file LICENSE.txt for copyright details.
   1006  * @author Matthew Doar, mdoar (at) pobox.com
   1007  */
   1008 class Index implements Comparable {
   1009 
   1010     /** The name of the program element this Index object represents. */
   1011     public String ename_ = null;
   1012 
   1013     /** Name of the changed package, class or member. */
   1014     public String name_ = null;
   1015 
   1016     /** Type of change. 0 = remove, 1 = add, 2 = change. */
   1017     public int changeType_;
   1018 
   1019     /** Name of the changed package if name_ is a class name. */
   1020     public String pkgName_ = null;
   1021 
   1022     /** Set if this class is an interface. */
   1023     public boolean isInterface_= false;
   1024 
   1025     /** The doc block of added elements, default is null. */
   1026     public String doc_ = null;
   1027 
   1028     /**
   1029      * The new member type. For methods, this is the signature.
   1030      */
   1031     public String type_ = null;
   1032 
   1033     /**
   1034      * The class name. Only used by methods.
   1035      */
   1036     public String className_ = null;
   1037 
   1038     /** Constructor for packages. */
   1039     public Index(String name, int changeType) {
   1040         ename_ = "package";
   1041         name_ = name;
   1042         changeType_ = changeType;
   1043     }
   1044 
   1045     /** Constructor for classes. */
   1046     public Index(String name, int changeType, String pkgName, boolean isInterface) {
   1047         ename_ = "class";
   1048         name_ = name;
   1049         changeType_ = changeType;
   1050         pkgName_ = pkgName;
   1051         isInterface_ = isInterface;
   1052     }
   1053 
   1054     /** Constructor for constructors. */
   1055     public Index(String name, int changeType, String pkgName, String type) {
   1056         ename_ = "constructor";
   1057         name_ = name;
   1058         changeType_ = changeType;
   1059         pkgName_ = pkgName;
   1060         type_  = type;
   1061     }
   1062 
   1063     /** Constructor for methods. */
   1064     public Index(String name, int changeType, String pkgName,
   1065                  String className, String type) {
   1066         ename_ = "method";
   1067         name_ = name;
   1068         changeType_ = changeType;
   1069         pkgName_ = pkgName;
   1070         className_ = className;
   1071         type_  = type;
   1072     }
   1073 
   1074     /**
   1075      * Constructor for fields.
   1076      *
   1077      * The boolean <code>fld</code> is simply there to differentiate this
   1078      * constructor from the one for methods.
   1079      */
   1080     public Index(String name, int changeType, String pkgName,
   1081                  String className, String type, boolean fld) {
   1082         ename_ = "field";
   1083         name_ = name;
   1084         changeType_ = changeType;
   1085         pkgName_ = pkgName;
   1086         className_ = className;
   1087         type_  = type;
   1088     }
   1089 
   1090 
   1091     /** Compare two Index objects by their simple names, ignoring case. */
   1092     public int compareTo(Object o) {
   1093         return name_.compareToIgnoreCase(((Index)o).name_);
   1094     }
   1095 
   1096 }
   1097 
   1098