| /** |
| ******************************************************************************* |
| * Copyright (C) 2002-2010, International Business Machines Corporation and * |
| * others. All Rights Reserved. * |
| ******************************************************************************* |
| */ |
| /** |
| * This is a tool to check the tags on ICU4J files. In particular, we're looking for: |
| * |
| * - methods that have no tags |
| * - custom tags: @draft, @stable, @internal? |
| * - standard tags: @since, @deprecated |
| * |
| * Syntax of tags: |
| * '@draft ICU X.X.X' |
| * '@stable ICU X.X.X' |
| * '@internal' |
| * '@since (don't use)' |
| * '@obsolete ICU X.X.X' |
| * '@deprecated to be removed in ICU X.X. [Use ...]' |
| * |
| * flags names of classes and their members that have no tags or incorrect syntax. |
| * |
| * Requires JDK 1.4 or later |
| * |
| * Use build.xml 'checktags' ant target, or |
| * run from directory containing CheckTags.class as follows: |
| * javadoc -classpath ${JAVA_HOME}/lib/tools.jar -doclet CheckTags -sourcepath ${ICU4J_src} [packagenames] |
| */ |
| |
| package com.ibm.icu.dev.tool.docs; |
| |
| import com.sun.javadoc.ClassDoc; |
| import com.sun.javadoc.ConstructorDoc; |
| import com.sun.javadoc.ExecutableMemberDoc; |
| import com.sun.javadoc.ProgramElementDoc; |
| import com.sun.javadoc.RootDoc; |
| import com.sun.javadoc.Tag; |
| |
| public class CheckTags { |
| RootDoc root; |
| boolean log; |
| boolean brief; |
| boolean isShort; |
| DocStack stack = new DocStack(); |
| |
| class DocNode { |
| private String header; |
| private boolean printed; |
| private boolean reportError; |
| private int errorCount; |
| |
| public void reset(String header, boolean reportError) { |
| this.header = header; |
| this.printed = false; |
| this.errorCount = 0; |
| this.reportError = reportError; |
| } |
| public String toString() { |
| return header + |
| " printed: " + printed + |
| " reportError: " + reportError + |
| " errorCount: " + errorCount; |
| } |
| } |
| |
| class DocStack { |
| private DocNode[] stack; |
| private int index; |
| private boolean newline; |
| |
| public void push(String header, boolean reportError) { |
| if (stack == null) { |
| stack = new DocNode[5]; |
| } else { |
| if (index == stack.length) { |
| DocNode[] temp = new DocNode[stack.length * 2]; |
| System.arraycopy(stack, 0, temp, 0, index); |
| stack = temp; |
| } |
| } |
| if (stack[index] == null) { |
| stack[index] = new DocNode(); |
| } |
| // System.out.println("reset [" + index + "] header: " + header + " report: " + reportError); |
| stack[index++].reset(header, reportError); |
| } |
| |
| public void pop() { |
| if (index == 0) { |
| throw new IndexOutOfBoundsException(); |
| } |
| --index; |
| |
| int ec = stack[index].errorCount; // index already decremented |
| if (ec > 0 || index == 0) { // always report for outermost element |
| if (stack[index].reportError) { |
| output("(" + ec + (ec == 1 ? " error" : " errors") + ")", false, true, index); |
| } |
| |
| // propagate to parent |
| if (index > 0) { |
| stack[index-1].errorCount += ec; |
| } |
| } |
| if (index == 0) { |
| System.out.println(); // always since we always report number of errors |
| } |
| } |
| |
| public void output(String msg, boolean error, boolean newline) { |
| output(msg, error, newline, index-1); |
| } |
| |
| void output(String msg, boolean error, boolean newline, int ix) { |
| DocNode last = stack[ix]; |
| if (error) { |
| last.errorCount += 1; |
| } |
| |
| boolean show = !brief || last.reportError; |
| // boolean nomsg = show && brief && error; |
| // System.out.println(">>> " + last + " error: " + error + " show: " + show + " nomsg: " + nomsg); |
| |
| if (show) { |
| if (isShort || (brief && error)) { |
| msg = null; // nuke error messages if we're brief, just report headers and totals |
| } |
| for (int i = 0; i <= ix;) { |
| DocNode n = stack[i]; |
| if (n.printed) { |
| if (msg != null || !last.printed) { // since index > 0 last is not null |
| if (this.newline && i == 0) { |
| System.out.println(); |
| this.newline = false; |
| } |
| System.out.print(" "); |
| } |
| ++i; |
| } else { |
| System.out.print(n.header); |
| n.printed = true; |
| this.newline = true; |
| i = 0; |
| } |
| } |
| |
| if (msg != null) { |
| if (index == 0 && this.newline) { |
| System.out.println(); |
| } |
| if (error) { |
| System.out.print("*** "); |
| } |
| System.out.print(msg); |
| } |
| } |
| |
| this.newline = newline; |
| } |
| } |
| |
| public static boolean start(RootDoc root) { |
| return new CheckTags(root).run(); |
| } |
| |
| public static int optionLength(String option) { |
| if (option.equals("-log")) { |
| return 1; |
| } else if (option.equals("-brief")) { |
| return 1; |
| } else if (option.equals("-short")) { |
| return 1; |
| } |
| return 0; |
| } |
| |
| CheckTags(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("-log")) { |
| this.log = true; |
| } else if (opt.equals("-brief")) { |
| this.brief = true; |
| } else if (opt.equals("-short")) { |
| this.isShort = true; |
| } |
| } |
| } |
| |
| boolean run() { |
| doDocs(root.classes(), "Package", true); |
| return false; |
| } |
| |
| static final String[] tagKinds = { |
| "@internal", "@draft", "@stable", "@since", "@deprecated", "@author", "@see", "@version", |
| "@param", "@return", "@throws", "@obsolete", "@exception", "@serial", "@provisional" |
| }; |
| |
| static final int UNKNOWN = -1; |
| static final int INTERNAL = 0; |
| static final int DRAFT = 1; |
| static final int STABLE = 2; |
| static final int SINCE = 3; |
| static final int DEPRECATED = 4; |
| static final int AUTHOR = 5; |
| static final int SEE = 6; |
| static final int VERSION = 7; |
| static final int PARAM = 8; |
| static final int RETURN = 9; |
| static final int THROWS = 10; |
| static final int OBSOLETE = 11; |
| static final int EXCEPTION = 12; |
| static final int SERIAL = 13; |
| static final int PROVISIONAL = 14; |
| |
| static int tagKindIndex(String kind) { |
| for (int i = 0; i < tagKinds.length; ++i) { |
| if (kind.equals(tagKinds[i])) { |
| return i; |
| } |
| } |
| return UNKNOWN; |
| } |
| |
| static final String[] icuTagNames = { |
| "@icu", "@icunote", "@icuenhanced" |
| }; |
| static final int ICU = 0; |
| static final int ICUNOTE = 1; |
| static final int ICUENHANCED = 2; |
| static int icuTagIndex(String name) { |
| for (int i = 0; i < icuTagNames.length; ++i) { |
| if (icuTagNames[i].equals(name)) { |
| return i; |
| } |
| } |
| return UNKNOWN; |
| } |
| |
| boolean newline = false; |
| |
| void output(String msg, boolean error, boolean newline) { |
| stack.output(msg, error, newline); |
| } |
| |
| void log() { |
| output(null, false, false); |
| } |
| |
| void logln() { |
| output(null, false, true); |
| } |
| |
| void log(String msg) { |
| output(msg, false, false); |
| } |
| |
| void logln(String msg) { |
| output(msg, false, true); |
| } |
| |
| void err(String msg) { |
| output(msg, true, false); |
| } |
| |
| void errln(String msg) { |
| output(msg, true, true); |
| } |
| |
| void tagErr(String msg, Tag tag) { |
| // Tag.position() requires JDK 1.4, build.xml tests for this |
| if (msg.length() > 0) { |
| msg += ": "; |
| } |
| errln(msg + tag.toString() + " [" + tag.position() + "]"); |
| }; |
| |
| void tagErr(Tag tag) { |
| tagErr("", tag); |
| } |
| |
| void doDocs(ProgramElementDoc[] docs, String header, boolean reportError) { |
| if (docs != null && docs.length > 0) { |
| stack.push(header, reportError); |
| for (int i = 0; i < docs.length; ++i) { |
| doDoc(docs[i]); |
| } |
| stack.pop(); |
| } |
| } |
| |
| void doDoc(ProgramElementDoc doc) { |
| if (doc != null && (doc.isPublic() || doc.isProtected()) |
| && !(doc instanceof ConstructorDoc && ((ConstructorDoc)doc).isSynthetic())) { |
| |
| // unfortunately, in JDK 1.4.1 MemberDoc.isSynthetic is not properly implemented for |
| // synthetic constructors. So you'll have to live with spurious errors or 'implement' |
| // the synthetic constructors... |
| |
| boolean isClass = doc.isClass() || doc.isInterface(); |
| String header; |
| if (!isShort || isClass) { |
| header = "--- "; |
| } else { |
| header = ""; |
| } |
| header += (isClass ? doc.qualifiedName() : doc.name()); |
| if (doc instanceof ExecutableMemberDoc) { |
| header += ((ExecutableMemberDoc)doc).flatSignature(); |
| } |
| if (!isShort || isClass) { |
| header += " ---"; |
| } |
| stack.push(header, isClass); |
| if (log) { |
| logln(); |
| } |
| boolean recurse = doTags(doc); |
| if (recurse && isClass) { |
| ClassDoc cdoc = (ClassDoc)doc; |
| doDocs(cdoc.fields(), "Fields", !brief); |
| doDocs(cdoc.constructors(), "Constructors", !brief); |
| doDocs(cdoc.methods(), "Methods", !brief); |
| } |
| stack.pop(); |
| } |
| } |
| |
| /** Return true if subelements of this doc should be checked */ |
| boolean doTags(ProgramElementDoc doc) { |
| boolean foundRequiredTag = false; |
| boolean foundDraftTag = false; |
| boolean foundProvisionalTag = false; |
| boolean foundDeprecatedTag = false; |
| boolean foundObsoleteTag = false; |
| boolean foundInternalTag = false; |
| boolean foundStableTag = false; |
| boolean retainAll = false; |
| |
| // first check inline tags |
| for (Tag tag : doc.inlineTags()) { |
| int index = icuTagIndex(tag.name()); |
| if (index >= 0) { |
| String text = tag.text().trim(); |
| switch (index) { |
| case ICU: { |
| if (doc.isClass() || doc.isInterface()) { |
| tagErr("tag should appear only in member docs", tag); |
| } |
| } break; |
| case ICUNOTE: { |
| if (text.length() > 0) { |
| tagErr("tag should not contain text", tag); |
| } |
| } break; |
| case ICUENHANCED: { |
| if (text.length() == 0) { |
| tagErr("text should name related jdk class", tag); |
| } |
| if (!(doc.isClass() || doc.isInterface())) { |
| tagErr("tag should appear only in class/interface docs", tag); |
| } |
| } break; |
| default: |
| tagErr("unrecognized tag index for tag", tag); |
| break; |
| } |
| } |
| } |
| |
| // next check regular tags |
| for (Tag tag : doc.tags()) { |
| String kind = tag.kind(); |
| int ix = tagKindIndex(kind); |
| |
| switch (ix) { |
| case UNKNOWN: |
| errln("unknown kind: " + kind); |
| break; |
| |
| case INTERNAL: |
| foundRequiredTag = true; |
| foundInternalTag = true; |
| break; |
| |
| case DRAFT: |
| foundRequiredTag = true; |
| foundDraftTag = true; |
| if (tag.text().indexOf("ICU 2.8") != -1 && |
| tag.text().indexOf("(retain") == -1) { // catch both retain and retainAll |
| tagErr(tag); |
| break; |
| } |
| if (tag.text().indexOf("ICU") != 0) { |
| tagErr(tag); |
| break; |
| } |
| retainAll |= (tag.text().indexOf("(retainAll)") != -1); |
| break; |
| |
| case PROVISIONAL: |
| foundProvisionalTag = true; |
| break; |
| |
| case DEPRECATED: |
| foundDeprecatedTag = true; |
| if (tag.text().indexOf("ICU") == 0) { |
| foundRequiredTag = true; |
| } |
| break; |
| |
| case OBSOLETE: |
| if (tag.text().indexOf("ICU") != 0) { |
| tagErr(tag); |
| } |
| foundObsoleteTag = true; |
| foundRequiredTag = true; |
| break; |
| |
| case STABLE: |
| { |
| String text = tag.text(); |
| if (text.length() != 0 && text.indexOf("ICU") != 0) { |
| tagErr(tag); |
| } |
| foundRequiredTag = true; |
| foundStableTag = true; |
| } |
| break; |
| |
| case SINCE: |
| tagErr(tag); |
| break; |
| |
| case EXCEPTION: |
| logln("You really ought to use @throws, you know... :-)"); |
| |
| case AUTHOR: |
| case SEE: |
| case PARAM: |
| case RETURN: |
| case THROWS: |
| case SERIAL: |
| break; |
| |
| case VERSION: |
| tagErr(tag); |
| break; |
| |
| default: |
| errln("unknown index: " + ix); |
| } |
| } |
| if (!foundRequiredTag) { |
| errln("missing required tag [" + doc.position() + "]"); |
| } |
| if (foundInternalTag && !foundDeprecatedTag) { |
| errln("internal tag missing deprecated"); |
| } |
| if (foundDraftTag && !(foundDeprecatedTag || foundProvisionalTag)) { |
| errln("draft tag missing deprecated or provisional"); |
| } |
| if (foundObsoleteTag && !foundDeprecatedTag) { |
| errln("obsolete tag missing deprecated"); |
| } |
| if (foundStableTag && foundDeprecatedTag) { |
| logln("stable deprecated"); |
| } |
| |
| return !retainAll; |
| } |
| } |