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