| /* |
| * 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; |
| } |
| |
| } |