Home | History | Annotate | Download | only in docs
      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 }
   1051