]> gitweb.fperrin.net Git - Dictionary.git/blob - jars/icu4j-4_8_1_1/tools/build/src/com/ibm/icu/dev/tool/docs/CheckAPI.java
Added flags.
[Dictionary.git] / jars / icu4j-4_8_1_1 / tools / build / src / com / ibm / icu / dev / tool / docs / CheckAPI.java
1 /**
2 *******************************************************************************
3 * Copyright (C) 2004-2010, International Business Machines Corporation and         *
4 * others. All Rights Reserved.                                                *
5 *******************************************************************************
6 */
7
8 /**
9  * Generate a list of ICU's public APIs, sorted by qualified name and signature
10  * public APIs are all non-internal, non-package apis in com.ibm.icu.[lang|math|text|util].
11  * For each API, list
12  * - public, package, protected, or private (PB PK PT PR)
13  * - static or non-static (STK NST)
14  * - final or non-final (FN NF)
15  * - synchronized or non-synchronized (SYN NSY)
16  * - stable, draft, deprecated, obsolete (ST DR DP OB)
17  * - abstract or non-abstract (AB NA)
18  * - constructor, member, field (C M F)
19  *
20  * Requires JDK 1.4.2 or later
21  * 
22  * Sample invocation:
23  * c:/j2sdk1.4.2/bin/javadoc 
24  *   -classpath c:/jd2sk1.4.2/lib/tools.jar 
25  *   -doclet com.ibm.icu.dev.tool.docs.CheckAPI 
26  *   -docletpath c:/doug/cvsproj/icu4j/src 
27  *   -sourcepath c:/eclipse2.1/workspace2/icu4j/src 
28  *   -compare c:/doug/cvsproj/icu4j/src/com/ibm/icu/dev/tool/docs/api2_6_1.txt 
29  *   -output foo 
30  *   com.ibm.icu.text
31  *
32  * todo: separate generation of data files (which requires taglet) from 
33  * comparison and report generation (which does not require it)
34  * todo: provide command-line control of filters of which subclasses/packages to process
35  * todo: record full inheritance heirarchy, not just immediate inheritance 
36  * todo: allow for aliasing comparisons (force (pkg.)*class to be treated as though it 
37  * were in a different pkg/class heirarchy (facilitates comparison of icu4j and java)
38  */
39
40 package com.ibm.icu.dev.tool.docs;
41
42 import java.io.BufferedReader;
43 import java.io.BufferedWriter;
44 import java.io.File;
45 import java.io.FileInputStream;
46 import java.io.FileNotFoundException;
47 import java.io.FileOutputStream;
48 import java.io.IOException;
49 import java.io.InputStream;
50 import java.io.InputStreamReader;
51 import java.io.OutputStream;
52 import java.io.OutputStreamWriter;
53 import java.util.ArrayList;
54 import java.util.Collection;
55 import java.util.Comparator;
56 import java.util.Iterator;
57 import java.util.TreeSet;
58
59 import com.sun.javadoc.ClassDoc;
60 import com.sun.javadoc.ConstructorDoc;
61 import com.sun.javadoc.Doc;
62 import com.sun.javadoc.ExecutableMemberDoc;
63 import com.sun.javadoc.FieldDoc;
64 import com.sun.javadoc.MethodDoc;
65 import com.sun.javadoc.ProgramElementDoc;
66 import com.sun.javadoc.RootDoc;
67 import com.sun.javadoc.Tag;
68
69 public class CheckAPI {
70     RootDoc root;
71     String compare; // file
72     String compareName;
73     TreeSet compareSet;
74     TreeSet results;
75     boolean html;
76     String srcName = "Current"; // default source name
77     String output;
78     
79     private static final int DATA_FILE_VERSION = 1;
80     private static final char SEP = ';';
81
82     private static final int STA = 0, STA_DRAFT = 0, STA_STABLE = 1, STA_DEPRECATED = 2, STA_OBSOLETE = 3;
83     private static final int VIS = 1, VIS_PACKAGE = 0, VIS_PUBLIC= 1, VIS_PROTECTED = 2, VIS_PRIVATE = 3;
84     private static final int STK = 2, STK_STATIC = 1;
85     private static final int FIN = 3, FIN_FINAL = 1;
86     private static final int SYN = 4, SYN_SYNCHRONIZED = 1;
87     private static final int ABS = 5, ABS_ABSTRACT = 1;
88     private static final int CAT = 6, CAT_CLASS = 0, CAT_FIELD = 1, CAT_CONSTRUCTOR = 2, CAT_METHOD = 3;
89     private static final int PAK = 7;
90     private static final int CLS = 8;
91     private static final int NAM = 9;
92     private static final int SIG = 10;
93     private static final int EXC = 11;
94     private static final int NUM_TYPES = 11;
95
96     static abstract class APIInfo {
97         public abstract int getVal(int typ);
98         public abstract String get(int typ, boolean brief);
99         public abstract void write(BufferedWriter w, boolean brief, boolean html, boolean detail);
100     }
101         
102     final static class Info extends APIInfo {
103         private int    info;
104         private String pack; // package
105         private String cls; // enclosing class
106         private String name; // name
107         private String sig;  // signature, class: inheritance, method: signature, field: type, const: signature
108         private String exc;  // throws 
109         
110         public int getVal(int typ) {
111             validateType(typ);
112             return (info >> (typ*2)) & 0x3;
113         }
114
115         public String get(int typ, boolean brief) {
116             validateType(typ);
117             String[] vals = brief ? shortNames[typ] : names[typ];
118             if (vals == null) {
119                 switch (typ) {
120                 case PAK: return pack;
121                 case CLS: return cls;
122                 case NAM: return name;
123                 case SIG: return sig;
124                 case EXC: return exc;
125                 }
126             }
127             int val = (info >> (typ*2)) & 0x3;
128             return vals[val];
129         }
130
131         private void setType(int typ, int val) {
132             validateType(typ);
133             info &= ~(0x3 << (typ*2));
134             info |= (val&0x3) << (typ * 2);
135         }
136
137         private void setType(int typ, String val) {
138             validateType(typ);
139             String[] vals = shortNames[typ];
140             if (vals == null) {
141                 switch (typ) {
142                 case PAK: pack = val; break;
143                 case CLS: cls = val; break;
144                 case NAM: name = val; break;
145                 case SIG: sig = val; break;
146                 case EXC: exc = val; break;
147                 }
148                 return;
149             }
150
151             for (int i = 0; i < vals.length; ++i) {
152                 if (val.equalsIgnoreCase(vals[i])) {
153                     info &= ~(0x3 << (typ*2));
154                     info |= i << (typ*2);
155                     return;
156                 }
157             }
158
159             throw new IllegalArgumentException("unrecognized value '" + val + "' for type '" + typeNames[typ] + "'");
160         }
161
162         public void write(BufferedWriter w, boolean brief, boolean html, boolean detail) {
163             try {
164                 if (brief) {
165                     for (int i = 0; i < NUM_TYPES; ++i) {
166                         String s = get(i, true);
167                         if (s != null) {
168                             w.write(s);
169                         }
170                         w.write(SEP);
171                     }
172                 } else {
173                     // remove all occurrences of icu packages from the param string
174                     // fortunately, all the packages have 4 chars (lang, math, text, util).
175                     String xsig = sig;
176                     if (!detail) {
177                         final String ICUPACK = "com.ibm.icu.";
178                         StringBuffer buf = new StringBuffer();
179                         for (int i = 0; i < sig.length();) {
180                             int n = sig.indexOf(ICUPACK, i);
181                             if (n == -1) {
182                                 buf.append(sig.substring(i));
183                                 break;
184                             }
185                             buf.append(sig.substring(i, n));
186                             i = n + ICUPACK.length() + 5; // trailing 'xxxx.'
187                         }
188                         xsig = buf.toString();
189                     }
190
191                     // construct signature
192                     for (int i = STA; i < CAT; ++i) { // include status
193                         String s = get(i, false);
194                         if (s != null && s.length() > 0) {
195                             if (i == STA) {
196                                 w.write('(');
197                                 w.write(s);
198                                 w.write(')');
199                             } else {
200                                 w.write(s);
201                             }
202                             w.write(' ');
203                         }
204                     }
205
206                     int val = getVal(CAT);
207                     switch (val) {
208                     case CAT_CLASS:
209                         if (sig.indexOf("extends") == -1) {
210                             w.write("interface ");
211                         } else {
212                             w.write("class ");
213                         }
214                         if (cls.length() > 0) {
215                             w.write(cls);
216                             w.write('.');
217                         }
218                         w.write(name);
219                         if (detail) {
220                             w.write(' ');
221                             w.write(sig);
222                         }
223                         break;
224
225                     case CAT_FIELD:
226                         w.write(xsig);
227                         w.write(' ');
228                         w.write(name);
229                         break;
230
231                     case CAT_METHOD:
232                     case CAT_CONSTRUCTOR:
233                         int n = xsig.indexOf('(');
234                         if (n > 0) {
235                             w.write(xsig.substring(0, n));
236                             w.write(' ');
237                         } else {
238                             n = 0;
239                         }
240                         w.write(name);
241                         w.write(xsig.substring(n));
242                         break;
243                     }
244                 }
245                 w.newLine();
246             }
247             catch (IOException e) {
248                 RuntimeException re = new RuntimeException("IO Error");
249                 re.initCause(e);
250                 throw re;
251             }
252         }
253
254         public boolean read(BufferedReader r) {
255             int i = 0;
256             try {
257                 for (; i < NUM_TYPES; ++i) {
258                     setType(i, readToken(r));
259                 }
260                 r.readLine(); // swallow line end sequence
261             }
262             catch (IOException e) {
263                 if (i == 0) { // assume if first read returns error, we have reached end of input
264                     return false;
265                 }
266                 RuntimeException re = new RuntimeException("IO Error");
267                 re.initCause(e);
268                 throw re;
269             }
270
271             return true;
272         }
273
274         public boolean read(ProgramElementDoc doc) {
275
276             // Doc. name
277             // Doc. isField, isMethod, isConstructor, isClass, isInterface
278             // ProgramElementDoc. containingClass, containingPackage
279             // ProgramElementDoc. isPublic, isProtected, isPrivate, isPackagePrivate
280             // ProgramElementDoc. isStatic, isFinal
281             // MemberDoc.isSynthetic
282             // ExecutableMemberDoc isSynchronized, signature
283             // Type.toString() // e.g. "String[][]"
284             // ClassDoc.isAbstract, superClass, interfaces, fields, methods, constructors, innerClasses
285             // FieldDoc type
286             // ConstructorDoc qualifiedName
287             // MethodDoc isAbstract, returnType
288
289             
290             // status
291             setType(STA, tagStatus(doc));
292
293             // visibility
294             if (doc.isPublic()) {
295                 setType(VIS, VIS_PUBLIC);
296             } else if (doc.isProtected()) {
297                 setType(VIS, VIS_PROTECTED);
298             } else if (doc.isPrivate()) {
299                 setType(VIS, VIS_PRIVATE);
300             } else {
301                 // default is package
302             }
303
304             // static
305             if (doc.isStatic()) {
306                 setType(STK, STK_STATIC);
307             } else {
308                 // default is non-static
309             }
310
311             // final
312             if (doc.isFinal()) {
313                 setType(FIN, FIN_FINAL);
314             } else {
315                 // default is non-final
316             }
317
318             // type
319             if (doc.isField()) {
320                 setType(CAT, CAT_FIELD);
321             } else if (doc.isMethod()) {
322                 setType(CAT, CAT_METHOD);
323             } else if (doc.isConstructor()) {
324                 setType(CAT, CAT_CONSTRUCTOR);
325             } else if (doc.isClass() || doc.isInterface()) {
326                 setType(CAT, CAT_CLASS);
327             }
328
329             setType(PAK, doc.containingPackage().name());
330             setType(CLS, (doc.isClass() || doc.isInterface() || (doc.containingClass() == null)) ? "" : doc.containingClass().name());
331             setType(NAM, doc.name());
332
333             if (doc instanceof FieldDoc) {
334                 FieldDoc fdoc = (FieldDoc)doc;
335                 setType(SIG, fdoc.type().toString());
336             } else if (doc instanceof ClassDoc) {
337                 ClassDoc cdoc = (ClassDoc)doc;
338
339                 if (cdoc.isClass() && cdoc.isAbstract()) { // interfaces are abstract by default, don't mark them as abstract
340                     setType(ABS, ABS_ABSTRACT);
341                 }
342
343                 StringBuffer buf = new StringBuffer();
344                 if (cdoc.isClass()) {
345                     buf.append("extends ");
346                     buf.append(cdoc.superclass().qualifiedName());
347                 }
348                 ClassDoc[] imp = cdoc.interfaces();
349                 if (imp != null && imp.length > 0) {
350                     if (buf.length() > 0) {
351                         buf.append(" ");
352                     }
353                     buf.append("implements");
354                     for (int i = 0; i < imp.length; ++i) {
355                         if (i != 0) {
356                             buf.append(",");
357                         }
358                         buf.append(" ");
359                         buf.append(imp[i].qualifiedName());
360                     }
361                 }
362                 setType(SIG, buf.toString());
363             } else {
364                 ExecutableMemberDoc emdoc = (ExecutableMemberDoc)doc;
365                 if (emdoc.isSynchronized()) {
366                     setType(SYN, SYN_SYNCHRONIZED);
367                 }
368
369                 if (doc instanceof MethodDoc) {
370                     MethodDoc mdoc = (MethodDoc)doc;
371                     if (mdoc.isAbstract()) {
372                         setType(ABS, ABS_ABSTRACT);
373                     }
374                     setType(SIG, mdoc.returnType().toString() + emdoc.signature());
375                 } else {
376                     // constructor
377                     setType(SIG, emdoc.signature());
378                 }
379             }
380
381             return true;
382         }
383
384         public static Comparator defaultComparator() {
385             final Comparator c = new Comparator() {
386                     public int compare(Object lhs, Object rhs) {
387                         Info lhi = (Info)lhs;
388                         Info rhi = (Info)rhs;
389                         int result = lhi.pack.compareTo(rhi.pack);
390                         if (result == 0) {
391                             result = (lhi.getVal(CAT) == CAT_CLASS ? lhi.name : lhi.cls)
392                                 .compareTo(rhi.getVal(CAT) == CAT_CLASS ? rhi.name : rhi.cls);
393                             if (result == 0) {
394                                 result = lhi.getVal(CAT)- rhi.getVal(CAT);
395                                 if (result == 0) {
396                                     result = lhi.name.compareTo(rhi.name);
397                                     if (result == 0) {
398                                         result = lhi.sig.compareTo(rhi.sig);
399                                     }
400                                 }
401                             }
402                         }
403                         return result;
404                     }
405                 };
406             return c;
407         }
408
409         public static Comparator changedComparator() {
410             final Comparator c = new Comparator() {
411                     public int compare(Object lhs, Object rhs) {
412                         Info lhi = (Info)lhs;
413                         Info rhi = (Info)rhs;
414                         int result = lhi.pack.compareTo(rhi.pack);
415                         if (result == 0) {
416                             result = (lhi.getVal(CAT) == CAT_CLASS ? lhi.name : lhi.cls)
417                                 .compareTo(rhi.getVal(CAT) == CAT_CLASS ? rhi.name : rhi.cls);
418                             if (result == 0) {
419                                 result = lhi.getVal(CAT)- rhi.getVal(CAT);
420                                 if (result == 0) {
421                                     result = lhi.name.compareTo(rhi.name);
422                                     if (result == 0 && lhi.getVal(CAT) != CAT_CLASS) {
423                                         result = lhi.sig.compareTo(rhi.sig);
424                                     }
425                                 }
426                             }
427                         }
428                         return result;
429                     }
430                 };
431             return c;
432         }
433
434         public static Comparator classFirstComparator() {
435             final Comparator c = new Comparator() {
436                     public int compare(Object lhs, Object rhs) {
437                         Info lhi = (Info)lhs;
438                         Info rhi = (Info)rhs;
439                         int result = lhi.pack.compareTo(rhi.pack);
440                         if (result == 0) {
441                             boolean lcls = lhi.getVal(CAT) == CAT_CLASS;
442                             boolean rcls = rhi.getVal(CAT) == CAT_CLASS;
443                             result = lcls == rcls ? 0 : (lcls ? -1 : 1);
444                             if (result == 0) {
445                                 result = (lcls ? lhi.name : lhi.cls).compareTo(rcls ? rhi.name : rhi.cls);
446                                 if (result == 0) {
447                                     result = lhi.getVal(CAT)- rhi.getVal(CAT);
448                                     if (result == 0) {
449                                         result = lhi.name.compareTo(rhi.name);
450                                         if (result == 0 && !lcls) {
451                                             result = lhi.sig.compareTo(rhi.sig);
452                                         }
453                                     }
454                                 }
455                             }
456                         }
457                         return result;
458                     }
459                 };
460             return c;
461         }
462
463         private static final String[] typeNames = {
464             "status", "visibility", "static", "final", "synchronized", 
465             "abstract", "category", "package", "class", "name", "signature"
466         };
467
468         private static final String[][] names = {
469             { "draft     ", "stable    ", "deprecated", "obsolete  " },
470             { "package", "public", "protected", "private" },
471             { "", "static" },
472             { "", "final" },
473             { "", "synchronized" },
474             { "", "abstract" },
475             { "class", "field", "constructor", "method"  },
476             null,
477             null,
478             null,
479             null,
480             null
481         };
482
483         private static final String[][] shortNames = {
484             { "DR", "ST", "DP", "OB" },
485             { "PK", "PB", "PT", "PR" },
486             { "NS", "ST" },
487             { "NF", "FN" },
488             { "NS", "SY" },
489             { "NA", "AB" },
490             { "L", "F", "C", "M" },
491             null,
492             null,
493             null,
494             null,
495             null
496         };
497
498         private static void validateType(int typ) {
499             if (typ < 0 || typ > NUM_TYPES) {
500                 throw new IllegalArgumentException("bad type index: " + typ);
501             }
502         }
503
504         public String toString() {
505             return get(NAM, true);
506         }
507     }
508
509     static final class DeltaInfo extends APIInfo {
510         private Info a;
511         private Info b;
512
513         DeltaInfo(Info a, Info b) {
514             this.a = a;
515             this.b = b;
516         }
517
518         public int getVal(int typ) {
519             return a.getVal(typ);
520         }
521
522         public String get(int typ, boolean brief) {
523             return a.get(typ, brief);
524         }
525
526         public void write(BufferedWriter w, boolean brief, boolean html, boolean detail) {
527             a.write(w, brief, html, detail);
528             try {
529                 if (html) {
530                     w.write("<br>");
531                 }
532                 w.newLine();
533             } 
534             catch (Exception e) {
535             }
536             b.write(w, brief, html, detail);
537         }
538
539         public String toString() {
540             return a.get(NAM, true);
541         }
542     }
543
544     public static int optionLength(String option) {
545         if (option.equals("-html")) {
546             return 1;
547         } else if (option.equals("-name")) {
548             return 2;
549         } else if (option.equals("-output")) {
550             return 2;
551         } else if (option.equals("-compare")) {
552             return 2;
553         }
554         return 0;
555     }
556
557     public static boolean start(RootDoc root) {
558         return new CheckAPI(root).run();
559     }
560
561     CheckAPI(RootDoc root) {
562         this.root = root;
563
564         //      this.compare = "c:/doug/cvsproj/icu4j/src/com/ibm/icu/dev/tool/docs/api2_8.txt";
565
566         String[][] options = root.options();
567         for (int i = 0; i < options.length; ++i) {
568             String opt = options[i][0];
569             if (opt.equals("-html")) {
570                 this.html = true;
571             } else if (opt.equals("-name")) {
572                 this.srcName = options[i][1];
573             } else if (opt.equals("-output")) {
574                 this.output = options[i][1];
575             } else if (opt.equals("-compare")) {
576                 this.compare = options[i][1];
577             }
578         }
579
580         if (compare != null) {
581             try {
582                 // URL url = new URL(compare);
583                 File f = new File(compare);
584                 InputStream is = new FileInputStream(f);
585                 InputStreamReader isr = new InputStreamReader(is);
586                 BufferedReader br = new BufferedReader(isr);
587
588                 // read header line
589                 /*int version = */Integer.parseInt(readToken(br));
590                 // check version if we change it later, probably can just rebuild though
591                 this.compareName = readToken(br);
592                 br.readLine();
593
594                 // read data
595                 this.compareSet = new TreeSet(Info.defaultComparator());
596                 for (Info info = new Info(); info.read(br); info = new Info()) {
597                     compareSet.add(info);
598                 }
599             }
600             catch (Exception e) {
601                 RuntimeException re = new RuntimeException("error reading " + compare);
602                 re.initCause(e);
603                 throw re;
604             }
605         }
606             
607         results = new TreeSet(Info.defaultComparator());
608     }
609
610     private boolean run() {
611         doDocs(root.classes());
612
613         OutputStream os = System.out;
614         if (output != null) {
615             try {
616                 os = new FileOutputStream(output);
617             }
618             catch (FileNotFoundException e) {
619                 RuntimeException re = new RuntimeException(e.getMessage());
620                 re.initCause(e);
621                 throw re;
622             }
623         }
624
625         BufferedWriter bw = null;
626         try {
627             OutputStreamWriter osw = new OutputStreamWriter(os, "UTF-8");
628             bw = new BufferedWriter(osw);
629
630             if (compareSet == null) {
631                 // writing data file
632                 bw.write(String.valueOf(DATA_FILE_VERSION) + SEP); // header version
633                 bw.write(srcName + SEP); // source name
634                 bw.newLine();
635                 writeResults(results, bw, true, false, false);
636             } else {
637                 // writing comparison info
638                 TreeSet removed = (TreeSet)compareSet.clone();
639                 removed.removeAll(results);
640
641                 TreeSet added = (TreeSet)results.clone();
642                 added.removeAll(compareSet);
643
644                 Iterator ai = added.iterator();
645                 Iterator ri = removed.iterator();
646                 ArrayList changed = new ArrayList();
647                 Comparator c = Info.changedComparator();
648                 Info a = null, r = null;
649                 while (ai.hasNext() && ri.hasNext()) {
650                     if (a == null) a = (Info)ai.next();
651                     if (r == null) r = (Info)ri.next();
652                     int result = c.compare(a, r);
653                     if (result < 0) {
654                         a = null;
655                     } else if (result > 0) {
656                         r = null;
657                     } else {
658                         changed.add(new DeltaInfo(a, r));
659                         a = null; ai.remove();
660                         r = null; ri.remove();
661                     }
662                 }
663
664                 added = stripAndResort(added);
665                 removed = stripAndResort(removed);
666
667                 if (html) {
668                     String title = "ICU4J API Comparison: " + srcName + " with " + compareName;
669
670                     bw.write("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">");
671                     bw.newLine();
672                     bw.write("<html>");
673                     bw.newLine();
674                     bw.write("<head>");
675                     bw.newLine();
676                     bw.write("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">");
677                     bw.newLine();
678                     bw.write("<title>");
679                     bw.write(title);
680                     bw.write("</title>");
681                     bw.newLine();
682                     bw.write("<body>");
683                     bw.newLine();
684
685                     bw.write("<h1>");
686                     bw.write(title);
687                     bw.write("</h1>");
688                     bw.newLine();
689
690                     bw.write("<hr/>");
691                     bw.newLine();
692                     bw.write("<h2>");
693                     bw.write("Removed from " + compareName);
694                     bw.write("</h2>");
695                     bw.newLine();
696
697                     if (removed.size() > 0) {
698                         writeResults(removed, bw, false, true, false);
699                     } else {
700                         bw.write("<p>(no API removed)</p>");
701                     }
702                     bw.newLine();
703
704                     bw.write("<hr/>");
705                     bw.newLine();
706                     bw.write("<h2>");
707                     bw.write("Changed in " + srcName);
708                     bw.write("</h2>");
709                     bw.newLine();
710
711                     if (changed.size() > 0) {
712                         writeResults(changed, bw, false, true, true);
713                     } else {
714                         bw.write("<p>(no API changed)</p>");
715                     }
716                     bw.newLine();
717
718                     bw.write("<hr/>");
719                     bw.newLine();
720                     bw.write("<h2>");
721                     bw.write("Added in " + srcName);
722                     bw.write("</h2>");
723                     bw.newLine();
724
725                     if (added.size() > 0) {
726                         writeResults(added, bw, false, true, false);
727                     } else {
728                         bw.write("<p>(no API added)</p>");
729                     }
730                     bw.write("<hr/>");
731                     bw.newLine();
732                     bw.write("<p><i>Contents generated by CheckAPI tool.<br/>Copyright (C) 2004, International Business Machines Corporation, All Rights Reserved.</i></p>");
733                     bw.newLine();
734                     bw.write("</body>");
735                     bw.newLine();
736                     bw.write("</html>");
737                     bw.newLine();
738                 } else {
739                     bw.write("Comparing " + srcName + " with " + compareName);
740                     bw.newLine();
741                     bw.newLine();
742
743                     bw.newLine();
744                     bw.write("=== Removed from " + compareName + " ===");
745                     bw.newLine();
746                     if (removed.size() > 0) {
747                         writeResults(removed, bw, false, false, false);
748                     } else {
749                         bw.write("(no API removed)");
750                     }
751                     bw.newLine();
752
753                     bw.newLine();
754                     bw.write("=== Changed in " + srcName + " ===");
755                     bw.newLine();
756                     if (changed.size() > 0) {
757                         writeResults(changed, bw, false, false, true);
758                     } else {
759                         bw.write("(no API changed)");
760                     }
761                     bw.newLine();
762
763                     bw.newLine();
764                     bw.write("=== Added in " + srcName + " ===");
765                     bw.newLine();
766                     if (added.size() > 0) {
767                         writeResults(added, bw, false, false, false);
768                     } else {
769                         bw.write("(no API added)");
770                     }
771                     bw.newLine();
772                 }
773             }
774
775             bw.close();
776         } catch (IOException e) {
777             try { bw.close(); } catch (IOException e2) {}
778             RuntimeException re = new RuntimeException("write error: " + e.getMessage());
779             re.initCause(e);
780             throw re;
781         }
782
783         return false;
784     }
785
786     private void doDocs(ProgramElementDoc[] docs) {
787         if (docs != null && docs.length > 0) {
788             for (int i = 0; i < docs.length; ++i) {
789                 doDoc(docs[i]);
790             }
791         }
792     }
793
794     private void doDoc(ProgramElementDoc doc) {
795         if (ignore(doc)) return;
796
797         if (doc.isClass() || doc.isInterface()) {
798             ClassDoc cdoc = (ClassDoc)doc;
799             doDocs(cdoc.fields());
800             doDocs(cdoc.constructors());
801             doDocs(cdoc.methods());
802             doDocs(cdoc.innerClasses());
803         }
804
805         Info info = new Info();
806         if (info.read(doc)) {
807             results.add(info);
808         }
809     }
810
811     private boolean ignore(ProgramElementDoc doc) {
812         if (doc == null) return true;
813         if (doc.isPrivate() || doc.isPackagePrivate()) return true;
814         if (doc instanceof ConstructorDoc && ((ConstructorDoc)doc).isSynthetic()) return true;
815         if (doc.qualifiedName().indexOf(".misc") != -1) return true;
816         Tag[] tags = doc.tags();
817         for (int i = 0; i < tags.length; ++i) {
818             if (tagKindIndex(tags[i].kind()) == INTERNAL) return true;
819         }
820
821         return false;
822     }
823
824     private static void writeResults(Collection c, BufferedWriter w, boolean brief, boolean html, boolean detail) {
825         Iterator iter = c.iterator();
826         String pack = null;
827         String clas = null;
828         while (iter.hasNext()) {
829             APIInfo info = (APIInfo)iter.next();
830             if (brief) {
831                 info.write(w, brief, false, detail);
832             } else {
833                 try {
834                     String p = info.get(PAK, true);
835                     if (!p.equals(pack)) {
836                         w.newLine();
837                         if (html) {
838                             if (clas != null) {
839                                 w.write("</ul>");
840                                 w.newLine();
841                             }
842                             if (pack != null) {
843                                 w.write("</ul>");
844                                 w.newLine();
845                             }
846                             
847                             w.write("<h3>Package ");
848                             w.write(p);
849                             w.write("</h3>");
850                             w.newLine();
851                             w.write("<ul>");
852                             w.newLine();
853                         } else {
854                             w.write("Package ");
855                             w.write(p);
856                             w.write(':');
857                         }
858                         w.newLine();
859                         w.newLine();
860                         
861                         pack = p;
862                         clas = null;
863                     }
864
865                     if (info.getVal(CAT) != CAT_CLASS) {
866                         String name = info.get(CLS, true);
867                         if (!name.equals(clas)) {
868                             if (html) {
869                                 if (clas != null) {
870                                     w.write("</ul>");
871                                 }
872                                 w.write("<li>");
873                                 w.write(name);
874                                 w.newLine();
875                                 w.write("<ul>");
876                             } else {
877                                 w.write(name);
878                                 w.newLine();
879                             }
880                             clas = name;
881                         }
882                         w.write("    ");
883                     }
884                     if (html) {
885                         w.write("<li>");
886                         info.write(w, brief, html, detail);
887                         w.write("</li>");
888                     } else {
889                         info.write(w, brief, html, detail);
890                     }
891                 }
892                 catch (IOException e) {
893                     System.err.println("IOException " + e.getMessage() + " writing " + info);
894                 }
895             }
896         }
897         if (html) {
898             try {
899                 if (clas != null) {
900                     w.write("</ul>");
901                     w.newLine();
902                 }
903                 if (pack != null) {
904                     w.write("</ul>");
905                     w.newLine();
906                 }
907             } 
908             catch (IOException e) {
909             }
910         }
911     }
912
913     private static String readToken(BufferedReader r) throws IOException {
914         char[] buf = new char[256];
915         int i = 0;
916         for (; i < buf.length; ++i) {
917             int c = r.read();
918             if (c == -1) {
919                 throw new IOException("unexpected EOF");
920             } else if (c == SEP) {
921                 break;
922             }
923             buf[i] = (char)c;
924         }
925         if (i == buf.length) {
926             throw new IOException("unterminated token" + new String(buf));
927         }
928             
929         return new String(buf, 0, i);
930     }
931
932     private static TreeSet stripAndResort(TreeSet t) {
933         stripClassInfo(t);
934         TreeSet r = new TreeSet(Info.classFirstComparator());
935         r.addAll(t);
936         return r;
937     }
938
939     private static void stripClassInfo(Collection c) {
940         // c is sorted with class info first
941         Iterator iter = c.iterator();
942         String cname = null;
943         while (iter.hasNext()) {
944             Info info = (Info)iter.next();
945             String cls = info.get(CLS, true);
946             if (cname != null) {
947                 if (cname.equals(cls)) {
948                     iter.remove();
949                     continue;
950                 }
951                 cname = null;
952             } 
953             if (info.getVal(CAT) == CAT_CLASS) {
954                 cname = info.get(NAM, true);
955             }
956         }
957     }
958
959     private static int tagStatus(final Doc doc) {
960         class Result {
961             int res = -1;
962             void set(int val) { if (res != -1) throw new RuntimeException("bad doc: " + doc); res = val; }
963             int get() {
964                 if (res == -1) {
965                     System.err.println("warning: no tag for " + doc);
966                     return 0;
967                 }
968                 return res;
969             }
970         }
971
972         Tag[] tags = doc.tags();
973         Result result = new Result();
974         for (int i = 0; i < tags.length; ++i) {
975             Tag tag = tags[i];
976
977             String kind = tag.kind();
978             int ix = tagKindIndex(kind);
979
980             switch (ix) {
981             case INTERNAL:
982                 result.set(-2);
983                 break;
984
985             case DRAFT:
986                 result.set(STA_DRAFT);
987                 break;
988
989             case STABLE:
990                 result.set(STA_STABLE);
991                 break;
992
993             case DEPRECATED:
994                 result.set(STA_DEPRECATED);
995                 break;
996
997             case OBSOLETE:
998                 result.set(STA_OBSOLETE);
999                 break;
1000
1001             case SINCE:
1002             case EXCEPTION:
1003             case VERSION:
1004             case UNKNOWN:
1005             case AUTHOR:
1006             case SEE:
1007             case PARAM:
1008             case RETURN:
1009             case THROWS:
1010             case SERIAL:
1011                 break;
1012
1013             default:
1014                 throw new RuntimeException("unknown index " + ix + " for tag: " + kind);
1015             }
1016         }
1017
1018         return result.get();
1019     }
1020
1021     private static final int UNKNOWN = -1;
1022     private static final int INTERNAL = 0;
1023     private static final int DRAFT = 1;
1024     private static final int STABLE = 2;
1025     private static final int SINCE = 3;
1026     private static final int DEPRECATED = 4;
1027     private static final int AUTHOR = 5;
1028     private static final int SEE = 6;
1029     private static final int VERSION = 7;
1030     private static final int PARAM = 8;
1031     private static final int RETURN = 9;
1032     private static final int THROWS = 10;
1033     private static final int OBSOLETE = 11;
1034     private static final int EXCEPTION = 12;
1035     private static final int SERIAL = 13;
1036
1037     private static int tagKindIndex(String kind) {
1038         final String[] tagKinds = {
1039             "@internal", "@draft", "@stable", "@since", "@deprecated", "@author", "@see", "@version",
1040             "@param", "@return", "@throws", "@obsolete", "@exception", "@serial"
1041         };
1042
1043         for (int i = 0; i < tagKinds.length; ++i) {
1044             if (kind.equals(tagKinds[i])) {
1045                 return i;
1046             }
1047         }
1048         return UNKNOWN;
1049     }
1050 }