2 *******************************************************************************
3 * Copyright (C) 2004-2010, International Business Machines Corporation and *
4 * others. All Rights Reserved. *
5 *******************************************************************************
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].
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)
20 * Requires JDK 1.4.2 or later
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
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)
40 package com.ibm.icu.dev.tool.docs;
42 import java.io.BufferedReader;
43 import java.io.BufferedWriter;
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;
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;
69 public class CheckAPI {
71 String compare; // file
76 String srcName = "Current"; // default source name
79 private static final int DATA_FILE_VERSION = 1;
80 private static final char SEP = ';';
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;
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);
102 final static class Info extends APIInfo {
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
110 public int getVal(int typ) {
112 return (info >> (typ*2)) & 0x3;
115 public String get(int typ, boolean brief) {
117 String[] vals = brief ? shortNames[typ] : names[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;
127 int val = (info >> (typ*2)) & 0x3;
131 private void setType(int typ, int val) {
133 info &= ~(0x3 << (typ*2));
134 info |= (val&0x3) << (typ * 2);
137 private void setType(int typ, String val) {
139 String[] vals = shortNames[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;
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);
159 throw new IllegalArgumentException("unrecognized value '" + val + "' for type '" + typeNames[typ] + "'");
162 public void write(BufferedWriter w, boolean brief, boolean html, boolean detail) {
165 for (int i = 0; i < NUM_TYPES; ++i) {
166 String s = get(i, true);
173 // remove all occurrences of icu packages from the param string
174 // fortunately, all the packages have 4 chars (lang, math, text, util).
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);
182 buf.append(sig.substring(i));
185 buf.append(sig.substring(i, n));
186 i = n + ICUPACK.length() + 5; // trailing 'xxxx.'
188 xsig = buf.toString();
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) {
206 int val = getVal(CAT);
209 if (sig.indexOf("extends") == -1) {
210 w.write("interface ");
214 if (cls.length() > 0) {
232 case CAT_CONSTRUCTOR:
233 int n = xsig.indexOf('(');
235 w.write(xsig.substring(0, n));
241 w.write(xsig.substring(n));
247 catch (IOException e) {
248 RuntimeException re = new RuntimeException("IO Error");
254 public boolean read(BufferedReader r) {
257 for (; i < NUM_TYPES; ++i) {
258 setType(i, readToken(r));
260 r.readLine(); // swallow line end sequence
262 catch (IOException e) {
263 if (i == 0) { // assume if first read returns error, we have reached end of input
266 RuntimeException re = new RuntimeException("IO Error");
274 public boolean read(ProgramElementDoc doc) {
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
286 // ConstructorDoc qualifiedName
287 // MethodDoc isAbstract, returnType
291 setType(STA, tagStatus(doc));
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);
301 // default is package
305 if (doc.isStatic()) {
306 setType(STK, STK_STATIC);
308 // default is non-static
313 setType(FIN, FIN_FINAL);
315 // default is non-final
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);
329 setType(PAK, doc.containingPackage().name());
330 setType(CLS, (doc.isClass() || doc.isInterface() || (doc.containingClass() == null)) ? "" : doc.containingClass().name());
331 setType(NAM, doc.name());
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;
339 if (cdoc.isClass() && cdoc.isAbstract()) { // interfaces are abstract by default, don't mark them as abstract
340 setType(ABS, ABS_ABSTRACT);
343 StringBuffer buf = new StringBuffer();
344 if (cdoc.isClass()) {
345 buf.append("extends ");
346 buf.append(cdoc.superclass().qualifiedName());
348 ClassDoc[] imp = cdoc.interfaces();
349 if (imp != null && imp.length > 0) {
350 if (buf.length() > 0) {
353 buf.append("implements");
354 for (int i = 0; i < imp.length; ++i) {
359 buf.append(imp[i].qualifiedName());
362 setType(SIG, buf.toString());
364 ExecutableMemberDoc emdoc = (ExecutableMemberDoc)doc;
365 if (emdoc.isSynchronized()) {
366 setType(SYN, SYN_SYNCHRONIZED);
369 if (doc instanceof MethodDoc) {
370 MethodDoc mdoc = (MethodDoc)doc;
371 if (mdoc.isAbstract()) {
372 setType(ABS, ABS_ABSTRACT);
374 setType(SIG, mdoc.returnType().toString() + emdoc.signature());
377 setType(SIG, emdoc.signature());
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);
391 result = (lhi.getVal(CAT) == CAT_CLASS ? lhi.name : lhi.cls)
392 .compareTo(rhi.getVal(CAT) == CAT_CLASS ? rhi.name : rhi.cls);
394 result = lhi.getVal(CAT)- rhi.getVal(CAT);
396 result = lhi.name.compareTo(rhi.name);
398 result = lhi.sig.compareTo(rhi.sig);
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);
416 result = (lhi.getVal(CAT) == CAT_CLASS ? lhi.name : lhi.cls)
417 .compareTo(rhi.getVal(CAT) == CAT_CLASS ? rhi.name : rhi.cls);
419 result = lhi.getVal(CAT)- rhi.getVal(CAT);
421 result = lhi.name.compareTo(rhi.name);
422 if (result == 0 && lhi.getVal(CAT) != CAT_CLASS) {
423 result = lhi.sig.compareTo(rhi.sig);
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);
441 boolean lcls = lhi.getVal(CAT) == CAT_CLASS;
442 boolean rcls = rhi.getVal(CAT) == CAT_CLASS;
443 result = lcls == rcls ? 0 : (lcls ? -1 : 1);
445 result = (lcls ? lhi.name : lhi.cls).compareTo(rcls ? rhi.name : rhi.cls);
447 result = lhi.getVal(CAT)- rhi.getVal(CAT);
449 result = lhi.name.compareTo(rhi.name);
450 if (result == 0 && !lcls) {
451 result = lhi.sig.compareTo(rhi.sig);
463 private static final String[] typeNames = {
464 "status", "visibility", "static", "final", "synchronized",
465 "abstract", "category", "package", "class", "name", "signature"
468 private static final String[][] names = {
469 { "draft ", "stable ", "deprecated", "obsolete " },
470 { "package", "public", "protected", "private" },
473 { "", "synchronized" },
475 { "class", "field", "constructor", "method" },
483 private static final String[][] shortNames = {
484 { "DR", "ST", "DP", "OB" },
485 { "PK", "PB", "PT", "PR" },
490 { "L", "F", "C", "M" },
498 private static void validateType(int typ) {
499 if (typ < 0 || typ > NUM_TYPES) {
500 throw new IllegalArgumentException("bad type index: " + typ);
504 public String toString() {
505 return get(NAM, true);
509 static final class DeltaInfo extends APIInfo {
513 DeltaInfo(Info a, Info b) {
518 public int getVal(int typ) {
519 return a.getVal(typ);
522 public String get(int typ, boolean brief) {
523 return a.get(typ, brief);
526 public void write(BufferedWriter w, boolean brief, boolean html, boolean detail) {
527 a.write(w, brief, html, detail);
534 catch (Exception e) {
536 b.write(w, brief, html, detail);
539 public String toString() {
540 return a.get(NAM, true);
544 public static int optionLength(String option) {
545 if (option.equals("-html")) {
547 } else if (option.equals("-name")) {
549 } else if (option.equals("-output")) {
551 } else if (option.equals("-compare")) {
557 public static boolean start(RootDoc root) {
558 return new CheckAPI(root).run();
561 CheckAPI(RootDoc root) {
564 // this.compare = "c:/doug/cvsproj/icu4j/src/com/ibm/icu/dev/tool/docs/api2_8.txt";
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")) {
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];
580 if (compare != null) {
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);
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);
595 this.compareSet = new TreeSet(Info.defaultComparator());
596 for (Info info = new Info(); info.read(br); info = new Info()) {
597 compareSet.add(info);
600 catch (Exception e) {
601 RuntimeException re = new RuntimeException("error reading " + compare);
607 results = new TreeSet(Info.defaultComparator());
610 private boolean run() {
611 doDocs(root.classes());
613 OutputStream os = System.out;
614 if (output != null) {
616 os = new FileOutputStream(output);
618 catch (FileNotFoundException e) {
619 RuntimeException re = new RuntimeException(e.getMessage());
625 BufferedWriter bw = null;
627 OutputStreamWriter osw = new OutputStreamWriter(os, "UTF-8");
628 bw = new BufferedWriter(osw);
630 if (compareSet == null) {
632 bw.write(String.valueOf(DATA_FILE_VERSION) + SEP); // header version
633 bw.write(srcName + SEP); // source name
635 writeResults(results, bw, true, false, false);
637 // writing comparison info
638 TreeSet removed = (TreeSet)compareSet.clone();
639 removed.removeAll(results);
641 TreeSet added = (TreeSet)results.clone();
642 added.removeAll(compareSet);
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);
655 } else if (result > 0) {
658 changed.add(new DeltaInfo(a, r));
659 a = null; ai.remove();
660 r = null; ri.remove();
664 added = stripAndResort(added);
665 removed = stripAndResort(removed);
668 String title = "ICU4J API Comparison: " + srcName + " with " + compareName;
670 bw.write("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">");
676 bw.write("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">");
680 bw.write("</title>");
693 bw.write("Removed from " + compareName);
697 if (removed.size() > 0) {
698 writeResults(removed, bw, false, true, false);
700 bw.write("<p>(no API removed)</p>");
707 bw.write("Changed in " + srcName);
711 if (changed.size() > 0) {
712 writeResults(changed, bw, false, true, true);
714 bw.write("<p>(no API changed)</p>");
721 bw.write("Added in " + srcName);
725 if (added.size() > 0) {
726 writeResults(added, bw, false, true, false);
728 bw.write("<p>(no API added)</p>");
732 bw.write("<p><i>Contents generated by CheckAPI tool.<br/>Copyright (C) 2004, International Business Machines Corporation, All Rights Reserved.</i></p>");
739 bw.write("Comparing " + srcName + " with " + compareName);
744 bw.write("=== Removed from " + compareName + " ===");
746 if (removed.size() > 0) {
747 writeResults(removed, bw, false, false, false);
749 bw.write("(no API removed)");
754 bw.write("=== Changed in " + srcName + " ===");
756 if (changed.size() > 0) {
757 writeResults(changed, bw, false, false, true);
759 bw.write("(no API changed)");
764 bw.write("=== Added in " + srcName + " ===");
766 if (added.size() > 0) {
767 writeResults(added, bw, false, false, false);
769 bw.write("(no API added)");
776 } catch (IOException e) {
777 try { bw.close(); } catch (IOException e2) {}
778 RuntimeException re = new RuntimeException("write error: " + e.getMessage());
786 private void doDocs(ProgramElementDoc[] docs) {
787 if (docs != null && docs.length > 0) {
788 for (int i = 0; i < docs.length; ++i) {
794 private void doDoc(ProgramElementDoc doc) {
795 if (ignore(doc)) return;
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());
805 Info info = new Info();
806 if (info.read(doc)) {
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;
824 private static void writeResults(Collection c, BufferedWriter w, boolean brief, boolean html, boolean detail) {
825 Iterator iter = c.iterator();
828 while (iter.hasNext()) {
829 APIInfo info = (APIInfo)iter.next();
831 info.write(w, brief, false, detail);
834 String p = info.get(PAK, true);
835 if (!p.equals(pack)) {
847 w.write("<h3>Package ");
865 if (info.getVal(CAT) != CAT_CLASS) {
866 String name = info.get(CLS, true);
867 if (!name.equals(clas)) {
886 info.write(w, brief, html, detail);
889 info.write(w, brief, html, detail);
892 catch (IOException e) {
893 System.err.println("IOException " + e.getMessage() + " writing " + info);
908 catch (IOException e) {
913 private static String readToken(BufferedReader r) throws IOException {
914 char[] buf = new char[256];
916 for (; i < buf.length; ++i) {
919 throw new IOException("unexpected EOF");
920 } else if (c == SEP) {
925 if (i == buf.length) {
926 throw new IOException("unterminated token" + new String(buf));
929 return new String(buf, 0, i);
932 private static TreeSet stripAndResort(TreeSet t) {
934 TreeSet r = new TreeSet(Info.classFirstComparator());
939 private static void stripClassInfo(Collection c) {
940 // c is sorted with class info first
941 Iterator iter = c.iterator();
943 while (iter.hasNext()) {
944 Info info = (Info)iter.next();
945 String cls = info.get(CLS, true);
947 if (cname.equals(cls)) {
953 if (info.getVal(CAT) == CAT_CLASS) {
954 cname = info.get(NAM, true);
959 private static int tagStatus(final Doc doc) {
962 void set(int val) { if (res != -1) throw new RuntimeException("bad doc: " + doc); res = val; }
965 System.err.println("warning: no tag for " + doc);
972 Tag[] tags = doc.tags();
973 Result result = new Result();
974 for (int i = 0; i < tags.length; ++i) {
977 String kind = tag.kind();
978 int ix = tagKindIndex(kind);
986 result.set(STA_DRAFT);
990 result.set(STA_STABLE);
994 result.set(STA_DEPRECATED);
998 result.set(STA_OBSOLETE);
1014 throw new RuntimeException("unknown index " + ix + " for tag: " + kind);
1018 return result.get();
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;
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"
1043 for (int i = 0; i < tagKinds.length; ++i) {
1044 if (kind.equals(tagKinds[i])) {