001: /**
002: *******************************************************************************
003: * Copyright (C) 2002-2006, International Business Machines Corporation and *
004: * others. All Rights Reserved. *
005: *******************************************************************************
006: */
007: /**
008: * This is a tool to check the tags on ICU4J files. In particular, we're looking for:
009: *
010: * - methods that have no tags
011: * - custom tags: @draft, @stable, @internal?
012: * - standard tags: @since, @deprecated
013: *
014: * Syntax of tags:
015: * '@draft ICU X.X.X'
016: * '@stable ICU X.X.X'
017: * '@internal'
018: * '@since (don't use)'
019: * '@obsolete ICU X.X.X'
020: * '@deprecated to be removed in ICU X.X. [Use ...]'
021: *
022: * flags names of classes and their members that have no tags or incorrect syntax.
023: *
024: * Requires JDK 1.4 or later
025: *
026: * Use build.xml 'checktags' ant target, or
027: * run from directory containing CheckTags.class as follows:
028: * javadoc -classpath ${JAVA_HOME}/lib/tools.jar -doclet CheckTags -sourcepath ${ICU4J_src} [packagenames]
029: */package com.ibm.icu.dev.tool.docs;
030:
031: import com.sun.javadoc.*;
032:
033: public class CheckTags {
034: RootDoc root;
035: boolean log;
036: boolean brief;
037: boolean isShort;
038: DocStack stack = new DocStack();
039:
040: class DocNode {
041: private String header;
042: private boolean printed;
043: private boolean reportError;
044: private int errorCount;
045:
046: public void reset(String header, boolean reportError) {
047: this .header = header;
048: this .printed = false;
049: this .errorCount = 0;
050: this .reportError = reportError;
051: }
052:
053: public String toString() {
054: return header + " printed: " + printed + " reportError: "
055: + reportError + " errorCount: " + errorCount;
056: }
057: }
058:
059: class DocStack {
060: private DocNode[] stack;
061: private int index;
062: private boolean newline;
063:
064: public void push(String header, boolean reportError) {
065: if (stack == null) {
066: stack = new DocNode[5];
067: } else {
068: if (index == stack.length) {
069: DocNode[] temp = new DocNode[stack.length * 2];
070: System.arraycopy(stack, 0, temp, 0, index);
071: stack = temp;
072: }
073: }
074: if (stack[index] == null) {
075: stack[index] = new DocNode();
076: }
077: // System.out.println("reset [" + index + "] header: " + header + " report: " + reportError);
078: stack[index++].reset(header, reportError);
079: }
080:
081: public void pop() {
082: if (index == 0) {
083: throw new IndexOutOfBoundsException();
084: }
085: --index;
086:
087: int ec = stack[index].errorCount; // index already decremented
088: if (ec > 0 || index == 0) { // always report for outermost element
089: if (stack[index].reportError) {
090: output("(" + ec + (ec == 1 ? " error" : " errors")
091: + ")", false, true, index);
092: }
093:
094: // propagate to parent
095: if (index > 0) {
096: stack[index - 1].errorCount += ec;
097: }
098: }
099: if (index == 0) {
100: System.out.println(); // always since we always report number of errors
101: }
102: }
103:
104: public void output(String msg, boolean error, boolean newline) {
105: output(msg, error, newline, index - 1);
106: }
107:
108: void output(String msg, boolean error, boolean newline, int ix) {
109: DocNode last = stack[ix];
110: if (error) {
111: last.errorCount += 1;
112: }
113:
114: boolean show = !brief || last.reportError;
115: // boolean nomsg = show && brief && error;
116: // System.out.println(">>> " + last + " error: " + error + " show: " + show + " nomsg: " + nomsg);
117:
118: if (show) {
119: if (isShort || (brief && error)) {
120: msg = null; // nuke error messages if we're brief, just report headers and totals
121: }
122: for (int i = 0; i <= ix;) {
123: DocNode n = stack[i];
124: if (n.printed) {
125: if (msg != null || !last.printed) { // since index > 0 last is not null
126: if (this .newline && i == 0) {
127: System.out.println();
128: this .newline = false;
129: }
130: System.out.print(" ");
131: }
132: ++i;
133: } else {
134: System.out.print(n.header);
135: n.printed = true;
136: this .newline = true;
137: i = 0;
138: }
139: }
140:
141: if (msg != null) {
142: if (index == 0 && this .newline) {
143: System.out.println();
144: }
145: if (error) {
146: System.out.print("*** ");
147: }
148: System.out.print(msg);
149: }
150: }
151:
152: this .newline = newline;
153: }
154: }
155:
156: public static boolean start(RootDoc root) {
157: return new CheckTags(root).run();
158: }
159:
160: public static int optionLength(String option) {
161: if (option.equals("-log")) {
162: return 1;
163: } else if (option.equals("-brief")) {
164: return 1;
165: } else if (option.equals("-short")) {
166: return 1;
167: }
168: return 0;
169: }
170:
171: CheckTags(RootDoc root) {
172: this .root = root;
173:
174: String[][] options = root.options();
175: for (int i = 0; i < options.length; ++i) {
176: String opt = options[i][0];
177: if (opt.equals("-log")) {
178: this .log = true;
179: } else if (opt.equals("-brief")) {
180: this .brief = true;
181: } else if (opt.equals("-short")) {
182: this .isShort = true;
183: }
184: }
185: }
186:
187: boolean run() {
188: doDocs(root.classes(), "Package", true);
189: return false;
190: }
191:
192: static final String[] tagKinds = { "@internal", "@draft",
193: "@stable", "@since", "@deprecated", "@author", "@see",
194: "@version", "@param", "@return", "@throws", "@obsolete",
195: "@exception", "@serial", "@provisional" };
196:
197: static final int UNKNOWN = -1;
198: static final int INTERNAL = 0;
199: static final int DRAFT = 1;
200: static final int STABLE = 2;
201: static final int SINCE = 3;
202: static final int DEPRECATED = 4;
203: static final int AUTHOR = 5;
204: static final int SEE = 6;
205: static final int VERSION = 7;
206: static final int PARAM = 8;
207: static final int RETURN = 9;
208: static final int THROWS = 10;
209: static final int OBSOLETE = 11;
210: static final int EXCEPTION = 12;
211: static final int SERIAL = 13;
212: static final int PROVISIONAL = 14;
213:
214: static int tagKindIndex(String kind) {
215: for (int i = 0; i < tagKinds.length; ++i) {
216: if (kind.equals(tagKinds[i])) {
217: return i;
218: }
219: }
220: return UNKNOWN;
221: }
222:
223: boolean newline = false;
224:
225: void output(String msg, boolean error, boolean newline) {
226: stack.output(msg, error, newline);
227: }
228:
229: void log() {
230: output(null, false, false);
231: }
232:
233: void logln() {
234: output(null, false, true);
235: }
236:
237: void log(String msg) {
238: output(msg, false, false);
239: }
240:
241: void logln(String msg) {
242: output(msg, false, true);
243: }
244:
245: void err(String msg) {
246: output(msg, true, false);
247: }
248:
249: void errln(String msg) {
250: output(msg, true, true);
251: }
252:
253: void tagErr(Tag tag) {
254: // Tag.position() requires JDK 1.4, build.xml tests for this
255: errln(tag.toString() + " [" + tag.position() + "]");
256: }
257:
258: void doDocs(ProgramElementDoc[] docs, String header,
259: boolean reportError) {
260: if (docs != null && docs.length > 0) {
261: stack.push(header, reportError);
262: for (int i = 0; i < docs.length; ++i) {
263: doDoc(docs[i]);
264: }
265: stack.pop();
266: }
267: }
268:
269: void doDoc(ProgramElementDoc doc) {
270: if (doc != null
271: && (doc.isPublic() || doc.isProtected())
272: && !(doc instanceof ConstructorDoc && ((ConstructorDoc) doc)
273: .isSynthetic())) {
274:
275: // unfortunately, in JDK 1.4.1 MemberDoc.isSynthetic is not properly implemented for
276: // synthetic constructors. So you'll have to live with spurious errors or 'implement'
277: // the synthetic constructors...
278:
279: boolean isClass = doc.isClass() || doc.isInterface();
280: String header;
281: if (!isShort || isClass) {
282: header = "--- ";
283: } else {
284: header = "";
285: }
286: header += (isClass ? doc.qualifiedName() : doc.name());
287: if (doc instanceof ExecutableMemberDoc) {
288: header += ((ExecutableMemberDoc) doc).flatSignature();
289: }
290: if (!isShort || isClass) {
291: header += " ---";
292: }
293: stack.push(header, isClass);
294: if (log) {
295: logln();
296: }
297: boolean recurse = doTags(doc);
298: if (recurse && isClass) {
299: ClassDoc cdoc = (ClassDoc) doc;
300: doDocs(cdoc.fields(), "Fields", !brief);
301: doDocs(cdoc.constructors(), "Constructors", !brief);
302: doDocs(cdoc.methods(), "Methods", !brief);
303: }
304: stack.pop();
305: }
306: }
307:
308: /** Return true if subelements of this doc should be checked */
309: boolean doTags(ProgramElementDoc doc) {
310: Tag[] tags = doc.tags();
311: boolean foundRequiredTag = false;
312: boolean foundDraftTag = false;
313: boolean foundProvisionalTag = false;
314: boolean foundDeprecatedTag = false;
315: boolean foundObsoleteTag = false;
316: boolean foundInternalTag = false;
317: boolean foundStableTag = false;
318: boolean retainAll = false;
319:
320: for (int i = 0; i < tags.length; ++i) {
321: Tag tag = tags[i];
322:
323: String kind = tag.kind();
324: int ix = tagKindIndex(kind);
325:
326: switch (ix) {
327: case UNKNOWN:
328: errln("unknown kind: " + kind);
329: break;
330:
331: case INTERNAL:
332: foundRequiredTag = true;
333: foundInternalTag = true;
334: break;
335:
336: case DRAFT:
337: foundRequiredTag = true;
338: foundDraftTag = true;
339: if (tag.text().indexOf("ICU 2.8") != -1
340: && tag.text().indexOf("(retain") == -1) { // catch both retain and retainAll
341: tagErr(tag);
342: break;
343: }
344: if (tag.text().indexOf("ICU") != 0) {
345: tagErr(tag);
346: break;
347: }
348: retainAll |= (tag.text().indexOf("(retainAll)") != -1);
349: break;
350:
351: case PROVISIONAL:
352: foundProvisionalTag = true;
353: break;
354:
355: case DEPRECATED:
356: foundDeprecatedTag = true;
357: if (tag.text().indexOf("ICU") == 0) {
358: foundRequiredTag = true;
359: }
360: break;
361:
362: case OBSOLETE:
363: if (tag.text().indexOf("ICU") != 0) {
364: tagErr(tag);
365: }
366: foundObsoleteTag = true;
367: foundRequiredTag = true;
368: break;
369:
370: case STABLE: {
371: String text = tag.text();
372: if (text.length() != 0 && text.indexOf("ICU") != 0) {
373: tagErr(tag);
374: }
375: foundRequiredTag = true;
376: foundStableTag = true;
377: }
378: break;
379:
380: case SINCE:
381: tagErr(tag);
382: break;
383:
384: case EXCEPTION:
385: logln("You really ought to use @throws, you know... :-)");
386:
387: case AUTHOR:
388: case SEE:
389: case PARAM:
390: case RETURN:
391: case THROWS:
392: case SERIAL:
393: break;
394:
395: case VERSION:
396: tagErr(tag);
397: break;
398:
399: default:
400: errln("unknown index: " + ix);
401: }
402: }
403: if (!foundRequiredTag) {
404: errln("missing required tag [" + doc.position() + "]");
405: }
406: if (foundInternalTag && !foundDeprecatedTag) {
407: errln("internal tag missing deprecated");
408: }
409: if (foundDraftTag
410: && !(foundDeprecatedTag || foundProvisionalTag)) {
411: errln("draft tag missing deprecated or provisional");
412: }
413: if (foundObsoleteTag && !foundDeprecatedTag) {
414: errln("obsolete tag missing deprecated");
415: }
416: if (foundStableTag && foundDeprecatedTag) {
417: logln("stable deprecated");
418: }
419:
420: return !retainAll;
421: }
422: }
|