diff --git a/icu4j/src/com/ibm/icu/dev/tool/docs/APIInfo.java b/icu4j/src/com/ibm/icu/dev/tool/docs/APIInfo.java new file mode 100644 index 00000000000..2df81e9237c --- /dev/null +++ b/icu4j/src/com/ibm/icu/dev/tool/docs/APIInfo.java @@ -0,0 +1,463 @@ +/** +******************************************************************************* +* Copyright (C) 2004, International Business Machines Corporation and * +* others. All Rights Reserved. * +******************************************************************************* +*/ + +/** + * Represents the API information on a doc element. + */ + +package com.ibm.icu.dev.tool.docs; + +import java.io.*; +import java.util.*; + +class APIInfo { + // version id for the format of the APIInfo data + + public static final int VERSION = 1; + + // public keys and values for queries on info + + public static final int STA = 0, STA_DRAFT = 0, STA_STABLE = 1, STA_DEPRECATED = 2, STA_OBSOLETE = 3; + public static final int VIS = 1, VIS_PACKAGE = 0, VIS_PUBLIC= 1, VIS_PROTECTED = 2, VIS_PRIVATE = 3; + public static final int STK = 2, STK_STATIC = 1; + public static final int FIN = 3, FIN_FINAL = 1; + public static final int SYN = 4, SYN_SYNCHRONIZED = 1; + public static final int ABS = 5, ABS_ABSTRACT = 1; + public static final int CAT = 6, CAT_CLASS = 0, CAT_FIELD = 1, CAT_CONSTRUCTOR = 2, CAT_METHOD = 3; + public static final int PAK = 7; + public static final int CLS = 8; + public static final int NAM = 9; + public static final int SIG = 10; + public static final int EXC = 11; + public static final int NUM_TYPES = 11; + + // the separator between tokens in the data file + + public static final char SEP = ';'; + + // internal state + private int info; // information about numeric values packed into an int as 2-bit nibbles + private String pack; // package + private String cls; // enclosing class + private String name; // name + private String sig; // signature, class: inheritance, method: signature, field: type, const: signature + private String exc; // throws + + public void setDraft() { setType(STA, STA_DRAFT); } + public void setStable() { setType(STA, STA_STABLE); } + public void setDeprecated() { setType(STA, STA_DEPRECATED); } + public void setObsolete() { setType(STA, STA_OBSOLETE); } + public void setPackage() { setType(VIS, VIS_PACKAGE); } + public void setPublic() { setType(VIS, VIS_PUBLIC); } + public void setProtected() { setType(VIS, VIS_PROTECTED); } + public void setPrivate() { setType(VIS, VIS_PRIVATE); } + public void setStatic() { setType(STK, STK_STATIC); } + public void setFinal() { setType(FIN, FIN_FINAL); } + public void setSynchronized() { setType(SYN, SYN_SYNCHRONIZED); } + public void setAbstract() { setType(ABS, ABS_ABSTRACT); } + public void setClass() { setType(CAT, CAT_CLASS); } + public void setField() { setType(CAT, CAT_FIELD); } + public void setConstructor() { setType(CAT, CAT_CONSTRUCTOR); } + public void setMethod() { setType(CAT, CAT_METHOD); } + + public void setPackage(String val) { setType(PAK, val); } + public void setClassName(String val) { setType(CLS, val); } + public void setName(String val) { setType(NAM, val); } + public void setSignature(String val) { setType(SIG, val); } + public void setExceptions(String val) { setType(EXC, val); } + + + public boolean isDraft() { return getVal(STA) == STA_DRAFT; } + public boolean isStable() { return getVal(STA) == STA_STABLE; } + public boolean isDeprecated() { return getVal(STA) == STA_DEPRECATED; } + public boolean isObsolete() { return getVal(STA) == STA_OBSOLETE; } + public boolean isPackage() { return getVal(VIS) == VIS_PACKAGE; } + public boolean isPublic() { return getVal(VIS) == VIS_PUBLIC; } + public boolean isProtected() { return getVal(VIS) == VIS_PROTECTED; } + public boolean isPrivate() { return getVal(VIS) == VIS_PRIVATE; } + public boolean isStatic() { return getVal(STK) == STK_STATIC; } + public boolean isFinal() { return getVal(FIN) == FIN_FINAL; } + public boolean isSynchronized() { return getVal(SYN) == SYN_SYNCHRONIZED; } + public boolean isAbstract() { return getVal(ABS) == ABS_ABSTRACT; } + public boolean isClass() { return getVal(CAT) == CAT_CLASS; } + public boolean isField() { return getVal(CAT) == CAT_FIELD; } + public boolean isConstructor() { return getVal(CAT) == CAT_CONSTRUCTOR; } + public boolean isMethod() { return getVal(CAT) == CAT_METHOD; } + + public String getPackageName() { return get(PAK, true); } + public String getClassName() { return get(CLS, true); } + public String getName() { return get(NAM, true); } + public String getSignature() { return get(SIG, true); } + public String getExceptions() { return get(EXC, true); } + + /** + * Return the integer value for the provided type. The type + * must be one of the defined type names. The return value + * will be one of corresponding values for that type. + */ + public int getVal(int typ) { + validateType(typ); + return (info >> (typ*2)) & 0x3; + } + + /** + * Return the string value for the provided type. The type + * must be one of the defined type names. The return value + * will be one of corresponding values for that type. Brief + * should be true for writing data files, false for presenting + * information to the user. + */ + public String get(int typ, boolean brief) { + validateType(typ); + String[] vals = brief ? shortNames[typ] : names[typ]; + if (vals == null) { + switch (typ) { + case PAK: return pack; + case CLS: return cls; + case NAM: return name; + case SIG: return sig; + case EXC: return exc; + } + } + int val = (info >> (typ*2)) & 0x3; + return vals[val]; + } + + /** + * Set the numeric value for the type. The value should be a + * value corresponding to the type. Only the lower two bits + * of the value are used. + */ + public void setType(int typ, int val) { + validateType(typ); + info &= ~(0x3 << (typ*2)); + info |= (val&0x3) << (typ * 2); + } + + /** + * Set the string value for the type. For numeric types, + * the value should be a string in 'brief' format. For + * non-numeric types, the value can be any + * string. + */ + private void setType(int typ, String val) { + validateType(typ); + String[] vals = shortNames[typ]; + if (vals == null) { + switch (typ) { + case PAK: pack = val; break; + case CLS: cls = val; break; + case NAM: name = val; break; + case SIG: sig = val; break; + case EXC: exc = val; break; + } + return; + } + + for (int i = 0; i < vals.length; ++i) { + if (val.equalsIgnoreCase(vals[i])) { + info &= ~(0x3 << (typ*2)); + info |= i << (typ*2); + return; + } + } + + throw new IllegalArgumentException("unrecognized value '" + val + "' for type '" + typeNames[typ] + "'"); + } + + /** + * Write the information out as a single line in brief format. + * If there are IO errors, throws a RuntimeException. + */ + public void writeln(BufferedWriter w) { + try { + for (int i = 0; i < NUM_TYPES; ++i) { + String s = get(i, true); + if (s != null) { + w.write(s); + } + w.write(SEP); + } + w.newLine(); + } + catch (IOException e) { + RuntimeException re = new RuntimeException("IO Error"); + re.initCause(e); + throw re; + } + } + + /** + * Read a record from the input and initialize this APIInfo. + * Return true if successfule, false if EOF, otherwise throw + * a RuntimeException. + */ + public boolean read(BufferedReader r) { + int i = 0; + try { + for (; i < NUM_TYPES; ++i) { + setType(i, readToken(r)); + } + r.readLine(); // swallow line end sequence + } + catch (IOException e) { + if (i == 0) { // assume if first read returns error, we have reached end of input + return false; + } + RuntimeException re = new RuntimeException("IO Error"); + re.initCause(e); + throw re; + } + + return true; + } + + /** + * Read one token from input, which should have been written by + * APIInfo. Throws IOException if EOF is encountered before the + * token is complete (i.e. before the separator character is + * encountered) or if the token exceeds the maximum length of + * 255 chars. + */ + public static String readToken(BufferedReader r) throws IOException { + char[] buf = new char[256]; + int i = 0; + for (; i < buf.length; ++i) { + int c = r.read(); + if (c == -1) { + throw new IOException("unexpected EOF"); + } else if (c == SEP) { + break; + } + buf[i] = (char)c; + } + if (i == buf.length) { + throw new IOException("unterminated token" + new String(buf)); + } + + return new String(buf, 0, i); + } + + /** + * The default comparator for APIInfo. This compares packages, class/name + * (as the info represents a class or other object), category, name, + * and signature. + */ + public static Comparator defaultComparator() { + final Comparator c = new Comparator() { + public int compare(Object lhs, Object rhs) { + APIInfo lhi = (APIInfo)lhs; + APIInfo rhi = (APIInfo)rhs; + int result = lhi.pack.compareTo(rhi.pack); + if (result == 0) { + result = (lhi.getVal(CAT) == CAT_CLASS ? lhi.name : lhi.cls) + .compareTo(rhi.getVal(CAT) == CAT_CLASS ? rhi.name : rhi.cls); + if (result == 0) { + result = lhi.getVal(CAT)- rhi.getVal(CAT); + if (result == 0) { + result = lhi.name.compareTo(rhi.name); + if (result == 0) { + result = lhi.sig.compareTo(rhi.sig); + } + } + } + } + return result; + } + }; + return c; + } + + /** + * This compares two APIInfos by package, class/name, category, name, and then if + * the APIInfo does not represent a class, by signature. The difference between + * this and the default comparator is that APIInfos representing classes are considered + * equal regardless of their signatures (which represent inheritance for classes). + */ + public static Comparator changedComparator() { + final Comparator c = new Comparator() { + public int compare(Object lhs, Object rhs) { + APIInfo lhi = (APIInfo)lhs; + APIInfo rhi = (APIInfo)rhs; + int result = lhi.pack.compareTo(rhi.pack); + if (result == 0) { + result = (lhi.getVal(CAT) == CAT_CLASS ? lhi.name : lhi.cls) + .compareTo(rhi.getVal(CAT) == CAT_CLASS ? rhi.name : rhi.cls); + if (result == 0) { + result = lhi.getVal(CAT)- rhi.getVal(CAT); + if (result == 0) { + result = lhi.name.compareTo(rhi.name); + if (result == 0 && lhi.getVal(CAT) != CAT_CLASS) { + result = lhi.sig.compareTo(rhi.sig); + } + } + } + } + return result; + } + }; + return c; + } + + /** + * This compares two APIInfos by package, then sorts classes before non-classes, then + * by class/name, category, name, and signature. + */ + public static Comparator classFirstComparator() { + final Comparator c = new Comparator() { + public int compare(Object lhs, Object rhs) { + APIInfo lhi = (APIInfo)lhs; + APIInfo rhi = (APIInfo)rhs; + int result = lhi.pack.compareTo(rhi.pack); + if (result == 0) { + boolean lcls = lhi.getVal(CAT) == CAT_CLASS; + boolean rcls = rhi.getVal(CAT) == CAT_CLASS; + result = lcls == rcls ? 0 : (lcls ? -1 : 1); + if (result == 0) { + result = (lcls ? lhi.name : lhi.cls).compareTo(rcls ? rhi.name : rhi.cls); + if (result == 0) { + result = lhi.getVal(CAT)- rhi.getVal(CAT); + if (result == 0) { + result = lhi.name.compareTo(rhi.name); + if (result == 0 && !lcls) { + result = lhi.sig.compareTo(rhi.sig); + } + } + } + } + } + return result; + } + }; + return c; + } + + /** + * Write the data in report format. + */ + public void print(PrintWriter pw, boolean detail) { + StringBuffer buf = new StringBuffer(); + + // remove all occurrences of icu packages from the param string + // fortunately, all the packages have 4 chars (lang, math, text, util). + String xsig = sig; + if (!detail) { + final String ICUPACK = "com.ibm.icu."; + StringBuffer tbuf = new StringBuffer(); + for (int i = 0; i < sig.length();) { + int n = sig.indexOf(ICUPACK, i); + if (n == -1) { + tbuf.append(sig.substring(i)); + break; + } + tbuf.append(sig.substring(i, n)); + i = n + ICUPACK.length() + 5; // trailing 'xxxx.' + } + xsig = tbuf.toString(); + } + + // construct signature + for (int i = STA; i < CAT; ++i) { // include status + String s = get(i, false); + if (s != null && s.length() > 0) { + buf.append(s); + buf.append(' '); + } + } + + int val = getVal(CAT); + switch (val) { + case CAT_CLASS: + if (sig.indexOf("extends") == -1) { + buf.append("interface "); + } else { + buf.append("class "); + } + if (cls.length() > 0) { + buf.append(cls); + buf.append('.'); + } + buf.append(name); + if (detail) { + buf.append(' '); + buf.append(sig); + } + break; + + case CAT_FIELD: + buf.append(xsig); + buf.append(' '); + buf.append(name); + break; + + case CAT_METHOD: + case CAT_CONSTRUCTOR: + int n = xsig.indexOf('('); + if (n > 0) { + buf.append(xsig.substring(0, n)); + buf.append(' '); + } else { + n = 0; + } + buf.append(name); + buf.append(xsig.substring(n)); + break; + } + + pw.print(buf.toString()); + } + + public void println(PrintWriter pw, boolean detail) { + print(pw, detail); + pw.println(); + } + + private static final String[] typeNames = { + "status", "visibility", "static", "final", "synchronized", + "abstract", "category", "package", "class", "name", "signature" + }; + + private static final String[][] names = { + { "(draft) ", "(stable) ", "(deprecated)", "(obsolete) " }, + { "package", "public", "protected", "private" }, + { "", "static" }, + { "", "final" }, + { "", "synchronized" }, + { "", "abstract" }, + { "class", "field", "constructor", "method" }, + null, + null, + null, + null, + null + }; + + private static final String[][] shortNames = { + { "DR", "ST", "DP", "OB" }, + { "PK", "PB", "PT", "PR" }, + { "NS", "ST" }, + { "NF", "FN" }, + { "NS", "SY" }, + { "NA", "AB" }, + { "L", "F", "C", "M" }, + null, + null, + null, + null, + null + }; + + private static void validateType(int typ) { + if (typ < 0 || typ > NUM_TYPES) { + throw new IllegalArgumentException("bad type index: " + typ); + } + } + + public String toString() { + return get(NAM, true); + } +} diff --git a/icu4j/src/com/ibm/icu/dev/tool/docs/GatherAPIData.java b/icu4j/src/com/ibm/icu/dev/tool/docs/GatherAPIData.java new file mode 100644 index 00000000000..13ac0204529 --- /dev/null +++ b/icu4j/src/com/ibm/icu/dev/tool/docs/GatherAPIData.java @@ -0,0 +1,408 @@ +/** +******************************************************************************* +* Copyright (C) 2004, International Business Machines Corporation and * +* others. All Rights Reserved. * +******************************************************************************* +*/ + +/** + * Generate a list of ICU's public APIs, sorted by qualified name and signature + * public APIs are all non-internal, non-package apis in com.ibm.icu.[lang|math|text|util]. + * For each API, list + * - public, package, protected, or private (PB PK PT PR) + * - static or non-static (STK NST) + * - final or non-final (FN NF) + * - synchronized or non-synchronized (SYN NSY) + * - stable, draft, deprecated, obsolete (ST DR DP OB) + * - abstract or non-abstract (AB NA) + * - constructor, member, field (C M F) + * + * Requires JDK 1.4.2 or later + * + * Sample compilation: + * c:/doug/java/jdk1.4.2/build/windows-i586/bin/javac *.java + * + * Sample execution + * c:/j2sdk1.4.2/bin/javadoc + * -classpath c:/jd2sk1.4.2/lib/tools.jar + * -doclet com.ibm.icu.dev.tool.docs.GatherAPIData + * -docletpath c:/doug/cvsproj/icu4j/src + * -sourcepath c:/doug/cvsproj/icu4j/src + * -name "ICU4J 3.0" + * -output icu4j30.api + * -zip + * com.ibm.icu.text + * + * todo: separate generation of data files (which requires taglet) from + * comparison and report generation (which does not require it) + * todo: provide command-line control of filters of which subclasses/packages to process + * todo: record full inheritance heirarchy, not just immediate inheritance + * todo: allow for aliasing comparisons (force (pkg.)*class to be treated as though it + * were in a different pkg/class heirarchy (facilitates comparison of icu4j and java) + */ + +package com.ibm.icu.dev.tool.docs; + +// standard release sdk won't work, need internal build to get access to javadoc +import com.sun.javadoc.*; +import java.io.*; +import java.util.*; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +public class GatherAPIData { + RootDoc root; + TreeSet results; + String srcName = "Current"; // default source name + String output; // name of output file to write + String base; // strip this prefix + String filter; // filter classes by name + boolean zip; + + public static int optionLength(String option) { + if (option.equals("-name")) { + return 2; + } else if (option.equals("-output")) { + return 2; + } else if (option.equals("-base")) { + return 2; + } else if (option.equals("-filter")) { + return 2; + } else if (option.equals("-zip")) { + return 1; + } + return 0; + } + + public static boolean start(RootDoc root) { + return new GatherAPIData(root).run(); + } + + GatherAPIData(RootDoc root) { + this.root = root; + + String[][] options = root.options(); + for (int i = 0; i < options.length; ++i) { + String opt = options[i][0]; + if (opt.equals("-name")) { + this.srcName = options[i][1]; + } else if (opt.equals("-output")) { + this.output = options[i][1]; + } else if (opt.equals("-base")) { + this.base = options[i][1]; // should not include '.' + } else if (opt.equals("-filter")) { + this.filter = options[i][1]; + } else if (opt.equals("-zip")) { + this.zip = true; + } + } + + results = new TreeSet(APIInfo.defaultComparator()); + } + + private boolean run() { + doDocs(root.classes()); + + OutputStream os = System.out; + if (output != null) { + try { + if (zip) { + ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(output + ".zip")); + zos.putNextEntry(new ZipEntry(output)); + os = zos; + } else { + os = new FileOutputStream(output); + } + } + catch (IOException e) { + RuntimeException re = new RuntimeException(e.getMessage()); + re.initCause(e); + throw re; + } + } + + BufferedWriter bw = null; + try { + OutputStreamWriter osw = new OutputStreamWriter(os, "UTF-8"); + bw = new BufferedWriter(osw); + + // writing data file + bw.write(String.valueOf(APIInfo.VERSION) + APIInfo.SEP); // header version + bw.write(srcName + APIInfo.SEP); // source name + bw.write((base == null ? "" : base) + APIInfo.SEP); // base + bw.newLine(); + writeResults(results, bw); + bw.close(); // should flush, close all, etc +// if (zip) { +// ((ZipOutputStream)os).finish(); +// } + } catch (IOException e) { + try { bw.close(); } catch (IOException e2) {} + RuntimeException re = new RuntimeException("write error: " + e.getMessage()); + re.initCause(e); + throw re; + } + + return false; + } + + private void doDocs(ProgramElementDoc[] docs) { + if (docs != null && docs.length > 0) { + for (int i = 0; i < docs.length; ++i) { + doDoc(docs[i]); + } + } + } + + private void doDoc(ProgramElementDoc doc) { + if (ignore(doc)) return; + + if (doc.isClass() || doc.isInterface()) { + ClassDoc cdoc = (ClassDoc)doc; + doDocs(cdoc.fields()); + doDocs(cdoc.constructors()); + doDocs(cdoc.methods()); + doDocs(cdoc.innerClasses()); + } + + APIInfo info = createInfo(doc); + if (info != null) { + results.add(info); + } + } + + private boolean ignore(ProgramElementDoc doc) { + if (doc == null) return true; + if (doc.isPrivate() || doc.isPackagePrivate()) return true; + if (doc instanceof ConstructorDoc && ((ConstructorDoc)doc).isSynthetic()) return true; + if (doc.qualifiedName().indexOf(".misc") != -1) { System.out.println("misc: " + doc.qualifiedName()); return true; } + Tag[] tags = doc.tags(); + for (int i = 0; i < tags.length; ++i) { + if (tagKindIndex(tags[i].kind()) == INTERNAL) return true; + } + + return false; + } + + private static void writeResults(Collection c, BufferedWriter w) { + Iterator iter = c.iterator(); + while (iter.hasNext()) { + APIInfo info = (APIInfo)iter.next(); + info.writeln(w); + } + } + + private String trimBase(String arg) { + if (base != null) { + for (int n = arg.indexOf(base); n != -1; n = arg.indexOf(base, n)) { + arg = arg.substring(0, n) + arg.substring(n+base.length()); + } + } + return arg; + } + + public APIInfo createInfo(ProgramElementDoc doc) { + + // Doc. name + // Doc. isField, isMethod, isConstructor, isClass, isInterface + // ProgramElementDoc. containingClass, containingPackage + // ProgramElementDoc. isPublic, isProtected, isPrivate, isPackagePrivate + // ProgramElementDoc. isStatic, isFinal + // MemberDoc.isSynthetic + // ExecutableMemberDoc isSynchronized, signature + // Type.toString() // e.g. "String[][]" + // ClassDoc.isAbstract, superClass, interfaces, fields, methods, constructors, innerClasses + // FieldDoc type + // ConstructorDoc qualifiedName + // MethodDoc isAbstract, returnType + + APIInfo info = new APIInfo(); + + // status + info.setType(APIInfo.STA, tagStatus(doc)); + + // visibility + if (doc.isPublic()) { + info.setPublic(); + } else if (doc.isProtected()) { + info.setProtected(); + } else if (doc.isPrivate()) { + info.setPrivate(); + } else { + // default is package + } + + // static + if (doc.isStatic()) { + info.setStatic(); + } else { + // default is non-static + } + + // final + if (doc.isFinal()) { + info.setFinal(); + } else { + // default is non-final + } + + // type + if (doc.isField()) { + info.setField(); + } else if (doc.isMethod()) { + info.setMethod(); + } else if (doc.isConstructor()) { + info.setConstructor(); + } else if (doc.isClass() || doc.isInterface()) { + info.setClass(); + } + + info.setPackage(trimBase(doc.containingPackage().name())); + info.setClassName((doc.isClass() || doc.isInterface() || (doc.containingClass() == null)) + ? "" + : trimBase(doc.containingClass().name())); + info.setName(trimBase(doc.name())); + + if (doc instanceof FieldDoc) { + FieldDoc fdoc = (FieldDoc)doc; + info.setSignature(trimBase(fdoc.type().toString())); + } else if (doc instanceof ClassDoc) { + ClassDoc cdoc = (ClassDoc)doc; + + if (cdoc.isClass() && cdoc.isAbstract()) { // interfaces are abstract by default, don't mark them as abstract + info.setAbstract(); + } + + StringBuffer buf = new StringBuffer(); + if (cdoc.isClass()) { + buf.append("extends "); + buf.append(cdoc.superclass().qualifiedName()); + } + ClassDoc[] imp = cdoc.interfaces(); + if (imp != null && imp.length > 0) { + if (buf.length() > 0) { + buf.append(" "); + } + buf.append("implements"); + for (int i = 0; i < imp.length; ++i) { + if (i != 0) { + buf.append(","); + } + buf.append(" "); + buf.append(imp[i].qualifiedName()); + } + } + info.setSignature(trimBase(buf.toString())); + } else { + ExecutableMemberDoc emdoc = (ExecutableMemberDoc)doc; + if (emdoc.isSynchronized()) { + info.setSynchronized(); + } + + if (doc instanceof MethodDoc) { + MethodDoc mdoc = (MethodDoc)doc; + if (mdoc.isAbstract()) { + info.setAbstract(); + } + info.setSignature(trimBase(mdoc.returnType().toString() + emdoc.signature())); + } else { + // constructor + info.setSignature(trimBase(emdoc.signature())); + } + } + + return info; + } + + private static int tagStatus(final Doc doc) { + class Result { + int res = -1; + void set(int val) { if (res != -1) throw new RuntimeException("bad doc: " + doc); res = val; } + int get() { + if (res == -1) { + System.err.println("warning: no tag for " + doc); + return 0; + } + return res; + } + }; + + Tag[] tags = doc.tags(); + Result result = new Result(); + for (int i = 0; i < tags.length; ++i) { + Tag tag = tags[i]; + + String kind = tag.kind(); + int ix = tagKindIndex(kind); + + switch (ix) { + case INTERNAL: + result.set(-2); + break; + + case DRAFT: + result.set(APIInfo.STA_DRAFT); + break; + + case STABLE: + result.set(APIInfo.STA_STABLE); + break; + + case DEPRECATED: + result.set(APIInfo.STA_DEPRECATED); + break; + + case OBSOLETE: + result.set(APIInfo.STA_OBSOLETE); + break; + + case SINCE: + case EXCEPTION: + case VERSION: + case UNKNOWN: + case AUTHOR: + case SEE: + case PARAM: + case RETURN: + case THROWS: + case SERIAL: + break; + + default: + throw new RuntimeException("unknown index " + ix + " for tag: " + kind); + } + } + + return result.get(); + } + + private static final int UNKNOWN = -1; + private static final int INTERNAL = 0; + private static final int DRAFT = 1; + private static final int STABLE = 2; + private static final int SINCE = 3; + private static final int DEPRECATED = 4; + private static final int AUTHOR = 5; + private static final int SEE = 6; + private static final int VERSION = 7; + private static final int PARAM = 8; + private static final int RETURN = 9; + private static final int THROWS = 10; + private static final int OBSOLETE = 11; + private static final int EXCEPTION = 12; + private static final int SERIAL = 13; + + private static int tagKindIndex(String kind) { + final String[] tagKinds = { + "@internal", "@draft", "@stable", "@since", "@deprecated", "@author", "@see", "@version", + "@param", "@return", "@throws", "@obsolete", "@exception", "@serial" + }; + + for (int i = 0; i < tagKinds.length; ++i) { + if (kind.equals(tagKinds[i])) { + return i; + } + } + return UNKNOWN; + } +} diff --git a/icu4j/src/com/ibm/icu/dev/tool/docs/ReportAPI.java b/icu4j/src/com/ibm/icu/dev/tool/docs/ReportAPI.java new file mode 100644 index 00000000000..d44b5ea6cd9 --- /dev/null +++ b/icu4j/src/com/ibm/icu/dev/tool/docs/ReportAPI.java @@ -0,0 +1,379 @@ +/** +******************************************************************************* +* Copyright (C) 2004, International Business Machines Corporation and * +* others. All Rights Reserved. * +******************************************************************************* +*/ + +/** + * Compare two API files (generated by GatherAPIData) and generate a report + * on the differences. + * + * Sample invocation: + * java -old: icu4j28.api.zip -new: icu4j30.api -html -out: icu4j_compare_28_30.html + */ + +package com.ibm.icu.dev.tool.docs; + +import com.sun.javadoc.*; +import java.io.*; +import java.util.*; +import java.util.zip.*; + +public class ReportAPI { + APIData oldData; + APIData newData; + boolean html; + String outputFile; + + TreeSet added; + TreeSet removed; + ArrayList changed; + + static final class APIData { + private int version; + private String name; + private String base; + private TreeSet set; + + static APIData read(BufferedReader br) { + try { + APIData data = new APIData(); + + data.version = Integer.parseInt(APIInfo.readToken(br)); // version + data.name = APIInfo.readToken(br); + data.base = APIInfo.readToken(br); // base + br.readLine(); + + data.set = new TreeSet(APIInfo.defaultComparator()); + for (APIInfo info = new APIInfo(); info.read(br); info = new APIInfo()) { + data.set.add(info); + } + return data; + } + catch (IOException e) { + RuntimeException re = new RuntimeException("error reading api data"); + re.initCause(e); + throw re; + } + } + + static APIData read(String fileName) { + try { + InputStream is; + if (fileName.endsWith(".zip")) { + ZipFile zf = new ZipFile(fileName); + Enumeration entryEnum = zf.entries(); + if (entryEnum.hasMoreElements()) { + ZipEntry entry = (ZipEntry)entryEnum.nextElement(); + is = zf.getInputStream(entry); + // we only handle one!!! + } else { + throw new IOException("zip file is empty"); + } + } else { + File f = new File(fileName); + is = new FileInputStream(f); + } + InputStreamReader isr = new InputStreamReader(is); + return read(new BufferedReader(isr)); + } + catch (IOException e) { + RuntimeException re = new RuntimeException("error getting info stream: " + fileName); + re.initCause(e); + throw re; + } + } + } + + static final class DeltaInfo extends APIInfo { + private APIInfo added; + private APIInfo removed; + + DeltaInfo(APIInfo added, APIInfo removed) { + this.added = added; + this.removed = removed; + } + + public int getVal(int typ) { + return added.getVal(typ); + } + + public String get(int typ, boolean brief) { + return added.get(typ, brief); + } + } + + public static void main(String[] args) { + String oldFile = null; + String newFile = null; + String outFile = null; + boolean html = false; + + for (int i = 0; i < args.length; ++i) { + String arg = args[i]; + if (arg.equals("-old:")) { + oldFile = args[++i]; + } else if (arg.equals("-new:")) { + newFile = args[++i]; + } else if (arg.equals("-out:")) { + outFile = args[++i]; + } else if (arg.equals("-html")) { + html = true; + } + } + + new ReportAPI(oldFile, newFile).writeReport(outFile, html); + } + + ReportAPI(String oldFile, String newFile) { + oldData = APIData.read(oldFile); + newData = APIData.read(newFile); + + // writing comparison info + removed = (TreeSet)oldData.set.clone(); + removed.removeAll(newData.set); + + added = (TreeSet)newData.set.clone(); + added.removeAll(oldData.set); + + changed = new ArrayList(); + Iterator ai = added.iterator(); + Iterator ri = removed.iterator(); + Comparator c = APIInfo.changedComparator(); + APIInfo a = null, r = null; + while (ai.hasNext() && ri.hasNext()) { + if (a == null) a = (APIInfo)ai.next(); + if (r == null) r = (APIInfo)ri.next(); + int result = c.compare(a, r); + if (result < 0) { + a = null; + } else if (result > 0) { + r = null; + } else { + changed.add(new DeltaInfo(a, r)); + a = null; ai.remove(); + r = null; ri.remove(); + } + } + + added = stripAndResort(added); + removed = stripAndResort(removed); + } + + private boolean writeReport(String outFile, boolean html) { + OutputStream os = System.out; + if (outFile != null) { + try { + os = new FileOutputStream(outFile); + } + catch (FileNotFoundException e) { + RuntimeException re = new RuntimeException(e.getMessage()); + re.initCause(e); + throw re; + } + } + + PrintWriter pw = null; + try { + pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(os, "UTF-8"))); + } + catch (UnsupportedEncodingException e) { + throw new InternalError(); // UTF-8 should always be supported + } + + String title = "ICU4J API Comparison: " + oldData.name + " with " + newData.name; + String info = "Contents generated by ReportAPI tool."; + String copyright = "Copyright (C) 2004, International Business Machines Corporation, All Rights Reserved."; + + if (html) { + pw.println(""); + pw.println(""); + pw.println(""); + pw.println(""); + pw.println("" + title + ""); + pw.println(""); + pw.println(""); + + pw.println("

" + title + "

"); + + pw.println(); + pw.println("
"); + pw.println("

Removed from " + oldData.name +"

"); + if (removed.size() > 0) { + printResults(removed, pw, true, false); + } else { + pw.println("

(no API removed)

"); + } + + pw.println(); + pw.println("
"); + pw.println("

Changed in " + newData.name + " (old, new)

"); + if (changed.size() > 0) { + printResults(changed, pw, true, true); + } else { + pw.println("

(no API changed)

"); + } + + pw.println(); + pw.println("
"); + pw.println("

Added in " + newData.name + "

"); + if (added.size() > 0) { + printResults(added, pw, true, false); + } else { + pw.println("

(no API added)

"); + } + + pw.println("
"); + pw.println("

" + info + "
" + copyright + "

"); + pw.println(""); + pw.println(""); + } else { + pw.println(title); + pw.println(); + pw.println(); + + pw.println("=== Removed from " + oldData.name + " ==="); + if (removed.size() > 0) { + printResults(removed, pw, false, false); + } else { + pw.println("(no API removed)"); + } + + pw.println(); + pw.println(); + pw.println("=== Changed in " + newData.name + " (old, new) ==="); + if (changed.size() > 0) { + printResults(changed, pw, false, true); + } else { + pw.println("(no API changed)"); + } + + pw.println(); + pw.println(); + pw.println("=== Added in " + newData.name + " ==="); + if (added.size() > 0) { + printResults(added, pw, false, false); + } else { + pw.println("(no API added)"); + } + + pw.println(); + pw.println("================"); + pw.println(info); + pw.println(copyright); + } + pw.close(); + + return false; + } + + private static void printResults(Collection c, PrintWriter pw, boolean html, boolean isChangedAPIs) { + Iterator iter = c.iterator(); + String pack = null; + String clas = null; + while (iter.hasNext()) { + APIInfo info = (APIInfo)iter.next(); + + String packageName = info.getPackageName(); + if (!packageName.equals(pack)) { + if (html) { + if (clas != null) { + pw.println(""); + } + if (pack != null) { + pw.println(""); + } + pw.println(); + pw.println("

Package " + packageName + "

"); + pw.print(""); + } + pw.println("
  • " + className); + pw.println(""); + } + if (pack != null) { + pw.println(""); + } + } + pw.println(); + } + + private static TreeSet stripAndResort(TreeSet t) { + stripClassInfo(t); + TreeSet r = new TreeSet(APIInfo.classFirstComparator()); + r.addAll(t); + return r; + } + + private static void stripClassInfo(Collection c) { + // c is sorted with class info first + Iterator iter = c.iterator(); + String cname = null; + while (iter.hasNext()) { + APIInfo info = (APIInfo)iter.next(); + String className = info.getClassName(); + if (cname != null) { + if (cname.equals(className)) { + iter.remove(); + continue; + } + cname = null; + } + if (info.isClass()) { + cname = info.getName(); + } + } + } +}