001: /**
002: *******************************************************************************
003: * Copyright (C) 2004-2006, International Business Machines Corporation and *
004: * others. All Rights Reserved. *
005: *******************************************************************************
006: */
007:
008: /**
009: * Generate a list of ICU's public APIs, sorted by qualified name and signature
010: * public APIs are all non-internal, non-package apis in com.ibm.icu.[lang|math|text|util].
011: * For each API, list
012: * - public, package, protected, or private (PB PK PT PR)
013: * - static or non-static (STK NST)
014: * - final or non-final (FN NF)
015: * - synchronized or non-synchronized (SYN NSY)
016: * - stable, draft, deprecated, obsolete (ST DR DP OB)
017: * - abstract or non-abstract (AB NA)
018: * - constructor, member, field (C M F)
019: *
020: * Requires JDK 1.4.2 or later
021: *
022: * Sample compilation:
023: * c:/doug/java/jdk1.4.2/build/windows-i586/bin/javac *.java
024: *
025: * Sample execution
026: * c:/j2sdk1.4.2/bin/javadoc
027: * -classpath c:/jd2sk1.4.2/lib/tools.jar
028: * -doclet com.ibm.icu.dev.tool.docs.GatherAPIData
029: * -docletpath c:/doug/cvsproj/icu4j/src
030: * -sourcepath c:/doug/cvsproj/icu4j/src
031: * -name "ICU4J 3.0"
032: * -output icu4j30.api
033: * -gzip
034: * -source 1.4
035: * com.ibm.icu.lang com.ibm.icu.math com.ibm.icu.text com.ibm.icu.util
036: *
037: * todo: provide command-line control of filters of which subclasses/packages to process
038: * todo: record full inheritance heirarchy, not just immediate inheritance
039: * todo: allow for aliasing comparisons (force (pkg.)*class to be treated as though it
040: * were in a different pkg/class heirarchy (facilitates comparison of icu4j and java)
041: */package com.ibm.icu.dev.tool.docs;
042:
043: // standard release sdk won't work, need internal build to get access to javadoc
044: import com.sun.javadoc.*;
045: import java.io.*;
046: import java.util.*;
047: import java.util.regex.*;
048: import java.util.zip.GZIPOutputStream;
049: import java.util.zip.ZipEntry;
050: import java.util.zip.ZipOutputStream;
051:
052: public class GatherAPIData {
053: RootDoc root;
054: TreeSet results;
055: String srcName = "Current"; // default source name
056: String output; // name of output file to write
057: String base; // strip this prefix
058: Pattern pat;
059: boolean zip;
060: boolean gzip;
061: boolean internal;
062:
063: public static int optionLength(String option) {
064: if (option.equals("-name")) {
065: return 2;
066: } else if (option.equals("-output")) {
067: return 2;
068: } else if (option.equals("-base")) {
069: return 2;
070: } else if (option.equals("-filter")) {
071: return 2;
072: } else if (option.equals("-zip")) {
073: return 1;
074: } else if (option.equals("-gzip")) {
075: return 1;
076: } else if (option.equals("-internal")) {
077: return 1;
078: }
079: return 0;
080: }
081:
082: public static boolean start(RootDoc root) {
083: return new GatherAPIData(root).run();
084: }
085:
086: GatherAPIData(RootDoc root) {
087: this .root = root;
088:
089: String[][] options = root.options();
090: for (int i = 0; i < options.length; ++i) {
091: String opt = options[i][0];
092: if (opt.equals("-name")) {
093: this .srcName = options[i][1];
094: } else if (opt.equals("-output")) {
095: this .output = options[i][1];
096: } else if (opt.equals("-base")) {
097: this .base = options[i][1]; // should not include '.'
098: } else if (opt.equals("-filter")) {
099: this .pat = Pattern.compile(options[i][1],
100: Pattern.CASE_INSENSITIVE);
101: } else if (opt.equals("-zip")) {
102: this .zip = true;
103: } else if (opt.equals("-gzip")) {
104: this .gzip = true;
105: } else if (opt.equals("-internal")) {
106: this .internal = true;
107: }
108: }
109:
110: results = new TreeSet(APIInfo.defaultComparator());
111: }
112:
113: private boolean run() {
114: doDocs(root.classes());
115:
116: OutputStream os = System.out;
117: if (output != null) {
118: try {
119: if (zip) {
120: ZipOutputStream zos = new ZipOutputStream(
121: new FileOutputStream(output + ".zip"));
122: zos.putNextEntry(new ZipEntry(output));
123: os = zos;
124: } else if (gzip) {
125: os = new GZIPOutputStream(new FileOutputStream(
126: output + ".gz"));
127: } else {
128: os = new FileOutputStream(output);
129: }
130: } catch (IOException e) {
131: RuntimeException re = new RuntimeException(e
132: .getMessage());
133: re.initCause(e);
134: throw re;
135: }
136: }
137:
138: BufferedWriter bw = null;
139: try {
140: OutputStreamWriter osw = new OutputStreamWriter(os, "UTF-8");
141: bw = new BufferedWriter(osw);
142:
143: // writing data file
144: bw.write(String.valueOf(APIInfo.VERSION) + APIInfo.SEP); // header version
145: bw.write(srcName + APIInfo.SEP); // source name
146: bw.write((base == null ? "" : base) + APIInfo.SEP); // base
147: bw.newLine();
148: writeResults(results, bw);
149: bw.close(); // should flush, close all, etc
150: } catch (IOException e) {
151: try {
152: bw.close();
153: } catch (IOException e2) {
154: }
155: RuntimeException re = new RuntimeException("write error: "
156: + e.getMessage());
157: re.initCause(e);
158: throw re;
159: }
160:
161: return false;
162: }
163:
164: private void doDocs(ProgramElementDoc[] docs) {
165: if (docs != null && docs.length > 0) {
166: for (int i = 0; i < docs.length; ++i) {
167: doDoc(docs[i]);
168: }
169: }
170: }
171:
172: private void doDoc(ProgramElementDoc doc) {
173: if (ignore(doc))
174: return;
175:
176: if (doc.isClass() || doc.isInterface()) {
177: ClassDoc cdoc = (ClassDoc) doc;
178: doDocs(cdoc.fields());
179: doDocs(cdoc.constructors());
180: doDocs(cdoc.methods());
181: doDocs(cdoc.innerClasses());
182: }
183:
184: APIInfo info = createInfo(doc);
185: if (info != null) {
186: results.add(info);
187: }
188: }
189:
190: private boolean ignore(ProgramElementDoc doc) {
191: if (doc == null)
192: return true;
193: if (doc.isPrivate() || doc.isPackagePrivate())
194: return true;
195: if (doc instanceof ConstructorDoc
196: && ((ConstructorDoc) doc).isSynthetic())
197: return true;
198: if (doc.qualifiedName().indexOf(".misc") != -1) {
199: System.out.println("misc: " + doc.qualifiedName());
200: return true;
201: }
202: if (!internal) { // debug
203: Tag[] tags = doc.tags();
204: for (int i = 0; i < tags.length; ++i) {
205: if (tagKindIndex(tags[i].kind()) == INTERNAL) {
206: return true;
207: }
208: }
209: }
210: if (pat != null && (doc.isClass() || doc.isInterface())) {
211: if (!pat.matcher(doc.name()).matches()) {
212: return true;
213: }
214: }
215: return false;
216: }
217:
218: private static void writeResults(Collection c, BufferedWriter w) {
219: Iterator iter = c.iterator();
220: while (iter.hasNext()) {
221: APIInfo info = (APIInfo) iter.next();
222: info.writeln(w);
223: }
224: }
225:
226: private String trimBase(String arg) {
227: if (base != null) {
228: for (int n = arg.indexOf(base); n != -1; n = arg.indexOf(
229: base, n)) {
230: arg = arg.substring(0, n)
231: + arg.substring(n + base.length());
232: }
233: }
234: return arg;
235: }
236:
237: public APIInfo createInfo(ProgramElementDoc doc) {
238:
239: // Doc. name
240: // Doc. isField, isMethod, isConstructor, isClass, isInterface
241: // ProgramElementDoc. containingClass, containingPackage
242: // ProgramElementDoc. isPublic, isProtected, isPrivate, isPackagePrivate
243: // ProgramElementDoc. isStatic, isFinal
244: // MemberDoc.isSynthetic
245: // ExecutableMemberDoc isSynchronized, signature
246: // Type.toString() // e.g. "String[][]"
247: // ClassDoc.isAbstract, superClass, interfaces, fields, methods, constructors, innerClasses
248: // FieldDoc type
249: // ConstructorDoc qualifiedName
250: // MethodDoc isAbstract, returnType
251:
252: APIInfo info = new APIInfo();
253:
254: // status
255: info.setType(APIInfo.STA, tagStatus(doc));
256:
257: // visibility
258: if (doc.isPublic()) {
259: info.setPublic();
260: } else if (doc.isProtected()) {
261: info.setProtected();
262: } else if (doc.isPrivate()) {
263: info.setPrivate();
264: } else {
265: // default is package
266: }
267:
268: // static
269: if (doc.isStatic()) {
270: info.setStatic();
271: } else {
272: // default is non-static
273: }
274:
275: // final
276: if (doc.isFinal()) {
277: info.setFinal();
278: } else {
279: // default is non-final
280: }
281:
282: // type
283: if (doc.isField()) {
284: info.setField();
285: } else if (doc.isMethod()) {
286: info.setMethod();
287: } else if (doc.isConstructor()) {
288: info.setConstructor();
289: } else if (doc.isClass() || doc.isInterface()) {
290: info.setClass();
291: }
292:
293: info.setPackage(trimBase(doc.containingPackage().name()));
294: info.setClassName((doc.isClass() || doc.isInterface() || (doc
295: .containingClass() == null)) ? "" : trimBase(doc
296: .containingClass().name()));
297: info.setName(trimBase(doc.name()));
298:
299: if (doc instanceof FieldDoc) {
300: FieldDoc fdoc = (FieldDoc) doc;
301: info.setSignature(trimBase(fdoc.type().toString()));
302: } else if (doc instanceof ClassDoc) {
303: ClassDoc cdoc = (ClassDoc) doc;
304:
305: if (cdoc.isClass() && cdoc.isAbstract()) {
306: // interfaces are abstract by default, don't mark them as abstract
307: info.setAbstract();
308: }
309:
310: StringBuffer buf = new StringBuffer();
311: if (cdoc.isClass()) {
312: buf.append("extends ");
313: buf.append(cdoc.super class().qualifiedName());
314: }
315: ClassDoc[] imp = cdoc.interfaces();
316: if (imp != null && imp.length > 0) {
317: if (buf.length() > 0) {
318: buf.append(" ");
319: }
320: buf.append("implements");
321: for (int i = 0; i < imp.length; ++i) {
322: if (i != 0) {
323: buf.append(",");
324: }
325: buf.append(" ");
326: buf.append(imp[i].qualifiedName());
327: }
328: }
329: info.setSignature(trimBase(buf.toString()));
330: } else {
331: ExecutableMemberDoc emdoc = (ExecutableMemberDoc) doc;
332: if (emdoc.isSynchronized()) {
333: info.setSynchronized();
334: }
335:
336: if (doc instanceof MethodDoc) {
337: MethodDoc mdoc = (MethodDoc) doc;
338: if (mdoc.isAbstract()) {
339: info.setAbstract();
340: }
341: info.setSignature(trimBase(mdoc.returnType().toString()
342: + emdoc.signature()));
343: } else {
344: // constructor
345: info.setSignature(trimBase(emdoc.signature()));
346: }
347: }
348:
349: return info;
350: }
351:
352: private int tagStatus(final Doc doc) {
353: class Result {
354: int res = -1;
355:
356: void set(int val) {
357: if (res != -1) {
358: if (val == APIInfo.STA_DEPRECATED) {
359: // ok to have both a 'standard' tag and deprecated
360: return;
361: } else if (res != APIInfo.STA_DEPRECATED) {
362: // if already not deprecated, this is an error
363: System.err.println("bad doc: "
364: + doc
365: + " both: "
366: + APIInfo.getTypeValName(APIInfo.STA,
367: res)
368: + " and: "
369: + APIInfo.getTypeValName(APIInfo.STA,
370: val));
371: return;
372: }
373: }
374: // ok to replace with new tag
375: res = val;
376: }
377:
378: int get() {
379: if (res == -1) {
380: System.err.println("warning: no tag for " + doc);
381: return 0;
382: }
383: return res;
384: }
385: }
386:
387: Tag[] tags = doc.tags();
388: Result result = new Result();
389: for (int i = 0; i < tags.length; ++i) {
390: Tag tag = tags[i];
391:
392: String kind = tag.kind();
393: int ix = tagKindIndex(kind);
394:
395: switch (ix) {
396: case INTERNAL:
397: result.set(internal ? APIInfo.STA_INTERNAL : -2); // -2 for legacy compatibility
398: break;
399:
400: case DRAFT:
401: result.set(APIInfo.STA_DRAFT);
402: break;
403:
404: case STABLE:
405: result.set(APIInfo.STA_STABLE);
406: break;
407:
408: case DEPRECATED:
409: result.set(APIInfo.STA_DEPRECATED);
410: break;
411:
412: case OBSOLETE:
413: result.set(APIInfo.STA_OBSOLETE);
414: break;
415:
416: case SINCE:
417: case EXCEPTION:
418: case VERSION:
419: case UNKNOWN:
420: case AUTHOR:
421: case SEE:
422: case PARAM:
423: case RETURN:
424: case THROWS:
425: case SERIAL:
426: break;
427:
428: default:
429: throw new RuntimeException("unknown index " + ix
430: + " for tag: " + kind);
431: }
432: }
433:
434: return result.get();
435: }
436:
437: private static final int UNKNOWN = -1;
438: private static final int INTERNAL = 0;
439: private static final int DRAFT = 1;
440: private static final int STABLE = 2;
441: private static final int SINCE = 3;
442: private static final int DEPRECATED = 4;
443: private static final int AUTHOR = 5;
444: private static final int SEE = 6;
445: private static final int VERSION = 7;
446: private static final int PARAM = 8;
447: private static final int RETURN = 9;
448: private static final int THROWS = 10;
449: private static final int OBSOLETE = 11;
450: private static final int EXCEPTION = 12;
451: private static final int SERIAL = 13;
452:
453: private static int tagKindIndex(String kind) {
454: final String[] tagKinds = { "@internal", "@draft", "@stable",
455: "@since", "@deprecated", "@author", "@see", "@version",
456: "@param", "@return", "@throws", "@obsolete",
457: "@exception", "@serial" };
458:
459: for (int i = 0; i < tagKinds.length; ++i) {
460: if (kind.equals(tagKinds[i])) {
461: return i;
462: }
463: }
464: return UNKNOWN;
465: }
466: }
|