• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /**
2  *******************************************************************************
3  * Copyright (C) 2004-2014, 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.5 or later
21  *
22  * Sample compilation:
23  * c:/doug/java/jdk1.5/build/windows-i586/bin/javac *.java
24  *
25  * Sample execution
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
31  *   -name "ICU4J 4.2"
32  *   -output icu4j42.api2
33  *   -gzip
34  *   -source 1.5
35  *   com.ibm.icu.lang com.ibm.icu.math com.ibm.icu.text com.ibm.icu.util
36  *
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)
41  */
42 
43 package com.ibm.icu.dev.tool.docs;
44 
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;
58 
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;
69 
70 public class GatherAPIData {
71     RootDoc root;
72     TreeSet results;
73     String srcName = "Current"; // default source name
74     String output; // name of output file to write
75     String base; // strip this prefix
76     Pattern pat;
77     boolean zip;
78     boolean gzip;
79     boolean internal;
80     boolean version;
81 
optionLength(String option)82     public static int optionLength(String option) {
83         if (option.equals("-name")) {
84             return 2;
85         } else if (option.equals("-output")) {
86             return 2;
87         } else if (option.equals("-base")) {
88             return 2;
89         } else if (option.equals("-filter")) {
90             return 2;
91         } else if (option.equals("-zip")) {
92             return 1;
93         } else if (option.equals("-gzip")) {
94             return 1;
95         } else if (option.equals("-internal")) {
96             return 1;
97         } else if (option.equals("-version")) {
98             return 1;
99         }
100         return 0;
101     }
102 
start(RootDoc root)103     public static boolean start(RootDoc root) {
104         return new GatherAPIData(root).run();
105     }
106 
107     /**
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.
110      */
languageVersion()111     public static LanguageVersion languageVersion() {
112         return LanguageVersion.JAVA_1_5;
113     }
114 
GatherAPIData(RootDoc root)115     GatherAPIData(RootDoc root) {
116         this.root = root;
117 
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")) {
130                 this.zip = true;
131             } else if (opt.equals("-gzip")) {
132                 this.gzip = true;
133             } else if (opt.equals("-internal")) {
134                 this.internal = true;
135             } else if (opt.equals("-version")) {
136                 this.version = true;
137             }
138         }
139 
140         results = new TreeSet(APIInfo.defaultComparator());
141     }
142 
run()143     private boolean run() {
144         doDocs(root.classes());
145 
146         OutputStream os = System.out;
147         if (output != null) {
148             ZipOutputStream zos = null;
149             try {
150                 if (zip) {
151                     zos = new ZipOutputStream(new FileOutputStream(output + ".zip"));
152                     zos.putNextEntry(new ZipEntry(output));
153                     os = zos;
154                 } else if (gzip) {
155                     os = new GZIPOutputStream(new FileOutputStream(output + ".gz"));
156                 } else {
157                     os = new FileOutputStream(output);
158                 }
159             }
160             catch (IOException e) {
161                 RuntimeException re = new RuntimeException(e.getMessage());
162                 re.initCause(e);
163                 throw re;
164             }
165             finally {
166                 if (zos != null) {
167                     try {
168                         zos.close();
169                     } catch (Exception e) {
170                         // ignore
171                     }
172                 }
173             }
174         }
175 
176         BufferedWriter bw = null;
177         try {
178             OutputStreamWriter osw = new OutputStreamWriter(os, "UTF-8");
179             bw = new BufferedWriter(osw);
180 
181             // writing data file
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
185             bw.newLine();
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());
191             re.initCause(e);
192             throw re;
193         }
194 
195         return false;
196     }
197 
doDocs(ProgramElementDoc[] docs)198     private void doDocs(ProgramElementDoc[] docs) {
199         if (docs != null && docs.length > 0) {
200             for (int i = 0; i < docs.length; ++i) {
201                 doDoc(docs[i]);
202             }
203         }
204     }
205 
doDoc(ProgramElementDoc doc)206     private void doDoc(ProgramElementDoc doc) {
207         if (ignore(doc)) return;
208 
209         if (doc.isClass() || doc.isInterface()) {
210             ClassDoc cdoc = (ClassDoc)doc;
211             doDocs(cdoc.fields());
212             doDocs(cdoc.constructors());
213             doDocs(cdoc.methods());
214             doDocs(cdoc.enumConstants());
215             // don't call this to iterate over inner classes,
216             // root.classes already includes them
217             // doDocs(cdoc.innerClasses());
218         }
219 
220         APIInfo info = createInfo(doc);
221         if (info != null) {
222             results.add(info);
223         }
224     }
225 
226     // Sigh. Javadoc doesn't indicate when the compiler generates
227     // the values and valueOf enum methods.  The position of the
228     // method for these is not always the same as the position of
229     // the class, though it often is, so we can't use that.
230 
isIgnoredEnumMethod(ProgramElementDoc doc)231     private boolean isIgnoredEnumMethod(ProgramElementDoc doc) {
232         if (doc.isMethod() && doc.containingClass().isEnum()) {
233             // System.out.println("*** " + doc.qualifiedName() + " pos: " +
234             //                    doc.position().line() +
235             //                    " containined by: " +
236             //                    doc.containingClass().name() +
237             //                    " pos: " +
238             //                    doc.containingClass().position().line());
239             // return doc.position().line() == doc.containingClass().position().line();
240 
241             String name = doc.name();
242             // assume we don't have enums that overload these method names.
243             return "values".equals(name) || "valueOf".equals(name);
244         }
245         return false;
246     }
247 
248     // isSynthesized also doesn't seem to work.  Let's do this, documenting
249     // synthesized constructors for abstract classes is kind of weird.
250     // We can't actually tell if the constructor was synthesized or is
251     // actually in the docs, but this shouldn't matter.  We don't really
252     // care if we didn't properly document the draft status of
253     // default constructors for abstract classes.
254 
255     // Update: We mandate a no-arg synthetic constructor with explicit
256     // javadoc comments by the policy. So, we no longer ignore abstract
257     // class's no-arg constructor blindly. -Yoshito 2014-05-21
258 
isAbstractClassDefaultConstructor(ProgramElementDoc doc)259     private boolean isAbstractClassDefaultConstructor(ProgramElementDoc doc) {
260         return doc.isConstructor()
261             && doc.containingClass().isAbstract()
262             && "()".equals(((ConstructorDoc) doc).signature());
263     }
264 
265     private static final boolean IGNORE_NO_ARG_ABSTRACT_CTOR = false;
266 
ignore(ProgramElementDoc doc)267     private boolean ignore(ProgramElementDoc doc) {
268         if (doc == null) return true;
269         if (doc.isPrivate() || doc.isPackagePrivate()) return true;
270         if (doc instanceof MemberDoc && ((MemberDoc)doc).isSynthetic()) return true;
271         if (doc.qualifiedName().indexOf(".misc") != -1) {
272             System.out.println("misc: " + doc.qualifiedName()); return true;
273         }
274         if (isIgnoredEnumMethod(doc)) {
275             return true;
276         }
277 
278         if (IGNORE_NO_ARG_ABSTRACT_CTOR && isAbstractClassDefaultConstructor(doc)) {
279             return true;
280         }
281 
282         if (false && doc.qualifiedName().indexOf("LocaleDisplayNames") != -1) {
283           System.err.print("*** " + doc.qualifiedName() + ":");
284           if (doc.isClass()) System.err.print(" class");
285           if (doc.isConstructor()) System.err.print(" constructor");
286           if (doc.isEnum()) System.err.print(" enum");
287           if (doc.isEnumConstant()) System.err.print(" enum_constant");
288           if (doc.isError()) System.err.print(" error");
289           if (doc.isException()) System.err.print(" exception");
290           if (doc.isField()) System.err.print(" field");
291           if (doc.isInterface()) System.err.print(" interface");
292           if (doc.isMethod()) System.err.print(" method");
293           if (doc.isOrdinaryClass()) System.err.print(" ordinary_class");
294           System.err.println();
295         }
296 
297         if (!internal) { // debug
298             Tag[] tags = doc.tags();
299             for (int i = 0; i < tags.length; ++i) {
300                 if (tagKindIndex(tags[i].kind()) == INTERNAL) { return true; }
301             }
302         }
303         if (pat != null && (doc.isClass() || doc.isInterface())) {
304             if (!pat.matcher(doc.name()).matches()) {
305                 return true;
306             }
307         }
308         return false;
309     }
310 
writeResults(Collection c, BufferedWriter w)311     private static void writeResults(Collection c, BufferedWriter w) {
312         Iterator iter = c.iterator();
313         while (iter.hasNext()) {
314             APIInfo info = (APIInfo)iter.next();
315             info.writeln(w);
316         }
317     }
318 
trimBase(String arg)319     private String trimBase(String arg) {
320         if (base != null) {
321             for (int n = arg.indexOf(base); n != -1; n = arg.indexOf(base, n)) {
322                 arg = arg.substring(0, n) + arg.substring(n+base.length());
323             }
324         }
325         return arg;
326     }
327 
createInfo(ProgramElementDoc doc)328     public APIInfo createInfo(ProgramElementDoc doc) {
329 
330         // Doc. name
331         // Doc. isField, isMethod, isConstructor, isClass, isInterface
332         // ProgramElementDoc. containingClass, containingPackage
333         // ProgramElementDoc. isPublic, isProtected, isPrivate, isPackagePrivate
334         // ProgramElementDoc. isStatic, isFinal
335         // MemberDoc.isSynthetic
336         // ExecutableMemberDoc isSynchronized, signature
337         // Type.toString() // e.g. "String[][]"
338         // ClassDoc.isAbstract, superClass, interfaces, fields, methods, constructors, innerClasses
339         // FieldDoc type
340         // ConstructorDoc qualifiedName
341         // MethodDoc isAbstract, returnType
342 
343         APIInfo info = new APIInfo();
344         if (version) {
345             info.includeStatusVersion(true);
346         }
347 
348         // status
349         String[] version = new String[1];
350         info.setType(APIInfo.STA, tagStatus(doc, version));
351         info.setStatusVersion(version[0]);
352 
353         // visibility
354         if (doc.isPublic()) {
355             info.setPublic();
356         } else if (doc.isProtected()) {
357             info.setProtected();
358         } else if (doc.isPrivate()) {
359             info.setPrivate();
360         } else {
361             // default is package
362         }
363 
364         // static
365         if (doc.isStatic()) {
366             info.setStatic();
367         } else {
368             // default is non-static
369         }
370 
371         // final
372         if (doc.isFinal() && !doc.isEnum()) {
373             info.setFinal();
374         } else {
375             // default is non-final
376         }
377 
378         // type
379         if (doc.isField()) {
380             info.setField();
381         } else if (doc.isMethod()) {
382             info.setMethod();
383         } else if (doc.isConstructor()) {
384             info.setConstructor();
385         } else if (doc.isClass() || doc.isInterface()) {
386             if (doc.isEnum()) {
387                 info.setEnum();
388             } else {
389                 info.setClass();
390             }
391         } else if (doc.isEnumConstant()) {
392             info.setEnumConstant();
393         }
394 
395         info.setPackage(trimBase(doc.containingPackage().name()));
396         info.setClassName((doc.isClass() || doc.isInterface() || (doc.containingClass() == null))
397                           ? ""
398                           : trimBase(doc.containingClass().name()));
399         info.setName(trimBase(doc.name()));
400 
401         if (doc instanceof FieldDoc) {
402             FieldDoc fdoc = (FieldDoc)doc;
403             info.setSignature(trimBase(fdoc.type().toString()));
404         } else if (doc instanceof ClassDoc) {
405             ClassDoc cdoc = (ClassDoc)doc;
406 
407             if (cdoc.isClass() && cdoc.isAbstract()) {
408                 // interfaces are abstract by default, don't mark them as abstract
409                 info.setAbstract();
410             }
411 
412             StringBuffer buf = new StringBuffer();
413             if (cdoc.isClass()) {
414                 buf.append("extends ");
415                 buf.append(cdoc.superclassType().toString());
416             }
417             ClassDoc[] imp = cdoc.interfaces();
418             if (imp != null && imp.length > 0) {
419                 if (buf.length() > 0) {
420                     buf.append(" ");
421                 }
422                 buf.append("implements");
423                 for (int i = 0; i < imp.length; ++i) {
424                     if (i != 0) {
425                         buf.append(",");
426                     }
427                     buf.append(" ");
428                     buf.append(imp[i].qualifiedName());
429                 }
430             }
431             info.setSignature(trimBase(buf.toString()));
432         } else {
433             ExecutableMemberDoc emdoc = (ExecutableMemberDoc)doc;
434             if (emdoc.isSynchronized()) {
435                 info.setSynchronized();
436             }
437 
438             if (doc instanceof MethodDoc) {
439                 MethodDoc mdoc = (MethodDoc)doc;
440                 if (mdoc.isAbstract()) {
441                     info.setAbstract();
442                 }
443                 info.setSignature(trimBase(mdoc.returnType().toString() + emdoc.signature()));
444             } else {
445                 // constructor
446                 info.setSignature(trimBase(emdoc.signature()));
447             }
448         }
449 
450         return info;
451     }
452 
tagStatus(final ProgramElementDoc doc, String[] version)453     private int tagStatus(final ProgramElementDoc doc, String[] version) {
454         class Result {
455             boolean deprecatedFlag = false;
456             int res = -1;
457             void set(int val) {
458                 if (res != -1) {
459                     boolean isValid = true;
460                     if (val == APIInfo.STA_DEPRECATED) {
461                         // @internal and @obsolete should be always used along with @deprecated.
462                         // no change for status
463                         isValid = (res == APIInfo.STA_INTERNAL || res == APIInfo.STA_OBSOLETE);
464                         deprecatedFlag = true;
465                     } else if (val == APIInfo.STA_INTERNAL) {
466                         // @deprecated should be always used along with @internal.
467                         // update status
468                         if (res == APIInfo.STA_DEPRECATED) {
469                             res = val;  // APIInfo.STA_INTERNAL
470                         } else {
471                             isValid = false;
472                         }
473                     } else if (val == APIInfo.STA_OBSOLETE) {
474                         // @deprecated should be always used along with @obsolete.
475                         // update status
476                         if (res == APIInfo.STA_DEPRECATED) {
477                             res = val;  // APIInfo.STA_OBSOLETE
478                         } else {
479                             isValid = false;
480                         }
481                     } else {
482                         // two different status tags must not co-exist, except for
483                         // following two cases:
484                         // 1. @internal and @deprecated
485                         // 2. @obsolete and @deprecated
486                         isValid = false;
487                     }
488                     if (!isValid) {
489                         System.err.println("bad doc: " + doc + " both: "
490                                            + APIInfo.getTypeValName(APIInfo.STA, res) + " and: "
491                                            + APIInfo.getTypeValName(APIInfo.STA, val));
492                         return;
493                     }
494                 } else {
495                     // ok to replace with new tag
496                     res = val;
497                     if (val == APIInfo.STA_DEPRECATED) {
498                         deprecatedFlag = true;
499                     }
500                 }
501             }
502             int get() {
503                 if (res == -1) {
504                     System.err.println("warning: no tag for " + doc);
505                     return 0;
506                 } else if (res == APIInfo.STA_INTERNAL && !deprecatedFlag) {
507                     System.err.println("warning: no @deprecated tag for @internal API: " + doc);
508                 }
509                 return res;
510             }
511         }
512 
513         Tag[] tags = doc.tags();
514         Result result = new Result();
515         String statusVer = "";
516         for (int i = 0; i < tags.length; ++i) {
517             Tag tag = tags[i];
518 
519             String kind = tag.kind();
520             int ix = tagKindIndex(kind);
521 
522             switch (ix) {
523             case INTERNAL:
524                 result.set(internal ? APIInfo.STA_INTERNAL : -2); // -2 for legacy compatibility
525                 statusVer = getStatusVersion(tag);
526                 break;
527 
528             case DRAFT:
529                 result.set(APIInfo.STA_DRAFT);
530                 statusVer = getStatusVersion(tag);
531                 break;
532 
533             case STABLE:
534                 result.set(APIInfo.STA_STABLE);
535                 statusVer = getStatusVersion(tag);
536                 break;
537 
538             case DEPRECATED:
539                 result.set(APIInfo.STA_DEPRECATED);
540                 statusVer = getStatusVersion(tag);
541                 break;
542 
543             case OBSOLETE:
544                 result.set(APIInfo.STA_OBSOLETE);
545                 statusVer = getStatusVersion(tag);
546                 break;
547 
548             case SINCE:
549             case EXCEPTION:
550             case VERSION:
551             case UNKNOWN:
552             case AUTHOR:
553             case SEE:
554             case PARAM:
555             case RETURN:
556             case THROWS:
557             case SERIAL:
558                 break;
559 
560             default:
561                 throw new RuntimeException("unknown index " + ix + " for tag: " + kind);
562             }
563         }
564 
565         if (version != null) {
566             version[0] = statusVer;
567         }
568         return result.get();
569     }
570 
getStatusVersion(Tag tag)571     private String getStatusVersion(Tag tag) {
572         String text = tag.text();
573         if (text != null && text.length() > 0) {
574             // Extract version string
575             int start = -1;
576             int i = 0;
577             for (; i < text.length(); i++) {
578                 char ch = text.charAt(i);
579                 if (ch == '.' || (ch >= '0' && ch <= '9')) {
580                     if (start == -1) {
581                         start = i;
582                     }
583                 } else if (start != -1) {
584                     break;
585                 }
586             }
587             if (start != -1) {
588                 return text.substring(start, i);
589             }
590         }
591         return "";
592     }
593 
594     private static final int UNKNOWN = -1;
595     private static final int INTERNAL = 0;
596     private static final int DRAFT = 1;
597     private static final int STABLE = 2;
598     private static final int SINCE = 3;
599     private static final int DEPRECATED = 4;
600     private static final int AUTHOR = 5;
601     private static final int SEE = 6;
602     private static final int VERSION = 7;
603     private static final int PARAM = 8;
604     private static final int RETURN = 9;
605     private static final int THROWS = 10;
606     private static final int OBSOLETE = 11;
607     private static final int EXCEPTION = 12;
608     private static final int SERIAL = 13;
609 
tagKindIndex(String kind)610     private static int tagKindIndex(String kind) {
611         final String[] tagKinds = {
612             "@internal", "@draft", "@stable", "@since", "@deprecated", "@author", "@see",
613             "@version", "@param", "@return", "@throws", "@obsolete", "@exception", "@serial"
614         };
615 
616         for (int i = 0; i < tagKinds.length; ++i) {
617             if (kind.equals(tagKinds[i])) {
618                 return i;
619             }
620         }
621         return UNKNOWN;
622     }
623 }
624