Home | History | Annotate | Download | only in src
      1 /*
      2  * Copyright (C) 2008 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 import com.sun.javadoc.AnnotationDesc;
     18 import com.sun.javadoc.AnnotationTypeDoc;
     19 import com.sun.javadoc.AnnotationValue;
     20 import com.sun.javadoc.ClassDoc;
     21 import com.sun.javadoc.ConstructorDoc;
     22 import com.sun.javadoc.Doc;
     23 import com.sun.javadoc.ExecutableMemberDoc;
     24 import com.sun.javadoc.LanguageVersion;
     25 import com.sun.javadoc.MethodDoc;
     26 import com.sun.javadoc.PackageDoc;
     27 import com.sun.javadoc.Parameter;
     28 import com.sun.javadoc.ParameterizedType;
     29 import com.sun.javadoc.RootDoc;
     30 import com.sun.javadoc.SourcePosition;
     31 import com.sun.javadoc.Tag;
     32 import com.sun.javadoc.Type;
     33 import com.sun.javadoc.TypeVariable;
     34 import com.sun.javadoc.AnnotationDesc.ElementValuePair;
     35 
     36 import java.io.File;
     37 import java.io.FileOutputStream;
     38 import java.io.IOException;
     39 import java.io.PrintWriter;
     40 import java.util.ArrayList;
     41 import java.util.Arrays;
     42 import java.util.Collections;
     43 import java.util.Comparator;
     44 import java.util.Date;
     45 import java.util.HashMap;
     46 import java.util.List;
     47 import java.util.Map;
     48 
     49 /*
     50  */
     51 public class TestCoverageDoclet {
     52 
     53     public static final int TYPE_FIELD = 0;
     54     public static final int TYPE_METHOD = 1;
     55     public static final int TYPE_CLASS = 2;
     56     public static final int TYPE_PACKAGE = 3;
     57     public static final int TYPE_ROOT = 4;
     58     public static final int VALUE_RED = 0;
     59     public static final int VALUE_YELLOW = 1;
     60     public static final int VALUE_GREEN = 2;
     61     public static final String[] COLORS = { "#ffa0a0", "#ffffa0", "#a0ffa0" };
     62     public static final String[] TYPES = { "Field", "Method", "Class", "Package", "All packages" };
     63 
     64     /**
     65      * Holds our basic output directory.
     66      */
     67     private File directory;
     68 
     69     private Map<ExecutableMemberDoc, AnnotationPointer> resolved =
     70             new HashMap<ExecutableMemberDoc, AnnotationPointer>(8192);
     71 
     72     /**
     73      * Helper class for comparing element with each other, in oder to determine
     74      * an order. Uses lexicographic order of names.
     75      */
     76     private class DocComparator implements Comparator<Doc> {
     77         public int compare(Doc elem1, Doc elem2) {
     78             return elem1.name().compareTo(elem2.name());
     79         }
     80 
     81         public boolean equals(Doc elem) {
     82             return this == elem;
     83         }
     84     }
     85 
     86     private class MemberComparator implements Comparator<ExecutableMemberDoc> {
     87         public int compare(ExecutableMemberDoc mem1, ExecutableMemberDoc mem2) {
     88             return mem1.toString().compareTo(mem2.toString());
     89         }
     90     }
     91 
     92     class MyStats {
     93         private String name;
     94         private String link;
     95         private int elemCnt = 0;
     96         private int[] ryg = new int[3];
     97         private String extra;
     98 
     99         public MyStats(int type, String name, String link) {
    100             this.name = name;
    101             this.link = link;
    102         }
    103 
    104         public void add(MyStats subStats) {
    105            elemCnt++;
    106            for (int i = 0; i < ryg.length; i++) {
    107                ryg[i]+= subStats.ryg[i];
    108            }
    109         }
    110 
    111         public int getCountFor(int color) {
    112             return ryg[color];
    113         }
    114 
    115         public String getStat() {
    116             float coverage = (float)(ryg[1]+ryg[2]) / (float)(ryg[0]+ryg[1]+ryg[2]);
    117             return "red: "+ryg[0]+", yellow:"+ryg[1]+", green:"+ryg[2]+",coverage:"+coverage;
    118         }
    119 
    120         public void inc(int color) {
    121             ryg[color]++;
    122         }
    123 
    124         public String getLink() {
    125             return link;
    126         }
    127 
    128         public String getName() {
    129             return name;
    130         }
    131 
    132         public String getExtra() {
    133             return extra;
    134         }
    135 
    136         public void setExtra(String extra) {
    137             this.extra = extra;
    138         }
    139     }
    140 
    141     /**
    142      * Holds our comparator instance for everything.
    143      */
    144     private DocComparator comparator = new DocComparator();
    145     private MemberComparator membercomparator = new MemberComparator();
    146 
    147     /**
    148      * Creates a new instance of the TestProgressDoclet for a given target
    149      * directory.
    150      */
    151     public TestCoverageDoclet(String directory) {
    152         this.directory = new File(directory);
    153     }
    154 
    155     /**
    156      * Opens a new output file and writes the usual HTML header. Directories
    157      * are created on demand.
    158      */
    159     private PrintWriter openFile(String name, String title) throws IOException {
    160         File file = new File(directory, name);
    161         File parent = file.getParentFile();
    162         parent.mkdirs();
    163 
    164         PrintWriter printer = new PrintWriter(new FileOutputStream(file));
    165 
    166         printer.println("<html>");
    167         printer.println("  <head>");
    168         printer.println("    <title>" + title + "</title>");
    169         printer.println("<style type=\"text/css\">\n"+
    170                 "body { }\n"+
    171                 "table {border-width: 0px; border: solid; border-collapse: collapse;}\n"+
    172                 "table tr td { vertical-align:top; padding:3px; border: 1px solid black;}\n"+
    173                 "</style>");
    174         printer.println("  </head>");
    175         printer.println("  <body>");
    176         printer.println("    <h1>" + title + "</h1>");
    177 
    178         return printer;
    179     }
    180 
    181     /**
    182      * Closes the given output file, writing the usual HTML footer before.
    183      */
    184     private void closeFile(PrintWriter printer) {
    185         printer.println("  </body>");
    186         printer.println("</html>");
    187         printer.flush();
    188         printer.close();
    189     }
    190 
    191     private class TablePrinter {
    192         private PrintWriter pr;
    193 
    194         public TablePrinter(PrintWriter pr) {
    195             this.pr = pr;
    196         }
    197 
    198         public void printRow(int color, String... columns) {
    199             String colo = COLORS[color];
    200             pr.print("<tr style=\"background-color:"+colo+"\">");
    201             for (String col : columns) {
    202                 pr.print("<td>"+col+"</td>");
    203             }
    204             pr.print("</tr>");
    205         }
    206 
    207         public void printRow(String... columns) {
    208             printRow(1, columns);
    209         }
    210 
    211         public void printPlain(String val) {
    212             pr.print(val);
    213         }
    214 
    215     }
    216 
    217     /**
    218      * Processes the whole list of classes that JavaDoc knows about.
    219      */
    220     private void process(RootDoc root) throws IOException {
    221 
    222         // 1. traverse all test-classes (those extending JUnit's TestCase)
    223         // and collect the annotation info. Print which test classes
    224         // need annotating
    225         PrintWriter pr = openFile("test-annotation.html", "test class annotation coverage");
    226         TablePrinter printer = new TablePrinter(pr);
    227         printer.printPlain("<table>");
    228         printer.printRow("className", "annotated methods", "total methods", "percentage");
    229 
    230         ClassDoc[] classes = root.classes();
    231         Arrays.sort(classes, new Comparator<ClassDoc>() {
    232             public int compare(ClassDoc c1, ClassDoc c2) {
    233                 return c1.toString().compareTo(c2.toString());
    234             }});
    235         for (ClassDoc classDoc : classes) {
    236             if (extendsJUnitTestCase(classDoc)) {
    237                 processTestClass(classDoc, printer);
    238             }
    239         }
    240         printer.printPlain("</table>");
    241         closeFile(pr);
    242         //dumpInfo();
    243 
    244         // 2. traverse all "normal" (non-junit) source files, for each method
    245         // get its status and propagate it up the tree
    246         MyStats stats = new MyStats(TYPE_ROOT, "All", "aaa.html");
    247         PrintWriter aprinter = openFile("index.html", "All packages");
    248         aprinter.println("Generated " + new Date().toString());
    249         aprinter.println("<br/><a href=\"test-annotation.html\">annotation progress of test classes</a><br/>");
    250         aprinter.println("<br/><a href=\"hidden-doc.html\">hidden classes and methods</a><br/>");
    251         aprinter.println("<br/><a href=\"interfaces.html\">interfaces</a><br/>");
    252         aprinter.println("<h2>Packages</h2>");
    253         aprinter.println("<table>");
    254 
    255         PrintWriter hiddenDocPr = openFile("hidden-doc.html", "hidden classes and methods list");
    256         TablePrinter hiddenDocPrinter = new TablePrinter(hiddenDocPr);
    257         hiddenDocPrinter.printPlain("<table>");
    258         hiddenDocPrinter.printRow("Package Name", "Class Name", "Method Name");
    259 
    260         PrintWriter interfacePr = openFile("interfaces.html", "interface list");
    261         TablePrinter interfacePrinter = new TablePrinter(interfacePr);
    262         interfacePrinter.printPlain("<table>");
    263         interfacePrinter.printRow("packageName", "className");
    264 
    265         PackageDoc[] packages = root.specifiedPackages();
    266         Arrays.sort(packages, comparator);
    267         for (PackageDoc pack : packages) {
    268             if (pack.allClasses().length != 0) {
    269 
    270                 if (pack.name().endsWith(".cts")) {
    271                     // Skip the cts test packages
    272 //                    System.out.println(">>>>>>>>>>>Skip package: " + pack.name());
    273                 } else {
    274                     MyStats subStat = processPackage(pack, hiddenDocPrinter, interfacePrinter);
    275 
    276                     System.out.println("package " + pack.name() + " has " + subStat.getCountFor(0) + " red.");
    277                     printStats(aprinter, subStat, true);
    278                     stats.add(subStat);
    279                 }
    280             }
    281         }
    282 
    283 
    284         System.out.println("Total has " + stats.getCountFor(0) + " red.");
    285 
    286         interfacePrinter.printPlain("</table>");
    287         closeFile(interfacePr);
    288 
    289         hiddenDocPrinter.printPlain("</table>");
    290         closeFile(hiddenDocPr);
    291 
    292         aprinter.println("</table>");
    293         aprinter.println("<h2>Summary</h2>");
    294         aprinter.println("<table>");
    295         printStats(aprinter, stats, false);
    296         aprinter.println("</table>");
    297 
    298         closeFile(aprinter);
    299     }
    300 
    301     /*private void processTargetClass(ClassDoc classDoc) {
    302         System.out.println("class:"+classDoc);
    303         // show all public/protected constructors
    304         for (ExecutableMemberDoc constr : classDoc.constructors()) {
    305             if (constr.isPublic() || constr.isProtected()) {
    306                 processTargetMC(constr);
    307             }
    308         }
    309         // show all public/protected methods
    310         for (ExecutableMemberDoc method : classDoc.methods()) {
    311             if (method.isPublic() || method.isProtected()) {
    312                 processTargetMC(method);
    313             }
    314         }
    315     }*/
    316 
    317     /*private void dumpInfo() {
    318         for (Map.Entry<ExecutableMemberDoc, AnnotationPointer> entry : resolved.entrySet()) {
    319             ExecutableMemberDoc mdoc = entry.getKey();
    320             AnnotationPointer ap = entry.getValue();
    321             System.out.println("----- entry -----------------------");
    322             System.out.println("target:"+mdoc.toString());
    323             System.out.println("=");
    324             for (MethodDoc meth : ap.testMethods) {
    325                 System.out.println("test method:"+meth);
    326             }
    327         }
    328     }*/
    329 
    330     private void processTestClass(ClassDoc classDoc, TablePrinter printer) {
    331         // System.out.println("Processing >>> " + classDoc);
    332         // collects all testinfo-annotation info of this class
    333         ClassDoc targetClass = null;
    334         // get the class annotation which names the default test target class
    335         AnnotationDesc[] cAnnots = classDoc.annotations();
    336         for (AnnotationDesc cAnnot : cAnnots) {
    337 
    338             AnnotationTypeDoc atype = cAnnot.annotationType();
    339             if (atype.toString().equals("dalvik.annotation.TestTargetClass")) {
    340                 // single member annot with one child 'value'
    341                 ElementValuePair[] cpairs = cAnnot.elementValues();
    342                 ElementValuePair evp = cpairs[0];
    343                 AnnotationValue av = evp.value();
    344                 Object obj = av.value();
    345 
    346                 // value must be a class doc
    347                 if (obj instanceof ClassDoc) {
    348                     targetClass = (ClassDoc) obj;
    349                 } else if (obj instanceof ParameterizedType) {
    350                     targetClass = ((ParameterizedType)obj).asClassDoc();
    351                 }
    352                 else throw new RuntimeException("annotation elem value is of type "+obj.getClass().getName());
    353             }
    354         }
    355 
    356         // now visit all methods (junit test methods - therefore we need not visit the constructors
    357         AnnotStat ast = new AnnotStat();
    358 
    359         //System.out.println("checking:"+classDoc.qualifiedName());
    360 
    361         MethodDoc[] methods = classDoc.methods();
    362         String note = "";
    363         if (targetClass == null) {
    364             note += "<br/>targetClass annotation missing!<br/>";
    365         }
    366 
    367         for (MethodDoc methodDoc : methods) {
    368             // ignore if it is not a junit test method
    369             if (!methodDoc.name().startsWith("test")) continue;
    370             if (classDoc.qualifiedName().equals("tests.api.java.io.BufferedInputStreamTest")) {
    371                 //System.out.println("method: "+methodDoc.toString());
    372             }
    373 
    374             if (targetClass == null) {
    375                 // if the targetClass is missing, count all methods as non-annotated
    376                 ast.incMethodCnt(false);
    377             } else {
    378                 String error = processTestMethod(methodDoc, ast, targetClass);
    379                 if (error != null) {
    380                     note+="<br/><b>E:</b> "+error;
    381                 }
    382             }
    383         }
    384 
    385         int man = ast.cntMethodWithAnnot;
    386         int mto = ast.cntAllMethods;
    387         float perc = mto==0? 100f : ((float)man)/mto * 100f;
    388 
    389         printer.printRow(man==mto && note.equals("")? 2:0, classDoc.qualifiedName(), ""+ast.cntMethodWithAnnot, ""+ast.cntAllMethods,
    390                 ""+perc+ note);
    391 
    392     }
    393 
    394     private class AnnotStat {
    395         int cntMethodWithAnnot = 0;
    396         int cntAllMethods = 0;
    397         /**
    398          * @param correctAnnot
    399          */
    400         public void incMethodCnt(boolean correctAnnot) {
    401             cntAllMethods++;
    402             if (correctAnnot) {
    403                 cntMethodWithAnnot++;
    404             }
    405         }
    406     }
    407 
    408     // points from one targetMethod to 0..n testMethods which test the target method
    409     private class AnnotationPointer {
    410         AnnotationPointer(ExecutableMemberDoc targetMethod) {
    411             this.targetMethod = targetMethod;
    412         }
    413 
    414         final ExecutableMemberDoc targetMethod;
    415         List<MethodDoc> testMethods = new ArrayList<MethodDoc>();
    416 
    417         public void addTestMethod(MethodDoc testMethod) {
    418             if (testMethods.contains(testMethod)) {
    419                 System.out.println("warn: testMethod refers more than once to the targetMethod, testMethod="+testMethod);
    420             } else {
    421                 testMethods.add(testMethod);
    422             }
    423         }
    424     }
    425 
    426     private String processTestMethod(MethodDoc methodDoc, AnnotStat ast, ClassDoc targetClass) {
    427         //System.out.println("processing method: " + methodDoc);
    428         // get all per-method-annotation
    429         boolean correctAnnot = false;
    430         AnnotationDesc[] annots = methodDoc.annotations();
    431         for (AnnotationDesc annot : annots) {
    432             if (annot.annotationType().toString().equals("dalvik.annotation.TestInfo")) {
    433                 ElementValuePair[] pairs = annot.elementValues();
    434                 for (ElementValuePair kv : pairs) {
    435                     if (kv.element().qualifiedName().equals("dalvik.annotation.TestInfo.targets")) {
    436                         // targets is an [] type
    437                         AnnotationValue[] targets = (AnnotationValue[]) kv.value().value();
    438                         for (AnnotationValue tval : targets) {
    439                             // the test targets must be annotations themselves
    440                             AnnotationDesc targetAnnot = (AnnotationDesc) tval.value();
    441                             ExecutableMemberDoc targetMethod = getTargetMethod(targetAnnot, targetClass);
    442                             if (targetMethod != null) {
    443                                 AnnotationPointer tar = getAnnotationPointer(targetMethod, true);
    444                                 tar.addTestMethod(methodDoc);
    445                                 correctAnnot = true;
    446                             } else {
    447                                 ast.incMethodCnt(false);
    448                                 return "error: could not resolve targetMethod for class "+targetClass+", annotation was:"+targetAnnot+", testMethod = "+methodDoc.toString();
    449                             }
    450                         }
    451                     }
    452                 }
    453             } // else some other annotation
    454         }
    455         ast.incMethodCnt(correctAnnot);
    456         return null;
    457     }
    458 
    459     private AnnotationPointer getAnnotationPointer(ExecutableMemberDoc targetMethod, boolean create) {
    460         AnnotationPointer ap = resolved.get(targetMethod);
    461         if (create && ap == null) {
    462             ap = new AnnotationPointer(targetMethod);
    463             resolved.put(targetMethod, ap);
    464         }
    465         return ap;
    466     }
    467 
    468     private ExecutableMemberDoc getTargetMethod(AnnotationDesc targetAnnot,
    469             ClassDoc targetClass) {
    470         // targetAnnot like @android.annotation.TestTarget(methodName="group", methodArgs=int.class)
    471         ElementValuePair[] pairs = targetAnnot.elementValues();
    472         String methodName = null;
    473         String args = "";
    474         for (ElementValuePair kval : pairs) {
    475             if (kval.element().name().equals("methodName")) {
    476                 methodName = (String) kval.value().value();
    477             } else if (kval.element().name().equals("methodArgs")) {
    478                 AnnotationValue[] vals = (AnnotationValue[]) kval.value().value();
    479                 for (int i = 0; i < vals.length; i++) {
    480                     AnnotationValue arg = vals[i];
    481                     String argV;
    482                     if (arg.value() instanceof ClassDoc) {
    483                        ClassDoc cd = (ClassDoc)arg.value();
    484                        argV = cd.qualifiedName();
    485                     } else { // primitive type or array type
    486                         // is there a nicer way to do this?
    487                         argV = arg.toString();
    488                     }
    489                     // strip .class out of args since signature does not contain those
    490                     if (argV.endsWith(".class")) {
    491                         argV = argV.substring(0, argV.length()-6);
    492                     }
    493                     args+= (i>0? ",":"") + argV;
    494                 }
    495             }
    496         }
    497         // both methodName and methodArgs != null because of Annotation definition
    498 
    499         String refSig = methodName+"("+args+")";
    500         //System.out.println("Check " + refSig);
    501         // find the matching method in the target class
    502         // check all methods
    503         for (ExecutableMemberDoc mdoc : targetClass.methods()) {
    504             if (equalsSignature(mdoc, refSig)) {
    505                 return mdoc;
    506             }
    507         }
    508         // check constructors, too
    509         for (ExecutableMemberDoc mdoc : targetClass.constructors()) {
    510             if (equalsSignature(mdoc, refSig)) {
    511                 return mdoc;
    512             }
    513         }
    514         return null;
    515     }
    516 
    517     private boolean equalsSignature(ExecutableMemberDoc mdoc, String refSignature) {
    518         Parameter[] params = mdoc.parameters();
    519         String targs = "";
    520         for (int i = 0; i < params.length; i++) {
    521             Parameter parameter = params[i];
    522             // check for generic type types
    523             Type ptype = parameter.type();
    524             TypeVariable typeVar = ptype.asTypeVariable();
    525             String ptname;
    526             if (typeVar != null) {
    527                 ptname = "java.lang.Object"; // the default fallback
    528                 Type[] bounds = typeVar.bounds();
    529                 if (bounds.length > 0) {
    530                     ClassDoc typeClass = bounds[0].asClassDoc();
    531                     ptname = typeClass.qualifiedName();
    532                 }
    533             } else {
    534                 // regular var
    535                 //ptname = parameter.type().qualifiedTypeName();
    536                 ptname = parameter.type().toString();
    537 
    538                 //System.out.println("quali:"+ptname);
    539                 //ptname = parameter.typeName();
    540                 // omit type signature
    541                 ptname = ptname.replaceAll("<.*>","");
    542             }
    543             targs+= (i>0? ",":"") + ptname;
    544         }
    545         String testSig = mdoc.name()+"("+targs+")";
    546 
    547         //return testSig.equals(refSignature);
    548         if (testSig.equals(refSignature)) {
    549             //System.out.println("found: Sig:"+testSig);
    550             return true;
    551         } else {
    552             //System.out.println("no match: ref = "+refSignature+", test = "+testSig);
    553             return false;
    554         }
    555     }
    556 
    557     private boolean extendsJUnitTestCase(ClassDoc classDoc) {
    558         //junit.framework.TestCase.java
    559         ClassDoc curClass = classDoc;
    560         while ((curClass = curClass.superclass()) != null) {
    561             if (curClass.toString().equals("junit.framework.TestCase")) {
    562                 return true;
    563             }
    564         }
    565 
    566         return false;
    567     }
    568 
    569     /**
    570      * Processes the details of a single package.
    571      * @param hiddenDocPrinter
    572      * @param excludedClassPrinter
    573      * @param interfacePrinter
    574      */
    575     private MyStats processPackage(PackageDoc pack, TablePrinter hiddenDocPrinter,
    576             TablePrinter interfacePrinter) throws IOException {
    577         String file = getPackageDir(pack) + "/package.html";
    578         PrintWriter printer = openFile(file, "Package " + pack.name());
    579 
    580         MyStats stats = new MyStats(TYPE_PACKAGE, pack.name(), file);
    581         printer.println("<table>");
    582 
    583         ClassDoc[] classes = pack.allClasses();
    584         Arrays.sort(classes, comparator);
    585         for (ClassDoc clazz : classes) {
    586             if (extendsJUnitTestCase(clazz)) {
    587                 printer.println("<tr><td>ignored(junit):"+clazz.name()+"</td></tr>");
    588             } else if (isHiddenClass(clazz)) {
    589                 hiddenDocPrinter.printRow(pack.name(), clazz.name(), "*");
    590             } else if (clazz.isInterface()) {
    591                 interfacePrinter.printRow(pack.name(), clazz.name());
    592             } else {
    593                 MyStats subStats = processClass(clazz, hiddenDocPrinter);
    594                 printStats(printer, subStats, true);
    595                 stats.add(subStats);
    596             }
    597         }
    598         printer.println("</table>");
    599         closeFile(printer);
    600         return stats;
    601     }
    602 
    603     private boolean isHiddenClass(ClassDoc clazz) {
    604         if (clazz == null) {
    605             return false;
    606         }
    607 
    608         if (isHiddenDoc(clazz)) {
    609             return true;
    610         }
    611 
    612         // If outter class is hidden, this class should be hidden as well
    613         return isHiddenClass(clazz.containingClass());
    614     }
    615 
    616     private boolean isHiddenDoc(Doc doc) {
    617         // Since currently we have two kinds of annotations to mark a class as hide:
    618         //  1. @hide
    619         //  2. {@hide}
    620         // So we should consider both conditions.
    621         for (Tag t : doc.tags()) {
    622             if (t.name().equals("@hide")) {
    623                 return true;
    624             }
    625         }
    626 
    627         for (Tag t : doc.inlineTags()) {
    628             if (t.name().equals("@hide")) {
    629                 return true;
    630             }
    631         }
    632 
    633         return false;
    634     }
    635 
    636     private MyStats processClass(ClassDoc clazz, TablePrinter hiddenDocPrinter) throws IOException {
    637         //System.out.println("Process source class: " + clazz);
    638         String file = getPackageDir(clazz.containingPackage()) + "/" + clazz.name() + ".html";
    639         PrintWriter printer = openFile(file, "Class " + clazz.name());
    640 
    641         String packageName = clazz.containingPackage().name();
    642         String className = clazz.name();
    643 
    644         MyStats stats = new MyStats(TYPE_CLASS, className, className+".html");
    645         printer.println("<table><tr><td>name</td><td>tested by</td></tr>");
    646         ConstructorDoc[] constructors = clazz.constructors();
    647         Arrays.sort(constructors, comparator);
    648         for (ConstructorDoc constructor : constructors) {
    649             //System.out.println("constructor: " + constructor);
    650             if (isHiddenDoc(constructor)) {
    651                 hiddenDocPrinter.printRow(packageName, className, constructor.name());
    652             } else if (!isGeneratedConstructor(constructor)) {
    653                 MyStats subStat = processElement(constructor);
    654                 printStats(printer, subStat, false);
    655                 stats.add(subStat);
    656             }
    657         }
    658 
    659         MethodDoc[] methods = clazz.methods();
    660         Arrays.sort(methods, comparator);
    661         for (MethodDoc method : methods) {
    662             //System.out.println("method: " + method);
    663             if ("finalize".equals(method.name())) {
    664                 // Skip finalize method
    665             } else if (isHiddenDoc(method)) {
    666                 hiddenDocPrinter.printRow(packageName, className, method.name());
    667             } else if (method.isAbstract()) {
    668                 // Skip abstract method
    669             } else {
    670                 MyStats subStat = processElement(method);
    671                 printStats(printer, subStat, false);
    672                 stats.add(subStat);
    673             }
    674         }
    675 
    676         printer.println("</table>");
    677         closeFile(printer);
    678         return stats;
    679     }
    680 
    681     /**
    682      * Determines whether a constructor has been automatically generated and is
    683      * thus not present in the original source. The only way to find out seems
    684      * to compare the source position against the one of the class. If they're
    685      * equal, the constructor does not exist. It's a bit hacky, but it works.
    686      */
    687     private boolean isGeneratedConstructor(ConstructorDoc doc) {
    688         SourcePosition constPos = doc.position();
    689         SourcePosition classPos = doc.containingClass().position();
    690 
    691         return ("" + constPos).equals("" + classPos);
    692     }
    693 
    694     /**
    695      * Processes a single method/constructor.
    696      */
    697     private MyStats processElement(ExecutableMemberDoc method) {
    698         //int color = getColor(doc)
    699         //derived.add(subStats)
    700         AnnotationPointer ap = getAnnotationPointer(method, false);
    701         MyStats stats = new MyStats(TYPE_METHOD, "<b>"+method.name() + "</b> "+method.signature(), null);
    702         int refCnt = 0;
    703         if (ap != null) {
    704             refCnt = ap.testMethods.size();
    705             String by = "";
    706             List<MethodDoc> testM = ap.testMethods;
    707             Collections.sort(testM, membercomparator);
    708             for (MethodDoc teme : testM) {
    709                 by+= "<br/>"+teme.toString();
    710             }
    711             stats.setExtra(by);
    712         } // else this class has no single test that targets one of its method
    713 
    714         if (refCnt == 0) {
    715             stats.inc(VALUE_RED);
    716         } else if (refCnt == 1) {
    717             stats.inc(VALUE_YELLOW);
    718         } else {
    719             stats.inc(VALUE_GREEN);
    720         }
    721         return stats;
    722     }
    723 
    724     /**
    725      * Prints a single row to a stats table.
    726      */
    727     private void printStats(PrintWriter printer, MyStats info, boolean wantLink) {
    728         int red = info.getCountFor(VALUE_RED);
    729         int yellow = info.getCountFor(VALUE_YELLOW);
    730 
    731         printer.println("<tr>");
    732 
    733         // rule for coloring:
    734         // if red > 0 -> red
    735         // if yellow > 0 -> yellow
    736         // else green
    737         int color;
    738         if (red > 0) {
    739             color = VALUE_RED;
    740         } else if (yellow > 0) {
    741             color = VALUE_YELLOW;
    742         } else {
    743             color = VALUE_GREEN;
    744         }
    745 
    746         printer.println("<td bgcolor=\""+COLORS[color]+"\">");
    747         String link = info.getLink();
    748         if (wantLink && link != null) {
    749             printer.print("<a href=\"" + link + "\">" + info.getName() + "</a>");
    750         } else {
    751             printer.print(info.getName());
    752         }
    753         printer.println(" ("+info.getStat()+") </td>");
    754         if (info.getExtra()!=null) {
    755             printer.println("<td>"+info.getExtra()+"</td>");
    756         }
    757         printer.println("</tr>");
    758     }
    759 
    760     /**
    761      * Returns the directory for a given package. Basically converts embedded
    762      * dots in the name into slashes.
    763      */
    764     private File getPackageDir(PackageDoc pack) {
    765         if (pack == null || pack.name() == null || "".equals(pack.name())) {
    766             return new File(".");
    767         } else {
    768             return new File(pack.name().replace('.', '/'));
    769         }
    770     }
    771 
    772     /**
    773      * Called by JavaDoc to find our which command line arguments are supported
    774      * and how many parameters they take. Part of the JavaDoc API.
    775      */
    776     public static int optionLength(String option) {
    777         if ("-d".equals(option)) {
    778             return 2;
    779         } else {
    780             return 0;
    781         }
    782     }
    783 
    784     /**
    785      * Called by JavaDoc to query a specific command line argument. Part of the
    786      * JavaDoc API.
    787      */
    788     private static String getOption(RootDoc root, String option, int index, String defValue) {
    789         String[][] allOptions = root.options();
    790         for (int i = 0; i < allOptions.length; i++) {
    791             if (allOptions[i][0].equals(option)) {
    792                 return allOptions[i][index];
    793             }
    794         }
    795         return defValue;
    796     }
    797 
    798     /**
    799      * Called by JavaDoc to find out which Java version we claim to support.
    800      * Part of the JavaDoc API.
    801      */
    802     public static LanguageVersion languageVersion() {
    803         return LanguageVersion.JAVA_1_5;
    804     }
    805 
    806     /**
    807      * The main entry point called by JavaDoc after all required information has
    808      * been collected. Part of the JavaDoc API.
    809      */
    810     public static boolean start(RootDoc root) {
    811         try {
    812             String target = getOption(root, "-d", 1, ".");
    813             TestCoverageDoclet doclet = new TestCoverageDoclet(target);
    814             doclet.process(root);
    815 
    816         } catch (Exception ex) {
    817             ex.printStackTrace();
    818             return false;
    819         }
    820         return true;
    821     }
    822 
    823 }
    824