/* * Copyright (C) 2008 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import java.io.BufferedOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import com.sun.javadoc.ClassDoc; import com.sun.javadoc.ConstructorDoc; import com.sun.javadoc.Doc; import com.sun.javadoc.ExecutableMemberDoc; import com.sun.javadoc.FieldDoc; import com.sun.javadoc.LanguageVersion; import com.sun.javadoc.MemberDoc; import com.sun.javadoc.MethodDoc; import com.sun.javadoc.PackageDoc; import com.sun.javadoc.ParamTag; import com.sun.javadoc.Parameter; import com.sun.javadoc.RootDoc; import com.sun.javadoc.Tag; import com.sun.javadoc.ThrowsTag; import com.sun.javadoc.TypeVariable; /** * Provides a Doclet for checking the correctness and completeness of the * Android core library JavaDoc (aka "the spec"). It generates an HTML-based * report vaguely similar to the standard JavaDoc output. The following rules * are currently implemented: * * Each package must have a package.html doc, and all classes must be documented * as described below. * * Each class must have an individual doc and all members (fields, constructors, * methods) must be documented as described below. All type parameters on class * level need to be documented. * * Each member must have an individual doc. * * Each executable member (constructor or method) must have a "@param" tag * describing each declared parameter. "@param" tags for non-existing parameters * are not allowed. * * Each method that has a non-void return type must have at least one "@return" * tag. A method that has a void return type must not have a "@return" tag. * * Each executable member must have a "@throws" tag for each declared exception * that does not extend java.lang.RuntimeException or java.lang.Error. This * tag may refer to a superclass of the exception actually being thrown. Each * exception specified by a "@throws" tag must actually be declared by the * member, unless it extends java.lang.RuntimeException or java.lang.Error. * Again, the exception being thrown might be more specific than the one * documented. * * Methods that override or implement another method are allowed to be * undocumented, resulting in the inherited documentation being used. If such a * method is documented anyway, it must have the complete documentation as * described above. * * Elements that have a "@hide" JavaDoc tag are not considered part of the * official API and hence are not required to be documented. * * Based on checking the above rules, the Doclet assigns statuses to individual * documentation elements as follows: * * Red: the element violates at least one of the above rules. * * Yellow: the element fulfills all the above rules, but neither it nor one of * its parent elements (class, package) has been marked with the * "@since Android-1.0" tag. * * Green: the element fulfills all the above rules, it does not have any "@cts" * tags, and either it or one if its parent elements (class, package) has been * marked with the "@since Android-1.0" tag. * * These colors propagate upwards in the hierarchy. Parent elements are assigned * colors as follows: * * Red: At least on the children is red. * * Yellow: None of the children are red and at least one of the children is * yellow. * * Green: All of the children are green. * * The ultimate goal, of course, is to get the summary for the complete API * green. */ public class SpecProgressDoclet { public static final int TYPE_FIELD = 0; public static final int TYPE_METHOD = 1; public static final int TYPE_CLASS = 2; public static final int TYPE_PACKAGE = 3; public static final int TYPE_ROOT = 4; public static final int VALUE_RED = 0; public static final int VALUE_YELLOW = 1; public static final int VALUE_GREEN = 2; public static final String[] COLORS = { "#ffa0a0", "#ffffa0", "#a0ffa0" }; public static final String[] TYPES = { "Field", "Method", "Class", "Package", "All packages" }; /** * Holds our basic output directory. */ private File directory; /** * Holds a reference to the doc for java.lang.RuntimeException, so we can * compare against it later. */ private ClassDoc runtimeException; /** * Holds a reference to the doc for java.lang.Error, so we can * compare against it later. */ private ClassDoc error; /** * States whether to check type parameters on class level. * To enable these checks use the option: '-Xgeneric' */ private static boolean checkTypeParameters; /** * Helper class for comparing element with each other, in oder to determine * an order. Uses lexicographic order of names. */ private class DocComparator implements Comparator<Doc> { public int compare(Doc elem1, Doc elem2) { return elem1.name().compareTo(elem2.name()); } public boolean equals(Doc elem) { return this == elem; } } /** * Class for collecting stats and propagating them upwards in the element * hierarchy. */ class Stats { /** * Holds the element type. */ int type; /** * Holds the name of the element. */ String name; /** * Holds information that is sufficient for building a hyperlink. */ String link; /** * Holds the total number of elements per type (package, class, etc.). */ private int[] numbersPerType = new int[4]; /** * Holds the total number of elements per status value (red, yellow, * green). */ private int[] numbersPerValue = new int[3]; /** * Holds the total number of "@cts" comments. */ private int numberOfComments; /** * Creates a new Stats instance. */ public Stats(int type, String name, String link) { this.type = type; this.name = name; this.link = link; } /** * Adds the contents of a single child element to this instance, * propagating values up in the hierachy */ public void add(int type, int status, int comments) { numbersPerType[type]++; numbersPerValue[status]++; numberOfComments += comments; } /** * Adds the contents of a child Stats instance to this instance, * propagating values up in the hierachy */ public void add(Stats stats) { for (int i = 0; i < numbersPerType.length; i++) { numbersPerType[i] += stats.numbersPerType[i]; } for (int i = 0; i < numbersPerValue.length; i++) { numbersPerValue[i] += stats.numbersPerValue[i]; } numberOfComments += stats.numberOfComments; } /** * Returns the link. */ public String getLink() { return link; } /** * Returns the name. */ public String getName() { return name; } /** * Returns the number of elements per element type. */ public int getNumbersPerType(int type) { return numbersPerType[type]; } /** * Returns the number of elements per status value. */ public int getNumbersPerValue(int type) { return numbersPerValue[type]; } /** * Returns the number of comments. */ public int getNumberOfComments() { return numberOfComments; } /** * Returns the type of the element to which this Stats instance belongs. */ public int getType() { return type; } /** * Returns the accumulated status value. */ public int getValue() { if (numbersPerValue[VALUE_RED] != 0) { return VALUE_RED; } else if (numbersPerValue[VALUE_YELLOW] != 0) { return VALUE_YELLOW; } else { return VALUE_GREEN; } } } /** * Holds our comparator instance for everything. */ private DocComparator comparator = new DocComparator(); /** * Creates a new instance of the SpecProgressDoclet for a given target * directory. */ public SpecProgressDoclet(String directory) { this.directory = new File(directory); } /** * Opens a new output file and writes the usual HTML header. Directories * are created on demand. */ private PrintWriter openFile(String name, String title) throws IOException { System.out.println("Writing file \"" + name + "\"..."); File file = new File(directory, name); File parent = file.getParentFile(); parent.mkdirs(); OutputStream stream = new BufferedOutputStream(new FileOutputStream(file)); PrintWriter printer = new PrintWriter(stream); printer.println("<html>"); printer.println(" <head>"); printer.println(" <title>" + title + "</title>"); printer.println(" <head>"); printer.println(" <body>"); printer.println(" <h1>" + title + "</h1>"); return printer; } /** * Closes the given output file, writing the usual HTML footer before. */ private void closeFile(PrintWriter printer) { printer.println(" </body>"); printer.println("</html>"); printer.flush(); printer.close(); } /** * Processes the whole list of classes that JavaDoc knows about. */ private void process(RootDoc root) throws IOException { runtimeException = root.classNamed("java.lang.RuntimeException"); error = root.classNamed("java.lang.Error"); PrintWriter printer = openFile("index.html", "All packages"); printer.println("Generated " + new Date().toString()); Stats derived = new Stats(TYPE_ROOT, "All packages", null); printer.println(" <h2>Children</h2>"); printer.println(" <table width=\"100%\">"); printStatsHeader(printer); PackageDoc[] packages = root.specifiedPackages(); Arrays.sort(packages, comparator); for (PackageDoc pack : packages) { if (pack.allClasses().length != 0 && !isHidden(pack)) { Stats subStats = processPackage(pack); printStats(printer, subStats, true); derived.add(subStats); } } printer.println(" </table>"); printer.println(" <p>"); printer.println(" <h2>Summary</h2>"); printer.println(" <table width=\"100%\">"); printStatsHeader(printer); printStats(printer, derived, false); printer.println(" </table>"); closeFile(printer); } /** * Processes the details of a single package. */ private Stats processPackage(PackageDoc pack) throws IOException { String file = getPackageDir(pack) + "/package.html"; PrintWriter printer = openFile(file, "Package " + pack.name()); Stats derived = new Stats(TYPE_PACKAGE, pack.name(), file); printer.println(" <h2>Elements</h2>"); printer.println(" <table width=\"100%\">"); printElementHeader(printer); processElement(printer, pack, TYPE_PACKAGE, derived); printer.println(" </table>"); printer.println(" <p>"); printer.println(" <h2>Children</h2>"); printer.println(" <table width=\"100%\">"); printStatsHeader(printer); ClassDoc[] classes = pack.allClasses(); Arrays.sort(classes, comparator); for (ClassDoc clazz : classes) { if (!isHidden(clazz)) { Stats subStats = processClass(clazz); printStats(printer, subStats, true); derived.add(subStats); } } printer.println(" </table>"); printer.println(" <h2>Summary</h2>"); printer.println(" <table width=\"100%\">"); printStatsHeader(printer); printStats(printer, derived, false); printer.println(" </table>"); closeFile(printer); return derived; } /** * Processes the details of a single class. */ private Stats processClass(ClassDoc clazz) throws IOException { String file = getPackageDir(clazz.containingPackage()) + "/" + clazz.name() + ".html"; PrintWriter printer = openFile(file, "Class " + clazz.name()); Stats derived = new Stats(TYPE_CLASS, clazz.name(), clazz.name() + ".html"); printer.println(" <h2>Elements</h2>"); printer.println(" <table width=\"100%\">"); printElementHeader(printer); processElement(printer, clazz, TYPE_CLASS, derived); if(clazz.isEnum()){ FieldDoc[] enums = clazz.enumConstants(); Arrays.sort(enums, comparator); for(FieldDoc e : enums) { processElement(printer, e, TYPE_FIELD, derived); } } FieldDoc[] fields = clazz.fields(); Arrays.sort(fields, comparator); for (FieldDoc field : fields) { processElement(printer, field, TYPE_FIELD, derived); } ConstructorDoc[] constructors = clazz.constructors(); Arrays.sort(constructors, comparator); for (ConstructorDoc constructor : constructors) { if (constructor.position() != null) { String constPos = constructor.position().toString(); String classPos = constructor.containingClass().position() .toString(); if (!constPos.equals(classPos)) { processElement(printer, constructor, TYPE_METHOD, derived); } } } HashSet<MethodDoc> methodSet = new HashSet<MethodDoc>(); ClassDoc superClass = clazz.superclass(); MethodDoc[] methods = null; if (superClass != null && superClass.isPackagePrivate()) { MethodDoc[] classMethods = clazz.methods(); for (int i = 0; i < classMethods.length; i++) { methodSet.add(classMethods[i]); } while (superClass != null && superClass.isPackagePrivate()) { classMethods = superClass.methods(); for (int i = 0; i < classMethods.length; i++) { methodSet.add(classMethods[i]); } superClass = superClass.superclass(); } methods = new MethodDoc[methodSet.size()]; methodSet.toArray(methods); } else { methods = clazz.methods(); } Arrays.sort(methods, comparator); for (MethodDoc method : methods) { if (!(clazz.isEnum() && ("values".equals(method.name()) || "valueOf".equals(method.name())))) { processElement(printer, method, TYPE_METHOD, derived); } } printer.println(" </table>"); printer.println(" <p>"); printer.println(" <h2>Summary</h2>"); printer.println(" <table width=\"100%\">"); printStatsHeader(printer); printStats(printer, derived, false); printer.println(" </table>"); closeFile(printer); return derived; } /** * Processes a single element. */ private void processElement(PrintWriter printer, Doc doc, int type, Stats derived) { if (isHidden(doc)) { return; } List<String> errors = new ArrayList<String>(); boolean documented = isValidComment(doc.commentText()); boolean inherited = false; if(checkTypeParameters && (doc.isClass() || doc.isInterface())){ boolean typeParamsOk = hasAllTypeParameterDocs((ClassDoc)doc, errors); documented = documented && typeParamsOk; } if (doc.isMethod()) { MethodDoc method = (MethodDoc) doc; if ("".equals(method.commentText().trim())) { inherited = method.overriddenMethod() != null || implementedMethod(method) != null; documented = inherited; } } if (!documented) { errors.add("Missing or insufficient doc."); } if (!inherited) { if (doc.isMethod() || doc.isConstructor()) { ExecutableMemberDoc executable = (ExecutableMemberDoc) doc; boolean paramsOk = hasAllParameterDocs(executable, errors); boolean exceptionsOk = hasAllExceptionDocs(executable, errors); documented = documented && paramsOk && exceptionsOk; } if (doc.isMethod()) { MethodDoc method = (MethodDoc) doc; boolean resultOk = hasReturnDoc(method, errors); documented = documented && resultOk; } } boolean reviewed = hasSinceTag(doc); Tag[] comments = doc.tags("cts"); int status = getStatus(documented, reviewed || inherited, comments); printer.println(" <tr bgcolor=\"" + COLORS[status] + "\">"); printer.println(" <td>" + TYPES[type] + "</td>"); if (doc instanceof PackageDoc) { printer.println(" <td>" + doc.toString() + "</td>"); } else { String s = doc.name(); String t = doc.toString(); int i = t.indexOf(s); if (i != -1) { t = t.substring(i); } printer.println(" <td>" + t + "</td>"); } printer.println(" <td>" + getFirstSentence(doc) + "</td>"); printer.println(" <td>" + (documented ? "Yes" : "No") + "</td>"); printer.println(" <td>" + (reviewed ? "Yes" : "No") + "</td>"); printer.println(" <td>"); if (comments.length != 0 || errors.size() != 0) { printer.println(" </ul>"); for (int i = 0; i < comments.length; i++) { printer.print(" <li>"); printer.print(comments[i].text()); printer.println("</li>"); } for (int i = 0; i < errors.size(); i++) { printer.print(" <li>"); printer.print(errors.get(i)); printer.println("</li>"); } printer.println(" </ul>"); } else { printer.println(" "); } printer.println(" </td>"); printer.println(" </tr>"); derived.add(type, status, comments.length); } /** * Print the table header for an element table. */ private void printElementHeader(PrintWriter printer) { printer.println(" <tr>"); printer.println(" <td>Type</td>"); printer.println(" <td>Name</td>"); printer.println(" <td>First sentence</td>"); printer.println(" <td>Doc'd</td>"); printer.println(" <td>Rev'd</td>"); printer.println(" <td>Comments</td>"); printer.println(" </tr>"); } /** * Print the table header for stats table table. */ private void printStatsHeader(PrintWriter printer) { printer.println(" <tr>"); printer.println(" <td>Type</td>"); printer.println(" <td>Name</td>"); printer.println(" <td>#Classes</td>"); printer.println(" <td>#Fields</td>"); printer.println(" <td>#Methods</td>"); printer.println(" <td>#Red</td>"); printer.println(" <td>#Yellow</td>"); printer.println(" <td>#Green</td>"); printer.println(" <td>#Comments</td>"); printer.println(" </tr>"); } /** * Prints a single row to a stats table. */ private void printStats(PrintWriter printer, Stats info, boolean wantLink) { printer.println(" <tr bgcolor=\"" + COLORS[info.getValue()] + "\">"); printer.println(" <td>" + TYPES[info.getType()] + "</td>"); printer.print(" <td>"); String link = info.getLink(); if (wantLink && link != null) { printer.print("<a href=\"" + link + "\">" + info.getName() + "</a>"); } else { printer.print(info.getName()); } printer.println("</td>"); printer.println(" <td>" + info.getNumbersPerType(TYPE_CLASS) + "</td>"); printer.println(" <td>" + info.getNumbersPerType(TYPE_FIELD) + "</td>"); printer.println(" <td>" + info.getNumbersPerType(TYPE_METHOD) + "</td>"); printer.println(" <td>" + info.getNumbersPerValue(VALUE_RED) + "</td>"); printer.println(" <td>" + info.getNumbersPerValue(VALUE_YELLOW) + "</td>"); printer.println(" <td>" + info.getNumbersPerValue(VALUE_GREEN) + "</td>"); printer.println(" <td>" + info.getNumberOfComments() + "</td>"); printer.println(" </tr>"); } /** * Returns the directory for a given package. Basically converts embedded * dots in the name into slashes. */ private File getPackageDir(PackageDoc pack) { if (pack == null || pack.name() == null || "".equals(pack.name())) { return new File("."); } else { return new File(pack.name().replace('.', '/')); } } /** * Checks whether the given comment is not null and not of length 0. */ private boolean isValidComment(String comment) { return comment != null && comment.length() > 0; } /** * Checks whether the given interface or class has documentation for * all declared type parameters (no less, no more). */ private boolean hasAllTypeParameterDocs(ClassDoc doc, List<String> errors) { boolean result = true; TypeVariable[] params = doc.typeParameters(); Set<String> paramsSet = new HashSet<String>(); for (TypeVariable param : params) { paramsSet.add(param.typeName()); } ParamTag[] paramTags = doc.typeParamTags(); Map<String, String> paramTagsMap = new HashMap<String, String>(); for (ParamTag paramTag : paramTags) { if (!paramsSet.contains(paramTag.parameterName())) { errors.add("Unknown type parameter \"" + paramTag.parameterName() + "\""); result = false; } paramTagsMap.put(paramTag.parameterName(), paramTag.parameterComment()); } for (TypeVariable param : params) { if (!isValidComment(paramTagsMap.get(param.typeName()))) { errors.add("Undocumented type parameter \"" + param.typeName() + "\""); result = false; } } return result; } /** * Checks whether the given executable member has documentation for * all declared parameters (no less, no more). */ private boolean hasAllParameterDocs(ExecutableMemberDoc doc, List<String> errors) { boolean result = true; Parameter params[] = doc.parameters(); Set<String> paramsSet = new HashSet<String>(); for (int i = 0; i < params.length; i++) { Parameter param = params[i]; paramsSet.add(param.name()); } ParamTag[] paramTags = doc.paramTags(); Map<String, String> paramTagsMap = new HashMap<String, String>(); for (int i = 0; i < paramTags.length; i++) { ParamTag paramTag = paramTags[i]; if (!paramsSet.contains(paramTag.parameterName())) { errors.add("Unknown parameter \"" + paramTag.parameterName() + "\""); result = false; } paramTagsMap.put(paramTag.parameterName(), paramTag.parameterComment()); } for (int i = 0; i < params.length; i++) { Parameter param = params[i]; if (!isValidComment(paramTagsMap.get(param.name()))) { errors.add("Undocumented parameter \"" + param.name() + "\""); result = false; } } return result; } /** * Checks whether the given executable member has documentation for * all non-runtime exceptions. Runtime exceptions may or may not be * documented. */ private boolean hasAllExceptionDocs(ExecutableMemberDoc doc, List<String> errors) { boolean result = true; ClassDoc exceptions[] = doc.thrownExceptions(); Set<ClassDoc> exceptionSet = new HashSet<ClassDoc>(); for (int i = 0; i < exceptions.length; i++) { ClassDoc exception = exceptions[i]; if (isRelevantException(exception)) { exceptionSet.add(exception); } } ThrowsTag[] throwsTags = doc.throwsTags(); Map<ClassDoc, String> throwsTagsMap = new HashMap<ClassDoc, String>(); for (int i = 0; i < throwsTags.length; i++) { ThrowsTag throwsTag = throwsTags[i]; if (throwsTag.exception() == null) { errors.add("Unknown exception \"" + throwsTag.exceptionName() + "\""); result = false; } else if (isRelevantException(throwsTag.exception())) { ClassDoc exception = throwsTag.exception(); while (exception != null && !exceptionSet.contains(exception)) { exception = exception.superclass(); } if (exception == null) { errors.add("Unknown exception \"" + throwsTag.exceptionName() + "\""); result = false; } } throwsTagsMap.put(throwsTag.exception(), throwsTag.exceptionComment()); } for (int i = 0; i < exceptions.length; i++) { ClassDoc exception = exceptions[i]; boolean found = false; for (int j = 0; j < throwsTags.length && !found; j++) { ThrowsTag throwsTag = throwsTags[j]; ClassDoc candidate = throwsTag.exception(); if (candidate != null) { if (candidate.equals(exception) || candidate.subclassOf(exception)) { if (isValidComment(throwsTag.exceptionComment())) { found = true; } } } } if (!found) { errors.add("Undocumented exception \"" + exception.name() + "\""); result = false; } } return result; } /** * Checks whether an exception needs to be documented. Runtime exceptions * and errors don't necessarily need documentation (although it doesn't * hurt to have it). */ private boolean isRelevantException(ClassDoc clazz) { return !(clazz.subclassOf(runtimeException) || clazz.subclassOf(error)); } /** * Checks whether the given method has documentation for the return value. */ private boolean hasReturnDoc(MethodDoc method, List<String> errors) { boolean result = true; if (!"void".equals(method.returnType().typeName())) { Tag[] returnTags = method.tags("return"); if (returnTags.length == 0) { errors.add("Missing result."); result = false; } for (int i = 0; i < returnTags.length; i++) { Tag tag = returnTags[i]; if (!isValidComment(tag.text())) { errors.add("Insufficient result."); result = false; } } } else { Tag[] returnTags = method.tags("return"); if (returnTags.length != 0) { errors.add("Unknown result."); result = false; } } return result; } /** * Returns the first sentence for the given documentation element. */ private String getFirstSentence(Doc doc) { StringBuilder builder = new StringBuilder(); Tag[] tags = doc.firstSentenceTags(); for (int i = 0; i < tags.length; i++) { Tag tag = tags[i]; if ("Text".equals(tag.kind())) { builder.append(tag.text()); } else { builder.append("{" + tag.toString() + "}"); } } return builder.toString(); } /** * Returns the interface method that a given method implements, or null if * the method does not implement any interface method. */ private MethodDoc implementedMethod(MethodDoc doc) { ClassDoc clazz = doc.containingClass(); MethodDoc myDoc = null; while(clazz != null && myDoc == null){ ClassDoc[] interfaces = clazz.interfaces(); myDoc = implementedMethod0(doc, interfaces); clazz = clazz.superclass(); } return myDoc; } /** * Recursive helper method for finding out which interface method a given * method implements. */ private MethodDoc implementedMethod0(MethodDoc doc, ClassDoc[] interfaces) { for (int i = 0; i < interfaces.length; i++) { ClassDoc classDoc = interfaces[i]; MethodDoc[] methods = classDoc.methods(); for (int j = 0; j < methods.length; j++) { MethodDoc methodDoc = methods[j]; if (doc.overrides(methodDoc)) { return methodDoc; } } } for (int i = 0; i < interfaces.length; i++) { MethodDoc myDoc = implementedMethod0(doc, interfaces[i].interfaces()); if (myDoc != null) { return myDoc; } } return null; } /** * Checks whether the given documentation element has a "@since" tag for * Android. */ private boolean hasSinceTag(Doc doc) { Tag[] tags = doc.tags("since"); for (int i = 0; i < tags.length; i++) { if ("Android 1.0".equals(tags[i].text())) { return true; } } if (doc instanceof MemberDoc) { return hasSinceTag(((MemberDoc)doc).containingClass()); } if (doc instanceof ClassDoc) { return hasSinceTag(((ClassDoc)doc).containingPackage()); } return false; } /** * Checks whether the given documentation element has a "@hide" tag that * excludes it from the official API. */ private boolean isHidden(Doc doc) { Tag[] tags = doc.tags("hide"); return tags != null && tags.length != 0; } /** * Determines the status of an element based on the existence of * documentation, the review status, and any comments it might have. */ private int getStatus(boolean documented, boolean reviewed, Tag[] comments) { if (!documented) { return VALUE_RED; } else if (reviewed && comments.length == 0) { return VALUE_GREEN; } else { return VALUE_YELLOW; } } /** * Called by JavaDoc to find our which command line arguments are supported * and how many parameters they take. Part of the JavaDoc API. */ public static int optionLength(String option) { if ("-d".equals(option)) { return 2; } else if("-Xgeneric".equals(option)){ return 1; } else { return 0; } } /** * Returns a particular command line argument for a given option. */ private static String getOption(RootDoc root, String option, int index, String defValue) { String[][] allOptions = root.options(); for (int i = 0; i < allOptions.length; i++) { if (allOptions[i][0].equals(option)) { return allOptions[i][index]; } } return defValue; } /** * Returns whether the specified option is present. */ private static boolean isOptionSet(RootDoc root, String option){ String[][] allOptions = root.options(); for (int i = 0; i < allOptions.length; i++) { if (allOptions[i][0].equals(option)) { return true; } } return false; } /** * Called by JavaDoc to find out which Java version we claim to support. * Part of the JavaDoc API. */ public static LanguageVersion languageVersion() { return LanguageVersion.JAVA_1_5; } /** * The main entry point called by JavaDoc after all required information has * been collected. Part of the JavaDoc API. */ public static boolean start(RootDoc root) { try { String target = getOption(root, "-d", 1, "."); checkTypeParameters = isOptionSet(root, "-Xgeneric"); SpecProgressDoclet doclet = new SpecProgressDoclet(target); doclet.process(root); File file = new File(target, "index.html"); System.out.println("Please see complete report in " + file.getAbsolutePath()); } catch (Exception ex) { ex.printStackTrace(); return false; } return true; } }