2 *******************************************************************************
3 * Copyright (C) 2004-2012, 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.5 or later
23 * c:/doug/java/jdk1.5/build/windows-i586/bin/javac *.java
26 * c:/j2sdk1.5/bin/javadoc
27 * -classpath c:/jd2sk1.5/lib/tools.jar
28 * -doclet com.ibm.icu.dev.tool.docs.GatherAPIData
29 * -docletpath c:/doug/icu4j/tools/build/out/lib/icu4j-build-tools.jar
30 * -sourcepath c:/doug/icu4j/main/classes/core/src
32 * -output icu4j42.api2
35 * com.ibm.icu.lang com.ibm.icu.math com.ibm.icu.text com.ibm.icu.util
37 * todo: provide command-line control of filters of which subclasses/packages to process
38 * todo: record full inheritance hierarchy, not just immediate inheritance
39 * todo: allow for aliasing comparisons (force (pkg.)*class to be treated as though it
40 * were in a different pkg/class hierarchy (facilitates comparison of icu4j and java)
43 package com.ibm.icu.dev.tool.docs;
45 // standard release sdk won't work, need internal build to get access to javadoc
46 import java.io.BufferedWriter;
47 import java.io.FileOutputStream;
48 import java.io.IOException;
49 import java.io.OutputStream;
50 import java.io.OutputStreamWriter;
51 import java.util.Collection;
52 import java.util.Iterator;
53 import java.util.TreeSet;
54 import java.util.regex.Pattern;
55 import java.util.zip.GZIPOutputStream;
56 import java.util.zip.ZipEntry;
57 import java.util.zip.ZipOutputStream;
59 import com.sun.javadoc.ClassDoc;
60 import com.sun.javadoc.ConstructorDoc;
61 import com.sun.javadoc.ExecutableMemberDoc;
62 import com.sun.javadoc.FieldDoc;
63 import com.sun.javadoc.LanguageVersion;
64 import com.sun.javadoc.MemberDoc;
65 import com.sun.javadoc.MethodDoc;
66 import com.sun.javadoc.ProgramElementDoc;
67 import com.sun.javadoc.RootDoc;
68 import com.sun.javadoc.Tag;
70 public class GatherAPIData {
73 String srcName = "Current"; // default source name
74 String output; // name of output file to write
75 String base; // strip this prefix
82 public static int optionLength(String option) {
83 if (option.equals("-name")) {
85 } else if (option.equals("-output")) {
87 } else if (option.equals("-base")) {
89 } else if (option.equals("-filter")) {
91 } else if (option.equals("-zip")) {
93 } else if (option.equals("-gzip")) {
95 } else if (option.equals("-internal")) {
97 } else if (option.equals("-version")) {
103 public static boolean start(RootDoc root) {
104 return new GatherAPIData(root).run();
108 * If you don't do this, javadoc treats enums like regular classes!
109 * doesn't matter if you pass -source 1.5 or not.
111 public static LanguageVersion languageVersion() {
112 return LanguageVersion.JAVA_1_5;
115 GatherAPIData(RootDoc root) {
118 String[][] options = root.options();
119 for (int i = 0; i < options.length; ++i) {
120 String opt = options[i][0];
121 if (opt.equals("-name")) {
122 this.srcName = options[i][1];
123 } else if (opt.equals("-output")) {
124 this.output = options[i][1];
125 } else if (opt.equals("-base")) {
126 this.base = options[i][1]; // should not include '.'
127 } else if (opt.equals("-filter")) {
128 this.pat = Pattern.compile(options[i][1], Pattern.CASE_INSENSITIVE);
129 } else if (opt.equals("-zip")) {
131 } else if (opt.equals("-gzip")) {
133 } else if (opt.equals("-internal")) {
134 this.internal = true;
135 } else if (opt.equals("-version")) {
140 results = new TreeSet(APIInfo.defaultComparator());
143 private boolean run() {
144 doDocs(root.classes());
146 OutputStream os = System.out;
147 if (output != null) {
148 ZipOutputStream zos = null;
151 zos = new ZipOutputStream(new FileOutputStream(output + ".zip"));
152 zos.putNextEntry(new ZipEntry(output));
155 os = new GZIPOutputStream(new FileOutputStream(output + ".gz"));
157 os = new FileOutputStream(output);
160 catch (IOException e) {
161 RuntimeException re = new RuntimeException(e.getMessage());
169 } catch (Exception e) {
176 BufferedWriter bw = null;
178 OutputStreamWriter osw = new OutputStreamWriter(os, "UTF-8");
179 bw = new BufferedWriter(osw);
182 bw.write(String.valueOf(APIInfo.VERSION) + APIInfo.SEP); // header version
183 bw.write(srcName + APIInfo.SEP); // source name
184 bw.write((base == null ? "" : base) + APIInfo.SEP); // base
186 writeResults(results, bw);
187 bw.close(); // should flush, close all, etc
188 } catch (IOException e) {
189 try { bw.close(); } catch (IOException e2) {}
190 RuntimeException re = new RuntimeException("write error: " + e.getMessage());
198 private void doDocs(ProgramElementDoc[] docs) {
199 if (docs != null && docs.length > 0) {
200 for (int i = 0; i < docs.length; ++i) {
206 private void doDoc(ProgramElementDoc doc) {
207 if (ignore(doc)) return;
209 if (doc.isClass() || doc.isInterface()) {
210 ClassDoc cdoc = (ClassDoc)doc;
211 doDocs(cdoc.fields());
212 doDocs(cdoc.constructors());
213 doDocs(cdoc.methods());
214 // don't call this to iterate over inner classes,
215 // root.classes already includes them
216 // doDocs(cdoc.innerClasses());
219 APIInfo info = createInfo(doc);
225 // Sigh. Javadoc doesn't indicate when the compiler generates
226 // the values and valueOf enum methods. The position of the
227 // method for these is not always the same as the position of
228 // the class, though it often is, so we can't use that.
230 private boolean isIgnoredEnumMethod(ProgramElementDoc doc) {
231 if (doc.isMethod() && doc.containingClass().isEnum()) {
232 // System.out.println("*** " + doc.qualifiedName() + " pos: " +
233 // doc.position().line() +
234 // " containined by: " +
235 // doc.containingClass().name() +
237 // doc.containingClass().position().line());
238 // return doc.position().line() == doc.containingClass().position().line();
240 String name = doc.name();
241 // assume we don't have enums that overload these method names.
242 return "values".equals(name) || "valueOf".equals(name);
247 // isSynthesized also doesn't seem to work. Let's do this, documenting
248 // synthesized constructors for abstract classes is kind of weird.
249 // We can't actually tell if the constructor was synthesized or is
250 // actually in the docs, but this shouldn't matter. We don't really
251 // care if we didn't properly document the draft status of
252 // default constructors for abstract classes.
254 private boolean isAbstractClassDefaultConstructor(ProgramElementDoc doc) {
255 return doc.isConstructor()
256 && doc.containingClass().isAbstract()
257 && "()".equals(((ConstructorDoc) doc).signature());
260 private boolean ignore(ProgramElementDoc doc) {
261 if (doc == null) return true;
262 if (doc.isPrivate() || doc.isPackagePrivate()) return true;
263 if (doc instanceof MemberDoc && ((MemberDoc)doc).isSynthetic()) return true;
264 if (doc.qualifiedName().indexOf(".misc") != -1) {
265 System.out.println("misc: " + doc.qualifiedName()); return true;
267 if (isIgnoredEnumMethod(doc)) {
270 if (isAbstractClassDefaultConstructor(doc)) {
274 if (false && doc.qualifiedName().indexOf("LocaleDisplayNames") != -1) {
275 System.err.print("*** " + doc.qualifiedName() + ":");
276 if (doc.isClass()) System.err.print(" class");
277 if (doc.isConstructor()) System.err.print(" constructor");
278 if (doc.isEnum()) System.err.print(" enum");
279 if (doc.isEnumConstant()) System.err.print(" enum_constant");
280 if (doc.isError()) System.err.print(" error");
281 if (doc.isException()) System.err.print(" exception");
282 if (doc.isField()) System.err.print(" field");
283 if (doc.isInterface()) System.err.print(" interface");
284 if (doc.isMethod()) System.err.print(" method");
285 if (doc.isOrdinaryClass()) System.err.print(" ordinary_class");
286 System.err.println();
289 if (!internal) { // debug
290 Tag[] tags = doc.tags();
291 for (int i = 0; i < tags.length; ++i) {
292 if (tagKindIndex(tags[i].kind()) == INTERNAL) { return true; }
295 if (pat != null && (doc.isClass() || doc.isInterface())) {
296 if (!pat.matcher(doc.name()).matches()) {
303 private static void writeResults(Collection c, BufferedWriter w) {
304 Iterator iter = c.iterator();
305 while (iter.hasNext()) {
306 APIInfo info = (APIInfo)iter.next();
311 private String trimBase(String arg) {
313 for (int n = arg.indexOf(base); n != -1; n = arg.indexOf(base, n)) {
314 arg = arg.substring(0, n) + arg.substring(n+base.length());
320 public APIInfo createInfo(ProgramElementDoc doc) {
323 // Doc. isField, isMethod, isConstructor, isClass, isInterface
324 // ProgramElementDoc. containingClass, containingPackage
325 // ProgramElementDoc. isPublic, isProtected, isPrivate, isPackagePrivate
326 // ProgramElementDoc. isStatic, isFinal
327 // MemberDoc.isSynthetic
328 // ExecutableMemberDoc isSynchronized, signature
329 // Type.toString() // e.g. "String[][]"
330 // ClassDoc.isAbstract, superClass, interfaces, fields, methods, constructors, innerClasses
332 // ConstructorDoc qualifiedName
333 // MethodDoc isAbstract, returnType
335 APIInfo info = new APIInfo();
337 info.includeStatusVersion(true);
341 String[] version = new String[1];
342 info.setType(APIInfo.STA, tagStatus(doc, version));
343 info.setStatusVersion(version[0]);
346 if (doc.isPublic()) {
348 } else if (doc.isProtected()) {
350 } else if (doc.isPrivate()) {
353 // default is package
357 if (doc.isStatic()) {
360 // default is non-static
367 // default is non-final
373 } else if (doc.isMethod()) {
375 } else if (doc.isConstructor()) {
376 info.setConstructor();
377 } else if (doc.isClass() || doc.isInterface()) {
381 info.setPackage(trimBase(doc.containingPackage().name()));
382 info.setClassName((doc.isClass() || doc.isInterface() || (doc.containingClass() == null))
384 : trimBase(doc.containingClass().name()));
385 info.setName(trimBase(doc.name()));
387 if (doc instanceof FieldDoc) {
388 FieldDoc fdoc = (FieldDoc)doc;
389 info.setSignature(trimBase(fdoc.type().toString()));
390 } else if (doc instanceof ClassDoc) {
391 ClassDoc cdoc = (ClassDoc)doc;
393 if (cdoc.isClass() && cdoc.isAbstract()) {
394 // interfaces are abstract by default, don't mark them as abstract
398 StringBuffer buf = new StringBuffer();
399 if (cdoc.isClass()) {
400 buf.append("extends ");
401 buf.append(cdoc.superclass().qualifiedName());
403 ClassDoc[] imp = cdoc.interfaces();
404 if (imp != null && imp.length > 0) {
405 if (buf.length() > 0) {
408 buf.append("implements");
409 for (int i = 0; i < imp.length; ++i) {
414 buf.append(imp[i].qualifiedName());
417 info.setSignature(trimBase(buf.toString()));
419 ExecutableMemberDoc emdoc = (ExecutableMemberDoc)doc;
420 if (emdoc.isSynchronized()) {
421 info.setSynchronized();
424 if (doc instanceof MethodDoc) {
425 MethodDoc mdoc = (MethodDoc)doc;
426 if (mdoc.isAbstract()) {
429 info.setSignature(trimBase(mdoc.returnType().toString() + emdoc.signature()));
432 info.setSignature(trimBase(emdoc.signature()));
439 private int tagStatus(final ProgramElementDoc doc, String[] version) {
444 if (val == APIInfo.STA_DEPRECATED) {
445 // ok to have both a 'standard' tag and deprecated
447 } else if (res != APIInfo.STA_DEPRECATED) {
448 // if already not deprecated, this is an error
449 System.err.println("bad doc: " + doc + " both: "
450 + APIInfo.getTypeValName(APIInfo.STA, res) + " and: "
451 + APIInfo.getTypeValName(APIInfo.STA, val));
455 // ok to replace with new tag
460 System.err.println("warning: no tag for " + doc);
467 Tag[] tags = doc.tags();
468 Result result = new Result();
469 String statusVer = "";
470 for (int i = 0; i < tags.length; ++i) {
473 String kind = tag.kind();
474 int ix = tagKindIndex(kind);
478 result.set(internal ? APIInfo.STA_INTERNAL : -2); // -2 for legacy compatibility
479 statusVer = getStatusVersion(tag);
483 result.set(APIInfo.STA_DRAFT);
484 statusVer = getStatusVersion(tag);
488 result.set(APIInfo.STA_STABLE);
489 statusVer = getStatusVersion(tag);
493 result.set(APIInfo.STA_DEPRECATED);
494 statusVer = getStatusVersion(tag);
498 result.set(APIInfo.STA_OBSOLETE);
499 statusVer = getStatusVersion(tag);
515 throw new RuntimeException("unknown index " + ix + " for tag: " + kind);
519 if (version != null) {
520 version[0] = statusVer;
525 private String getStatusVersion(Tag tag) {
526 String text = tag.text();
527 if (text != null && text.length() > 0) {
528 // Extract version string
531 for (; i < text.length(); i++) {
532 char ch = text.charAt(i);
533 if (ch == '.' || (ch >= '0' && ch <= '9')) {
537 } else if (start != -1) {
542 return text.substring(start, i);
548 private static final int UNKNOWN = -1;
549 private static final int INTERNAL = 0;
550 private static final int DRAFT = 1;
551 private static final int STABLE = 2;
552 private static final int SINCE = 3;
553 private static final int DEPRECATED = 4;
554 private static final int AUTHOR = 5;
555 private static final int SEE = 6;
556 private static final int VERSION = 7;
557 private static final int PARAM = 8;
558 private static final int RETURN = 9;
559 private static final int THROWS = 10;
560 private static final int OBSOLETE = 11;
561 private static final int EXCEPTION = 12;
562 private static final int SERIAL = 13;
564 private static int tagKindIndex(String kind) {
565 final String[] tagKinds = {
566 "@internal", "@draft", "@stable", "@since", "@deprecated", "@author", "@see",
567 "@version", "@param", "@return", "@throws", "@obsolete", "@exception", "@serial"
570 for (int i = 0; i < tagKinds.length; ++i) {
571 if (kind.equals(tagKinds[i])) {