Home | History | Annotate | Download | only in testprogress2
      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 package testprogress2;
     18 
     19 import com.sun.javadoc.AnnotationDesc;
     20 import com.sun.javadoc.AnnotationTypeDoc;
     21 import com.sun.javadoc.AnnotationValue;
     22 import com.sun.javadoc.ClassDoc;
     23 import com.sun.javadoc.ConstructorDoc;
     24 import com.sun.javadoc.Doc;
     25 import com.sun.javadoc.ExecutableMemberDoc;
     26 import com.sun.javadoc.LanguageVersion;
     27 import com.sun.javadoc.MethodDoc;
     28 import com.sun.javadoc.PackageDoc;
     29 import com.sun.javadoc.ParameterizedType;
     30 import com.sun.javadoc.RootDoc;
     31 import com.sun.javadoc.Tag;
     32 import com.sun.javadoc.AnnotationDesc.ElementValuePair;
     33 
     34 import testprogress2.TestMethodInformation.Color;
     35 import testprogress2.TestMethodInformation.Level;
     36 
     37 import java.io.File;
     38 import java.io.FileNotFoundException;
     39 import java.io.FileOutputStream;
     40 import java.io.PrintWriter;
     41 import java.util.ArrayList;
     42 import java.util.Arrays;
     43 import java.util.Collections;
     44 import java.util.Comparator;
     45 import java.util.Date;
     46 import java.util.HashMap;
     47 import java.util.List;
     48 import java.util.Map;
     49 
     50 /**
     51  * this doclet generates a .html report about the test coverage of the core api
     52  * reading test method's annotations.
     53  */
     54 public class TestCoverageDoclet {
     55 
     56     /**
     57      * color for the stats: green, yellow, red
     58      */
     59     public static final String[] COLORS = {
     60             "#a0ffa0", "#ffffa0", "#ff8080"
     61     };
     62 
     63     // debugging output
     64     private static final boolean DEBUG = false;
     65 
     66     /**
     67      * Holds our basic output directory.
     68      */
     69     private File directory;
     70 
     71     private boolean _ignoreInterfacesAndAbstractMethods = false;
     72 
     73     private boolean _doIncludeDisabledTests = false;
     74 
     75     private boolean _acceptCompleteWithOtherStatus = false;
     76 
     77     private Map<ExecutableMemberDoc, AnnotationPointer> resolved =
     78         new HashMap<ExecutableMemberDoc, AnnotationPointer>(8192);
     79 
     80     /**
     81      * Helper class for comparing element with each other, in oder to determine
     82      * an order. Uses lexicographic order of names.
     83      */
     84     private class DocComparator implements Comparator<Doc> {
     85         public int compare(Doc elem1, Doc elem2) {
     86             return elem1.name().compareTo(elem2.name());
     87         }
     88 
     89         public boolean equals(Doc elem) {
     90             return this == elem;
     91         }
     92     }
     93 
     94     private class MemberComparator implements Comparator<ExecutableMemberDoc> {
     95         public int compare(ExecutableMemberDoc mem1, ExecutableMemberDoc mem2) {
     96             return mem1.toString().compareTo(mem2.toString());
     97         }
     98     }
     99 
    100     /**
    101      * Holds our comparator instance for everything.
    102      */
    103     private DocComparator classComparator = new DocComparator();
    104 
    105     private MemberComparator memberComparator = new MemberComparator();
    106 
    107     private Map<ClassDoc, List<TestTargetNew>> classToSpecialTargets =
    108         new HashMap<ClassDoc, List<TestTargetNew>>();
    109 
    110     /**
    111      * Creates a new instance of the TestProgressDoclet for a given target
    112      * directory.
    113      *
    114      * @param directory
    115      */
    116     public TestCoverageDoclet(String directory) {
    117         this.directory = new File(directory);
    118     }
    119 
    120     /**
    121      * Opens a new output file and writes the usual HTML header. Directories are
    122      * created on demand.
    123      */
    124     private PrintWriter openFile(String filename, String title) {
    125         File file = new File(directory, filename);
    126         File parent = file.getParentFile();
    127         parent.mkdirs();
    128 
    129         PrintWriter printer;
    130         try {
    131             printer = new PrintWriter(new FileOutputStream(file));
    132         } catch (FileNotFoundException e) {
    133             throw new RuntimeException("file not found:" + e.getMessage());
    134         }
    135 
    136         printer.println("<html>");
    137         printer.println("  <head>");
    138         printer.println("    <title>" + title + "</title>");
    139         printer.println("<style type=\"text/css\">\n"
    140                 + "body, body table tr td { font-size:10pt;font-family: "
    141                 + " sans-serif; }\n" + "li { padding-bottom:2px }\n"
    142                 + "table { width:100%; border-width: 0px; border: solid; "
    143                 + "border-collapse: collapse;}\n"
    144                 + "table tr td { vertical-align:top; padding:3px; border: "
    145                 + "1px solid black;}\n" + "</style>");
    146         printer.println("  </head>");
    147         printer.println("  <body>");
    148         printer.println("    <h1>" + title + "</h1>");
    149 
    150         return printer;
    151     }
    152 
    153     /**
    154      * Closes the given output file, writing the usual HTML footer before.
    155      */
    156     private void closeFile(PrintWriter printer) {
    157         printer.println("  </body>");
    158         printer.println("</html>");
    159         printer.flush();
    160         printer.close();
    161     }
    162 
    163     private class TablePrinter {
    164         private PrintWriter pr;
    165 
    166         public TablePrinter(PrintWriter pr) {
    167             this.pr = pr;
    168         }
    169 
    170         public void printRow(String... columns) {
    171             pr.print("<tr style=\"background-color:white\">");
    172             for (String col : columns) {
    173                 pr.print("<td>" + col + "</td>");
    174             }
    175             pr.print("</tr>");
    176         }
    177 
    178         public void printPlain(String val) {
    179             pr.print(val);
    180         }
    181 
    182         public void printTableStart() {
    183             pr.print("<table>");
    184         }
    185 
    186         public void printTableEnd() {
    187             pr.print("</table>");
    188         }
    189 
    190         public void printPlainLn(String val) {
    191             printPlain(val);
    192             pr.print("<br>");
    193         }
    194     }
    195 
    196     /**
    197      * Processes the whole list of classes that JavaDoc knows about.
    198      */
    199     private void process(RootDoc root) {
    200         System.out.println("V 0.9a");
    201         String mode = getOption(root, "-f", 1, "dummy-see bash script");
    202         System.out.println("mode: " + mode);
    203 
    204         // standard mode is to include disabled tests
    205         _doIncludeDisabledTests = mode.contains("countdisabled");
    206         _acceptCompleteWithOtherStatus = mode.contains("acceptcandx");
    207         if (_doIncludeDisabledTests) {
    208             System.out.println("- including disabled tests");
    209         } else {
    210             System.out.println("- excluding disabled tests");
    211         }
    212         if (_acceptCompleteWithOtherStatus) {
    213             System.out.println("- accepting complete tests with partial tests");
    214         } else {
    215             System.out.println("- not accepting complete tests with partial "
    216                     + "tests");
    217         }
    218 
    219         // 1. traverse all test-classes (those extending JUnit's TestCase)
    220         // and collect the annotation info.
    221         // ===================================================================
    222         System.out.println("stage 1 - get targets from all junit test methods");
    223         PrintWriter pr = openFile("testcoverage/test-annotation.html",
    224                 "test class annotation coverage");
    225         TablePrinter printer = new TablePrinter(pr);
    226         printer.printTableStart();
    227         printer.printRow("Test package name", "JUnit classes", "Meth", "Unt",
    228                 "Part", "Compl", "Disab", "Broken", "ToBeFixed", "KnownFail");
    229 
    230         ColorStat totalStats = new ColorStat("All test packages", null);
    231         PackageDoc[] packages = root.specifiedPackages();
    232         Arrays.sort(packages, classComparator);
    233         for (PackageDoc pack : packages) {
    234             if (pack.allClasses().length != 0) {
    235                 ColorStat packageStat = processTestPackage(pack);
    236                 if (!packageStat.ignored) {
    237                     // ignore those packages which have 0 tests in it
    238                     printTestStats(printer, packageStat, true);
    239                     totalStats.add(packageStat);
    240                 }
    241             }
    242         }
    243         printer.printTableEnd();
    244 
    245         printer.printPlainLn("<h2>Summary of all test packages</h2>");
    246         printer.printTableStart();
    247         printTestStats(printer, totalStats, false);
    248         printer.printTableEnd();
    249         closeFile(pr);
    250 
    251         System.out.println("stage 2 - proxy test targets to abstract classes"
    252                 + "and interfaces");
    253         // 1/2 - traverse all normal (non-junit) classes for interface
    254         // and abstract methods implementation tests.
    255         ClassDoc[] classes = root.classes();
    256         for (ClassDoc classDoc : classes) {
    257             if (!extendsJUnitTestCase(classDoc)) {
    258                 MethodDoc[] methods = classDoc.methods();
    259                 for (MethodDoc methodDoc : methods) {
    260                     AnnotationPointer ap = getAnnotationPointer(methodDoc,
    261                             false);
    262                     if (ap != null) {
    263                         // if there are already tests targeting this method,
    264                         // search for abstract/interface methods which this
    265                         // method
    266                         // implements, and mark them as tested by this method,
    267                         // too.
    268                         List<MethodDoc> impls = implementingMethods(methodDoc);
    269                         for (MethodDoc impl : impls) {
    270                             AnnotationPointer apImpl = getAnnotationPointer(
    271                                     impl, true);
    272                             // add all tests which pointed to the original
    273                             // method.
    274                             apImpl.addProxiesFrom(ap);
    275                         }
    276                     }
    277                 }
    278             }
    279         }
    280 
    281         // 2. traverse all "normal" (non-junit) source files
    282         // ===================================================================
    283         System.out.println("stage 3 - generating report for target api");
    284         pr = openFile("index.html", "All target api packages");
    285         printer = new TablePrinter(pr);
    286         printer.printPlainLn("Generated " + new Date().toString()
    287                 + " - V0.9a<br>");
    288         printer.printPlainLn("<a href=\"testcoverage/test-annotation.html\">"
    289                 + "annotation progress of test classes</a><br><br>");
    290 
    291         printer.printTableStart();
    292         printer.printRow("Package", "Classes", "Methods", "Untested",
    293                 "Partial", "Complete", "Disabled", "Broken", "ToBeFixed", "KnownFail");
    294 
    295         totalStats = new ColorStat("All target packages", null);
    296         packages = root.specifiedPackages();
    297         Arrays.sort(packages, classComparator);
    298 
    299         int classCount = 0;
    300         for (PackageDoc pack : packages) {
    301             if (pack.allClasses().length != 0) {
    302                 ColorStat packageStat = processPackage(pack);
    303 
    304                 if (!packageStat.ignored) {
    305                     printStats(printer, packageStat, true);
    306                     totalStats.add(packageStat);
    307 
    308                     classCount += Integer.parseInt(packageStat.getExtra());
    309                 }
    310             }
    311         }
    312         printer.printTableEnd();
    313 
    314         totalStats.setExtra("" + classCount);
    315         printer.printPlainLn("<h2>Summary of all target packages</h2>");
    316         printer.printTableStart();
    317         printStats(printer, totalStats, false);
    318         printer.printTableEnd();
    319         closeFile(pr);
    320     }
    321 
    322     /**
    323      * Returns the interface(s) method(s) and the abstract method(s) that a
    324      * given method implements, or an empty list if it does not implement
    325      * anything.
    326      */
    327     private List<MethodDoc> implementingMethods(MethodDoc doc) {
    328         List<MethodDoc> resultmethods = new ArrayList<MethodDoc>();
    329         ClassDoc clazz = doc.containingClass();
    330         implementedMethod0(resultmethods, doc, clazz, false);
    331         return resultmethods;
    332     }
    333 
    334     /**
    335      * Recursive helper method for finding out which interface methods or
    336      * abstract methods a given method implements.
    337      */
    338     private void implementedMethod0(List<MethodDoc> resultmethods,
    339             MethodDoc doc, ClassDoc testClass, boolean testMethods) {
    340 
    341         // test all methods of this class
    342         if (testMethods) {
    343             MethodDoc[] methods = testClass.methods();
    344             for (int j = 0; j < methods.length; j++) {
    345                 MethodDoc methodDoc = methods[j];
    346                 if ((methodDoc.isAbstract() || testClass.isInterface())
    347                         && doc.overrides(methodDoc)) {
    348                     resultmethods.add(methodDoc);
    349                 }
    350             }
    351         }
    352 
    353         // test all implementing interfaces
    354         ClassDoc[] ifs = testClass.interfaces();
    355         for (int i = 0; i < ifs.length; i++) {
    356             ClassDoc iface = ifs[i];
    357             implementedMethod0(resultmethods, doc, iface, true);
    358         }
    359 
    360         // test the superclass
    361         ClassDoc superclass = testClass.superclass();
    362         if (superclass != null) {
    363             implementedMethod0(resultmethods, doc, superclass, true);
    364         }
    365     }
    366 
    367     private ColorStat processTestPackage(PackageDoc packageDoc) {
    368         ColorStat stats = new ColorStat(packageDoc.name(),
    369                 getPackageBaseLink(packageDoc) + "/package.html");
    370         if (hasHideFlag(packageDoc)) {
    371             stats.ignored = true;
    372             return stats;
    373         }
    374         String file = getPackageDir("testcoverage", packageDoc)
    375                 + "/package.html";
    376         PrintWriter pr = openFile(file, "Test package " + packageDoc.name());
    377         TablePrinter printer = new TablePrinter(pr);
    378         printer.printTableStart();
    379         printer.printRow("Class", "Extra", "Meth", "Unt", "Part", "Compl",
    380                 "Disab", "Broken", "ToBeFixed", "KnownFail");
    381 
    382         ClassDoc[] classes = packageDoc.allClasses();
    383         Arrays.sort(classes, classComparator);
    384         int junitCnt = 0;
    385         for (ClassDoc clazz : classes) {
    386             if (extendsJUnitTestCase(clazz)) {
    387                 junitCnt++;
    388                 ColorStat subStats = processTestClass(clazz);
    389                 printTestStats(printer, subStats, true);
    390                 stats.add(subStats);
    391             } else {
    392                 printer.printRow(clazz.name() + " ignored (no junit class): ",
    393                         "", "", "", "", "", "", "", "");
    394             }
    395         }
    396         printer.printTableEnd();
    397 
    398         printer.printPlainLn("<h2>Test package summary</h2>");
    399         printer.printTableStart();
    400         printStats(printer, stats, false);
    401         printer.printTableEnd();
    402 
    403         closeFile(pr);
    404         if (junitCnt == 0) {
    405             if ((packageDoc.name().contains("tests.")
    406                     || packageDoc.name().contains("junit.") || packageDoc
    407                     .name().contains(".testframework"))
    408                     && !(packageDoc.name().equals("junit.framework"))) {
    409                 System.err.println("warning!: no junit classes in package '"
    410                         + packageDoc.name() + "' even though package name "
    411                         + "contains tests.,junit. or .testframework");
    412             }
    413             stats = new ColorStat(packageDoc.name(),
    414                     getPackageBaseLink(packageDoc) + "/package.html");
    415             stats.incColor(TestMethodInformation.Color.GREEN);
    416             stats.setExtra("Ignored since no Junit test and suites");
    417             stats.ignored = true;
    418         } else {
    419             stats.setExtra("" + junitCnt);
    420         }
    421         return stats;
    422     }
    423 
    424     private ColorStat processPackage(PackageDoc packageDoc) {
    425         ColorStat stats = new ColorStat(packageDoc.name(),
    426                 getPackageBaseLink(packageDoc) + "/package.html");
    427         if (hasHideFlag(packageDoc)) {
    428             stats.ignored = true;
    429             return stats;
    430         }
    431         String file = getPackageDir("", packageDoc) + "/package.html";
    432         PrintWriter pr = openFile(file, "Package " + packageDoc.name());
    433         TablePrinter printer = new TablePrinter(pr);
    434         printer.printTableStart();
    435         printer.printRow("Class", "Extra", "Meth", "Unt", "Part", "Compl",
    436                 "Disab", "Broken", "ToBeFixed", "KnownFail");
    437 
    438         ClassDoc[] classes = packageDoc.allClasses();
    439         Arrays.sort(classes, classComparator);
    440         int cnt = 0;
    441         int junitCnt = 0;
    442         for (ClassDoc clazz : classes) {
    443             cnt++;
    444             if (hasHideFlag(clazz)) {
    445                 // ignored since it has a @hide in the javadoc on the class
    446                 // level
    447             } else if (extendsJUnitTestCase(clazz)) {
    448                 printer.printRow("ignored (junit class): " + clazz.name());
    449                 junitCnt++;
    450             } else if (clazz.name().equals("AllTests")) {
    451                 printer.printRow("ignored (junit test suite class): "
    452                         + clazz.name());
    453                 junitCnt++;
    454             } else {
    455                 ColorStat subStats = processClass(clazz);
    456                 printStats(printer, subStats, true);
    457                 stats.add(subStats);
    458             }
    459         }
    460         printer.printTableEnd();
    461 
    462         printer.printPlainLn("<h2>Target package summary</h2>");
    463         printer.printTableStart();
    464         printStats(printer, stats, false);
    465         printer.printTableEnd();
    466 
    467         closeFile(pr);
    468         if (junitCnt == cnt || packageDoc.name().contains("tests.")
    469                 || packageDoc.name().contains("junit.")
    470                 || packageDoc.name().contains(".testframework")
    471                 || packageDoc.name().endsWith(".cts")) {
    472             // we only have junit classes -> mark green
    473             stats = new ColorStat(packageDoc.name(),
    474                     getPackageBaseLink(packageDoc) + "/package.html");
    475             stats.incColor(TestMethodInformation.Color.GREEN);
    476             stats
    477                     .setExtra(junitCnt == cnt ? "Ignored since only Junit test and "
    478                             + "suites"
    479                             : "Ignored since \"tests.\" in name - recheck");
    480             stats.ignored = true;
    481         } else {
    482             stats.setExtra("" + cnt);
    483         }
    484         return stats;
    485     }
    486 
    487     private boolean hasHideFlag(Doc doc) {
    488         // workaround for the non-recognized @hide tag in package.html
    489         if (doc instanceof PackageDoc) {
    490             String comment = doc.getRawCommentText();
    491             return comment != null && comment.contains("@hide");
    492         } else {
    493             Tag[] hideTags = doc.tags("hide");
    494             return hideTags.length > 0;
    495         }
    496     }
    497 
    498     private ColorStat processTestClass(ClassDoc clazz) {
    499         String file = getPackageDir("testcoverage", clazz.containingPackage())
    500                 + "/" + clazz.name() + ".html";
    501         PrintWriter pr = openFile(file, "Test class " + clazz.qualifiedName());
    502         TablePrinter printer = new TablePrinter(pr);
    503         ColorStat classStat = new ColorStat(clazz.name(), clazz.name()
    504                 + ".html");
    505 
    506         TestTargetClass testTargetClass = getTargetClass(clazz);
    507         ClassDoc targetClass = testTargetClass.targetClass;
    508 
    509         String note = "Note:";
    510         if (targetClass == null) {
    511             note += "<br>targetClass annotation missing!<br>";
    512         } else {
    513             // add untested[] annotations to statistics
    514             ClassOriginator co = new ClassOriginator(clazz, null);
    515 
    516             AnnotationDesc[] annotsC = testTargetClass.untestedMethods
    517                     .toArray(new AnnotationDesc[] {});
    518             if (annotsC.length > 0) {
    519                 // we have "untested" refs
    520                 ColorStat classLevelStat = new ColorStat(clazz.name(), null);
    521                 TestMethodInformation tminfo = new TestMethodInformation(co,
    522                         annotsC, targetClass);
    523                 if (tminfo.getError() != null) {
    524                     printer.printPlainLn("<b>Error:</b>" + tminfo.getError());
    525                     classLevelStat.incColor(Color.RED);
    526                 } else {
    527                     linkTargets(tminfo.getTargets());
    528                     classLevelStat.incColor(Color.GREEN);
    529                 }
    530                 classStat.add(classLevelStat);
    531             }
    532         }
    533 
    534         printer.printPlainLn(note);
    535 
    536         printer.printTableStart();
    537         printer.printRow("Method", "Note", "Meth", "Unt", "Part", "Compl",
    538                 "Disab", "Broken", "ToBeFixed", "KnownFail");
    539 
    540         int methodCnt = 0;
    541         // also collects test... methods from all superclasses below TestCase,
    542         // since those are called as well
    543         List<MethodDoc> testMethods = collectAllTestMethods(clazz);
    544         Collections.sort(testMethods, memberComparator);
    545 
    546         for (MethodDoc testMethod : testMethods) {
    547             methodCnt++;
    548 
    549             // Make disabled tests visible in the report so we don't forget
    550             // them.
    551             boolean disTest = testMethod.name().startsWith("_test");
    552 
    553             ColorStat methodStat = new ColorStat(testMethod.name(), null);
    554             if (disTest) {
    555                 methodStat.incDisabledTestsCnt();
    556             }
    557             String comments = disTest ? "<b><span style=\"background:red\">"
    558                     + "DISABLED</span></b>" : null;
    559 
    560             MethodOriginator mo = new MethodOriginator(testMethod, clazz,
    561                     comments);
    562             AnnotationDesc[] annots = testMethod.annotations();
    563             TestMethodInformation minfo = new TestMethodInformation(mo, annots,
    564                     targetClass);
    565             linkTargets(minfo.getTargets());
    566 
    567             String extra = null;
    568             if (comments != null) {
    569                 if (extra == null)
    570                     extra = "";
    571                 extra += comments;
    572             }
    573 
    574             if (minfo.getError() != null) { // error case
    575                 if (extra == null)
    576                     extra = "";
    577                 extra += "<b>Error:</b> " + minfo.getError() + "<br>";
    578                 methodStat.addMethodInfo(minfo);
    579             } else {
    580                 if (mo.getKnownFailure() != null) {
    581                     methodStat.incKnownFailureCnt();
    582                     if (extra == null)
    583                         extra = "";
    584                     extra += mo.getKnownFailure();
    585                 }
    586 
    587                 // check for @BrokenTest
    588                 if (mo.getBrokenTest() != null) {
    589                     // override with warning
    590                     methodStat.incBrokenTestCnt();
    591                     methodStat.incColor(Color.YELLOW);
    592                     if (extra == null)
    593                         extra = "";
    594                     extra += mo.getBrokenTest();
    595                 }
    596 
    597                 // check for @ToBeFixed
    598                 if (mo.getToBeFixed() != null) {
    599                     // override with warning
    600                     methodStat.incToBeFixedCnt();
    601                     methodStat.incColor(Color.YELLOW);
    602                     if (extra == null) {
    603                         extra = "";
    604                     }
    605 
    606                     extra += mo.getToBeFixed();
    607                 } else { // add regular stats
    608                     methodStat.addMethodInfo(minfo);
    609                 }
    610             }
    611             if (extra != null) {
    612                 methodStat.setExtra(extra);
    613             }
    614 
    615             printTestStats(printer, methodStat, false);
    616             classStat.add(methodStat);
    617         }
    618         printer.printTableEnd();
    619 
    620         printer.printPlainLn("<h2>Test class summary</h2>");
    621         printer.printTableStart();
    622         printStats(printer, classStat, false);
    623         printer.printTableEnd();
    624 
    625         closeFile(pr);
    626         classStat.setExtra("#methods: " + testMethods.size());
    627         return classStat;
    628     }
    629 
    630     private void linkTargets(List<TestTargetNew> targets) {
    631         for (TestTargetNew ttn : targets) {
    632             if (ttn.getTargetMethod() != null) {
    633                 AnnotationPointer tar = getAnnotationPointer(ttn
    634                         .getTargetMethod(), true);
    635                 tar.addTestTargetNew(ttn);
    636             } else if (ttn.getTargetClass() != null) {
    637                 // some special target only pointing to a class, not a method.
    638                 addToClassTargets(ttn.getTargetClass(), ttn);
    639             }
    640         }
    641     }
    642 
    643     private boolean isGreen(TestMethodInformation.Level level) {
    644         boolean lComplete = level == TestMethodInformation.Level.COMPLETE;
    645         boolean lSufficient = level == TestMethodInformation.Level.SUFFICIENT;
    646         boolean lPartialOk = level == TestMethodInformation.Level.PARTIAL_COMPLETE;
    647         boolean lPartial = level == TestMethodInformation.Level.PARTIAL;
    648         boolean lTodo = level == TestMethodInformation.Level.TODO;
    649         boolean lNotFeasible = level == TestMethodInformation.Level.NOT_FEASIBLE;
    650         boolean lNotNecessary = level == TestMethodInformation.Level.NOT_NECESSARY;
    651 
    652         return lComplete || lPartialOk || lSufficient || lNotFeasible
    653                 || lNotNecessary;
    654     }
    655 
    656     private ColorStat processClass(ClassDoc clazz) {
    657         String file = getPackageDir("", clazz.containingPackage()) + "/"
    658                 + clazz.name() + ".html";
    659         String classDesc = getClassString(clazz);
    660         PrintWriter pr = openFile(file, classDesc);
    661         TablePrinter printer = new TablePrinter(pr);
    662         printer.printPlain("<b>package " + clazz.containingPackage() + "</b>");
    663         ColorStat classStats = new ColorStat(classDesc, clazz.name() + ".html");
    664 
    665         // list special targets
    666         List<TestTargetNew> classTargets = getTargetsFor(clazz);
    667         if (classTargets != null) {
    668             printer.printPlain("<h3>Class level tests</h3>");
    669             printer.printPlain("<ul>");
    670             for (TestTargetNew ttn : classTargets) {
    671                 String line = "<li>" + ttn.getOriginator().asString();
    672                 Level lev = ttn.getLevel();
    673                 line += " <font color=\""
    674                         + (isGreen(lev) ? "green" : "red")
    675                         + "\"><b>"
    676                         + lev.name()
    677                         + "</b></font>"
    678                         + (ttn.getNotes() != null ? "<br>Notes: "
    679                                 + ttn.getNotes() : "") + "</li>";
    680                 printer.printPlain(line);
    681             }
    682             printer.printPlainLn("</ul>");
    683         }
    684 
    685         printer.printPlain("<h3>Method level tests</h3>");
    686         printer.printTableStart();
    687         printer.printRow("Method", "Tested by", "Meth", "Unt", "Part", "Compl",
    688                 "Disab", "Broken", "ToBeFixed", "KnownFail");
    689         ConstructorDoc[] constructors = clazz.constructors();
    690         Arrays.sort(constructors, classComparator);
    691         int cnt = 0;
    692         for (ConstructorDoc constructor : constructors) {
    693             if (!hasHideFlag(constructor) && !hasHideFlag(clazz)) {
    694                 cnt++;
    695                 ColorStat memberStat = processElement(constructor);
    696                 printStats(printer, memberStat, false);
    697                 classStats.add(memberStat);
    698             }
    699         }
    700 
    701         MethodDoc[] methods = clazz.methods();
    702         Arrays.sort(methods, classComparator);
    703         for (MethodDoc method : methods) {
    704             if (!hasHideFlag(method) && !hasHideFlag(clazz)) {
    705                 cnt++;
    706                 ColorStat subStat = processElement(method);
    707                 printStats(printer, subStat, false);
    708                 classStats.add(subStat);
    709             }
    710         }
    711         printer.printTableEnd();
    712 
    713         printer.printPlainLn("<h2>Target class summary</h2>");
    714         printer.printTableStart();
    715         printStats(printer, classStats, false);
    716         printer.printTableEnd();
    717 
    718         closeFile(pr);
    719         classStats.setExtra("#methods: " + cnt);
    720 
    721         // mark as green
    722         if (_ignoreInterfacesAndAbstractMethods && clazz.isInterface()) {
    723             classStats = new ColorStat(clazz.name()
    724                     + (clazz.isInterface() ? " (Interface)" : ""), clazz.name()
    725                     + ".html");
    726             int mcnt = clazz.methods().length;
    727             // fake all methods to green
    728             for (int i = 0; i < mcnt; i++) {
    729                 classStats.incColor(TestMethodInformation.Color.GREEN);
    730             }
    731             classStats.setExtra("Ignored since interface");
    732         }
    733         return classStats;
    734     }
    735 
    736     private class TestTargetClass {
    737         ClassDoc targetClass;
    738 
    739         List<AnnotationDesc> untestedMethods = new ArrayList<AnnotationDesc>();
    740         // a List of @TestTargetNew annotations
    741     }
    742 
    743     private TestTargetClass getTargetClass(ClassDoc classDoc) {
    744         // get the class annotation which names the default test target class
    745         TestTargetClass ttc = new TestTargetClass();
    746         ClassDoc targetClass = null;
    747         AnnotationDesc[] cAnnots = classDoc.annotations();
    748         for (AnnotationDesc cAnnot : cAnnots) {
    749             AnnotationTypeDoc atype = cAnnot.annotationType();
    750             if (atype.toString().equals("dalvik.annotation.TestTargetClass")) {
    751                 ElementValuePair[] cpairs = cAnnot.elementValues();
    752                 for (int i = 0; i < cpairs.length; i++) {
    753                     ElementValuePair ev = cpairs[i];
    754                     String elName = ev.element().name();
    755                     if (elName.equals("value")) {
    756                         // the target class
    757                         AnnotationValue av = ev.value();
    758                         Object obj = av.value();
    759                         // value must be a class doc
    760                         if (obj instanceof ClassDoc) {
    761                             targetClass = (ClassDoc)obj;
    762                         } else if (obj instanceof ParameterizedType) {
    763                             targetClass = ((ParameterizedType)obj).asClassDoc();
    764                         } else
    765                             throw new RuntimeException(
    766                                     "annotation elem value is of type "
    767                                             + obj.getClass().getName());
    768                     } else if (elName.equals("untestedMethods")) {
    769                         // TestTargetNew[] untestedMethods() default {};
    770                         AnnotationValue[] targets = (AnnotationValue[])ev
    771                                 .value().value();
    772                         for (AnnotationValue ttn : targets) {
    773                             // each untested method must be a TestTargetNew
    774                             AnnotationDesc ttnd = (AnnotationDesc)ttn.value();
    775                             ttc.untestedMethods.add(ttnd);
    776                         }
    777                     }
    778                 }
    779             }
    780         }
    781         ttc.targetClass = targetClass;
    782         return ttc;
    783     }
    784 
    785     private List<MethodDoc> collectAllTestMethods(ClassDoc classDoc) {
    786         List<MethodDoc> m = new ArrayList<MethodDoc>();
    787 
    788         ClassDoc curCl = classDoc;
    789         do {
    790             m.addAll(getJunitTestMethods(curCl));
    791         } while ((curCl = curCl.superclass()) != null
    792                 && !curCl.qualifiedName().equals("junit.framework.TestCase"));
    793         return m;
    794     }
    795 
    796     private List<MethodDoc> getJunitTestMethods(ClassDoc classDoc) {
    797         List<MethodDoc> cl = new ArrayList<MethodDoc>();
    798         for (MethodDoc methodDoc : classDoc.methods()) {
    799             if (methodDoc.isPublic()
    800                     && (methodDoc.name().startsWith("test") || methodDoc.name()
    801                             .startsWith("_test"))) {
    802                 cl.add(methodDoc);
    803             }
    804         }
    805         return cl;
    806     }
    807 
    808     private class ColorStat {
    809         private String name;
    810 
    811         private String link;
    812 
    813         private String extra;
    814 
    815         public boolean ignored;
    816 
    817         private int[] cntCol = new int[4];
    818 
    819         private int disabledTestsCnt = 0;
    820 
    821         private int brokenTestCnt = 0;
    822 
    823         private int toBeFixedCnt = 0;
    824 
    825         private int knownFailureCnt = 0;
    826 
    827         public String getName() {
    828             return name;
    829         }
    830 
    831         public String getLink() {
    832             return link;
    833         }
    834 
    835         public ColorStat(String name, String link) {
    836             this.name = name;
    837             this.link = link;
    838         }
    839 
    840         public void add(ColorStat subStat) {
    841             for (int i = 0; i < cntCol.length; i++) {
    842                 cntCol[i] += subStat.cntCol[i];
    843             }
    844             disabledTestsCnt += subStat.disabledTestsCnt;
    845             brokenTestCnt += subStat.brokenTestCnt;
    846             toBeFixedCnt += subStat.toBeFixedCnt;
    847             knownFailureCnt += subStat.knownFailureCnt;
    848         }
    849 
    850         public void incDisabledTestsCnt() {
    851             disabledTestsCnt++;
    852         }
    853 
    854         public void incBrokenTestCnt() {
    855             brokenTestCnt++;
    856         }
    857 
    858         public void incToBeFixedCnt() {
    859             toBeFixedCnt++;
    860         }
    861 
    862         public void incKnownFailureCnt() {
    863             knownFailureCnt++;
    864         }
    865 
    866         public void incColor(TestMethodInformation.Color color) {
    867             cntCol[color.ordinal()]++;
    868         }
    869 
    870         public int getColorCnt(TestMethodInformation.Color color) {
    871             return cntCol[color.ordinal()];
    872         }
    873 
    874         public void addMethodInfo(TestMethodInformation minfo) {
    875             TestMethodInformation.Color c = minfo.getColor();
    876             int ord = c.ordinal();
    877             cntCol[ord]++;
    878         }
    879 
    880         public void setExtra(String extra) {
    881             this.extra = extra;
    882         }
    883 
    884         public String getExtra() {
    885             return extra;
    886         }
    887 
    888         public int getDisabledTestsCnt() {
    889             return disabledTestsCnt;
    890         }
    891 
    892         public int getBrokenTestCnt() {
    893             return brokenTestCnt;
    894         }
    895 
    896         public int getToBeFixedCnt() {
    897             return toBeFixedCnt;
    898         }
    899 
    900         public int getKnownFailureCnt() {
    901             return knownFailureCnt;
    902         }
    903     }
    904 
    905     private AnnotationPointer getAnnotationPointer(
    906             ExecutableMemberDoc targetMethod, boolean create) {
    907         AnnotationPointer ap = resolved.get(targetMethod);
    908         if (create && ap == null) {
    909             ap = new AnnotationPointer(targetMethod);
    910             resolved.put(targetMethod, ap);
    911         }
    912         return ap;
    913     }
    914 
    915     private void addToClassTargets(ClassDoc targetClass, TestTargetNew ttn) {
    916         List<TestTargetNew> targets = classToSpecialTargets.get(targetClass);
    917         if (targets == null) {
    918             targets = new ArrayList<TestTargetNew>();
    919             classToSpecialTargets.put(targetClass, targets);
    920         }
    921         targets.add(ttn);
    922     }
    923 
    924     private List<TestTargetNew> getTargetsFor(ClassDoc targetClass) {
    925         return classToSpecialTargets.get(targetClass);
    926     }
    927 
    928     private boolean extendsJUnitTestCase(ClassDoc classDoc) {
    929         // junit.framework.TestCase.java
    930         ClassDoc curClass = classDoc;
    931         while ((curClass = curClass.superclass()) != null) {
    932             if (curClass.toString().equals("junit.framework.TestCase")) {
    933                 return true;
    934             }
    935         }
    936         return false;
    937     }
    938 
    939     /**
    940      * Processes a single method/constructor.
    941      */
    942     private ColorStat processElement(ExecutableMemberDoc method) {
    943         if (DEBUG) System.out.println("Processing " + method);
    944         ColorStat memberStats = new ColorStat(getMethodString(method), null);
    945         TestMethodInformation.Color c = TestMethodInformation.Color.RED;
    946         // if flagged, consider abstract method ok also without tests
    947         if (_ignoreInterfacesAndAbstractMethods && method instanceof MethodDoc
    948                 && ((MethodDoc)method).isAbstract()) {
    949             c = TestMethodInformation.Color.GREEN;
    950             memberStats.setExtra("ignored since abstract");
    951         } else {
    952             AnnotationPointer ap = getAnnotationPointer(method, false);
    953             int testedByCnt = 0;
    954             if (ap != null) {
    955                 List<TestTargetNew> targets = ap.getTargets();
    956                 testedByCnt = targets.size();
    957                 if (testedByCnt == 0) {
    958                     throw new RuntimeException(
    959                             "existing annotation pointer with no entries!, "
    960                                     + "method:" + method);
    961                 }
    962                 // at least tested by one method
    963                 String by = "<ul>";
    964                 int completeTestCnt = 0;
    965                 int partialOkTestCnt = 0;
    966                 int partialTestCnt = 0;
    967                 int todoTestCnt = 0;
    968                 int notFeasableTestCnt = 0;
    969                 int notNecessaryTestCnt = 0;
    970                 int sufficientTestCnt = 0;
    971                 // int totalCnt = targets.size();
    972 
    973                 for (TestTargetNew target : targets) {
    974                     Originator originator = target.getOriginator();
    975                     boolean disabledTest = originator.isDisabled();
    976                     boolean brokenTest = originator.getBrokenTest() != null;
    977                     boolean toBeFixed = originator.getToBeFixed() != null;
    978                     boolean knownFailure = originator.getKnownFailure() != null;
    979                     by += "<li>" + originator.asString();
    980                     TestMethodInformation.Level lev;
    981                     if (target.isHavingProblems()) {
    982                         lev = TestMethodInformation.Level.TODO;
    983                     } else {
    984                         lev = target.getLevel();
    985                     }
    986                     // TODO: improve: avoid c&p by adding ColorStat.addInfo
    987                     // (originator) or similar
    988                     if (disabledTest) {
    989                         memberStats.incDisabledTestsCnt();
    990                     }
    991                     if (brokenTest) {
    992                         memberStats.incBrokenTestCnt();
    993                     }
    994                     if (toBeFixed) {
    995                         memberStats.incToBeFixedCnt();
    996                     }
    997                     if (knownFailure) {
    998                         memberStats.incKnownFailureCnt();
    999                     }
   1000 
   1001                     boolean lComplete = lev == TestMethodInformation.Level.COMPLETE;
   1002                     boolean lSufficient = lev == TestMethodInformation.Level.SUFFICIENT;
   1003                     boolean lPartialOk = lev == TestMethodInformation.Level.PARTIAL_COMPLETE;
   1004                     boolean lPartial = lev == TestMethodInformation.Level.PARTIAL;
   1005                     boolean lTodo = lev == TestMethodInformation.Level.TODO;
   1006                     boolean lNotFeasible = lev == TestMethodInformation.Level.NOT_FEASIBLE;
   1007                     boolean lNotNecessary = lev == TestMethodInformation.Level.NOT_NECESSARY;
   1008 
   1009                     by += " <font color=\""
   1010                             + (lComplete || lPartialOk || lSufficient
   1011                                     || lNotFeasible || lNotNecessary ? "green"
   1012                                     : "red")
   1013                             + "\"><b>"
   1014                             + lev.name()
   1015                             + "</b></font>"
   1016                             + (target.getNotes() != null ? "<br>Notes: "
   1017                                     + target.getNotes() : "");
   1018 
   1019                     // only count tests when they are either ok (not disabled)
   1020                     // or if the doIncludeDisabledTests flag is set
   1021                     if ((_doIncludeDisabledTests || !disabledTest)
   1022                             && (!brokenTest) && (!toBeFixed)) {
   1023                         if (lComplete) {
   1024                             completeTestCnt++;
   1025                         } else if (lPartialOk) {
   1026                             partialOkTestCnt++;
   1027                         } else if (lPartial) {
   1028                             partialTestCnt++;
   1029                         } else if (lTodo) {
   1030                             todoTestCnt++;
   1031                         } else if (lSufficient) {
   1032                             sufficientTestCnt++;
   1033                         } else if (lNotFeasible) {
   1034                             notFeasableTestCnt++;
   1035                         } else if (lNotNecessary) {
   1036                             notNecessaryTestCnt++;
   1037                         }
   1038                     }
   1039 
   1040                     if (toBeFixed) {
   1041                         partialTestCnt++;
   1042                     }
   1043 
   1044                     if (DEBUG) {
   1045                         System.out.println("completeTestCnt: " + completeTestCnt
   1046                             + ", partialOkTestCnt: " + partialOkTestCnt
   1047                             + ", partialTestCnt: " + partialTestCnt
   1048                             + ", todoTestCnt: " + todoTestCnt
   1049                             + ", sufficientTestCnt: " + sufficientTestCnt
   1050                             + ", notFeasableTestCnt: " + notFeasableTestCnt
   1051                             + ", notNecessaryTestCnt: " + notNecessaryTestCnt);
   1052                     }
   1053                     by += "</li>";
   1054                 }
   1055 
   1056                 String warnings = "";
   1057                 // calculate the color
   1058                 int singularTestCnt = notFeasableTestCnt + notNecessaryTestCnt;
   1059                 boolean isAbstract = (method.containingClass().isInterface() ||
   1060                         (method instanceof MethodDoc) && ((MethodDoc)method).isAbstract());
   1061 
   1062                 if (_acceptCompleteWithOtherStatus
   1063                         && (completeTestCnt > 0 || sufficientTestCnt > 0)) {
   1064                     c = TestMethodInformation.Color.GREEN;
   1065                 } else if (_acceptCompleteWithOtherStatus
   1066                         && (partialOkTestCnt > 1)) {
   1067                     c = TestMethodInformation.Color.GREEN;
   1068                 } else {
   1069                     if (singularTestCnt > 0) {
   1070                         // we have tests which claim not_neccessary or
   1071                         // not_feasible
   1072                         if (targets.size() > singularTestCnt) {
   1073                             // other tests exist
   1074                             c = TestMethodInformation.Color.RED;
   1075                             warnings += "<b>WARNING:</b>NOT_FEASIBLE or "
   1076                                     + "NOT_NECESSARY together with other "
   1077                                     + "status!<br>";
   1078                         } else {
   1079                             // a collection of not_necessary and/or not_feasible
   1080                             if (notNecessaryTestCnt > 0
   1081                                     && notFeasableTestCnt > 0) {
   1082                                 // a blend of both -> error
   1083                                 warnings += "<b>WARNING:</b>both NOT_FEASIBLE "
   1084                                         + "and NOT_NECESSARY together!<br>";
   1085                                 c = TestMethodInformation.Color.RED;
   1086                             } else { // just one sort of tests -> ok and
   1087                                 // sufficent
   1088                                 c = TestMethodInformation.Color.GREEN;
   1089                             }
   1090                         }
   1091                     } else if (todoTestCnt > 0) {
   1092                         c = TestMethodInformation.Color.RED;
   1093                     } else if (partialTestCnt > 0) {
   1094                         // at least one partial test
   1095                         c = TestMethodInformation.Color.YELLOW;
   1096                         if (completeTestCnt > 0 || sufficientTestCnt > 0) {
   1097                             if (_acceptCompleteWithOtherStatus) {
   1098                                 // accept complete even if there are partials
   1099                                 c = TestMethodInformation.Color.GREEN;
   1100                             } else if (completeTestCnt > 0) {
   1101                                 // yellow+warning: mixed PARTIAL_COMPLETE and
   1102                                 // COMPLETE status
   1103                                 warnings += "<b>WARNING</b>: mixed PARTIAL "
   1104                                         + "and COMPLETE status<br>";
   1105                             }
   1106                         }
   1107                     } else if (partialOkTestCnt > 0 || sufficientTestCnt > 0) {
   1108                         // at least one partialOk test and no partial tests
   1109                         c = TestMethodInformation.Color.GREEN;
   1110                         if (partialOkTestCnt == 1) {
   1111                             // yellow: 1 PARTIAL_COMPLETE (we need either zero
   1112                             // or >=2 PARTIAL_OK tests)
   1113                             warnings += "<b>WARNING</b>: only one "
   1114                                     + "PARTIAL_COMPLETE status<br>";
   1115                             c = TestMethodInformation.Color.YELLOW;
   1116                         }
   1117                     } else if (completeTestCnt > 0 || singularTestCnt == 1) {
   1118                         // only complete tests
   1119                         c = TestMethodInformation.Color.GREEN;
   1120                     }
   1121 
   1122                     if (completeTestCnt > 1 && !isAbstract
   1123                             && !_acceptCompleteWithOtherStatus) {
   1124                         // green+warning: >1 COMPLETE (we need either 0 or 1
   1125                         // COMPLETE, more would be strange, but possible)
   1126                         warnings += "<b>WARNING</b>: more than one "
   1127                                 + "COMPLETE status<br>";
   1128                         if (c != TestMethodInformation.Color.RED) {
   1129                             c = TestMethodInformation.Color.YELLOW;
   1130                         }
   1131                     }
   1132                 }
   1133                 by = warnings + by;
   1134                 memberStats.setExtra(by);
   1135             } else { // else this class has no single test that targets the
   1136                 // current method
   1137                 // handle special cases:
   1138 
   1139                 // can be ok if the current method is a default constructor
   1140                 // which does not occur in the source code.
   1141                 if (method.isConstructor() && method.signature().equals("()")) {
   1142                     if (method.position() != null) {
   1143                         // hacky stuff - the doclet should return null for a
   1144                         // default constructor,
   1145                         // but instead returns a source position pointing to the
   1146                         // same location as
   1147                         // the class.
   1148                         String constPos = method.position().toString();
   1149                         String classPos = method.containingClass().position()
   1150                                 .toString();
   1151                         if (constPos.equals(classPos)) {
   1152                             // we have a default constructor not visible in
   1153                             // source code -> mark green, no testing needed
   1154                             c = TestMethodInformation.Color.GREEN;
   1155                             memberStats
   1156                                     .setExtra("automatically marked green "
   1157                                             + "since implicit default "
   1158                                             + "constructor");
   1159                         }
   1160                     } else {
   1161                         // should be called for default constructors according
   1162                         // to the doclet spec, but is never called.
   1163                         // spec:
   1164                         // "A default constructor returns null because it has no
   1165                         // location in the source file"
   1166                         // ??
   1167                         // what about default constructors being in the source
   1168                         // code?
   1169                         System.err.println("warning: doclet returned null for "
   1170                                 + "source position: method:" + method);
   1171                     }
   1172                 } else if (method.containingClass().superclass() != null
   1173                         && method.containingClass().superclass()
   1174                                 .qualifiedName().equals("java.lang.Enum")) {
   1175                     // check enum types
   1176                     // <anyreturnclass> valueOf (java.lang.String) and
   1177                     // <anyreturnclass[]> values()
   1178                     // do not need to be tested, since they are autogenerated
   1179                     // by the compiler
   1180                     String sig = method.name() + method.signature();
   1181                     if (sig.equals("valueOf(java.lang.String)")
   1182                             || sig.equals("values()")) {
   1183                         c = TestMethodInformation.Color.GREEN;
   1184                         memberStats
   1185                                 .setExtra("automatically marked green since "
   1186                                         + "generated by compiler for enums");
   1187                     }
   1188                 }
   1189             }
   1190         }
   1191         memberStats.incColor(c);
   1192         return memberStats;
   1193     }
   1194 
   1195     private String getMethodString(ExecutableMemberDoc method) {
   1196         String methodDesc = (method.isPublic() ? "public " : method
   1197                 .isProtected() ? "protected " : method.isPrivate() ? "private "
   1198                 : "");
   1199 
   1200         return methodDesc + "<b>" + method.name() + "</b> "
   1201                 + method.signature();
   1202     }
   1203 
   1204     private String getClassString(ClassDoc clazz) {
   1205         return (clazz.isPublic() ? "public "
   1206                 : clazz.isProtected() ? "protected "
   1207                         : clazz.isPrivate() ? "private " : "")
   1208                 + (clazz.isInterface() ? "interface" : "class")
   1209                 + " "
   1210                 + clazz.name();
   1211     }
   1212 
   1213     private void printTestStats(TablePrinter printer, ColorStat stat,
   1214             boolean wantLink) {
   1215         printStats(printer, stat, wantLink);
   1216     }
   1217 
   1218     private void printStats(TablePrinter printer, ColorStat stat,
   1219             boolean wantLink) {
   1220         int redCnt = stat.getColorCnt(TestMethodInformation.Color.RED);
   1221         int yellowCnt = stat.getColorCnt(TestMethodInformation.Color.YELLOW);
   1222         int greenCnt = stat.getColorCnt(TestMethodInformation.Color.GREEN);
   1223         int disabledCnt = stat.getDisabledTestsCnt();
   1224         int brokenTestCnt = stat.getBrokenTestCnt();
   1225         int toBeFixedCnt = stat.getToBeFixedCnt();
   1226         int knownFailureCnt = stat.getKnownFailureCnt();
   1227 
   1228         int total = redCnt + yellowCnt + greenCnt;
   1229 
   1230         String link = stat.getLink();
   1231         String namePart;
   1232         if (wantLink && link != null) {
   1233             namePart = "<a href=\"" + link + "\">" + stat.getName() + "</a>";
   1234         } else {
   1235             namePart = stat.getName();
   1236         }
   1237 
   1238         String extra = stat.getExtra() == null ? "" : stat.getExtra();
   1239 
   1240         int totalDots = 120;
   1241 
   1242         float toP = total == 0 ? 0 : (((float)totalDots) / total);
   1243 
   1244         int redD = (int)(toP * redCnt);
   1245         if (redD == 0 && redCnt > 0) {
   1246             // never let red cut to zero;
   1247             redD = 1;
   1248         }
   1249         int yellowD = (int)(toP * yellowCnt);
   1250         if (yellowD == 0 && yellowCnt > 0) {
   1251             yellowD = 1;
   1252         }
   1253         int greenD = totalDots - redD - yellowD; // (int)(toP*greenCnt);
   1254 
   1255         printer.printRow(namePart, extra, "" + total, "" + redCnt, ""
   1256                 + yellowCnt, "" + greenCnt, "" + disabledCnt, ""
   1257                 + brokenTestCnt, "" + toBeFixedCnt, "" + knownFailureCnt, ""
   1258                 + (redCnt == 0 ? "" : "<span style=\"background:"
   1259                         + COLORS[TestMethodInformation.Color.RED.ordinal()]
   1260                         + "\">" + getDots(redD) + "</span>")
   1261                 + (yellowCnt == 0 ? "" : "<span style=\"background:"
   1262                         + COLORS[TestMethodInformation.Color.YELLOW.ordinal()]
   1263                         + "\">" + getDots(yellowD) + "</span>")
   1264                 + (greenCnt == 0 && total > 0 ? ""
   1265                         : "<span style=\"background:"
   1266                                 + COLORS[TestMethodInformation.Color.GREEN
   1267                                         .ordinal()] + "\">" + getDots(greenD)
   1268                                 + "</span>")
   1269                 + "&nbsp;&nbsp;&nbsp;<span style=\"background:blue\">"
   1270                 + getDots(total / 10) + "</span>");
   1271     }
   1272 
   1273     private String getDots(int cnt) {
   1274         StringBuffer sb = new StringBuffer();
   1275         for (int i = 0; i < cnt; i++) {
   1276             sb.append("&nbsp;");
   1277         }
   1278         return sb.toString();
   1279     }
   1280 
   1281     /**
   1282      * Returns the directory for a given package. Basically converts embedded
   1283      * dots in the name into slashes.
   1284      */
   1285 
   1286     private String getPackageBaseLink(PackageDoc pack) {
   1287         return pack.name().replace('.', '/');
   1288     }
   1289 
   1290     private File getPackageDir(String prefix, PackageDoc pack) {
   1291         if (pack == null || pack.name() == null || "".equals(pack.name())) {
   1292             return new File(prefix + "/" + ".");
   1293         } else {
   1294             return new File(prefix + "/" + pack.name().replace('.', '/'));
   1295         }
   1296     }
   1297 
   1298     /**
   1299      * Called by JavaDoc to find our which command line arguments are supported
   1300      * and how many parameters they take. Part of the JavaDoc API.
   1301      *
   1302      * @param option the options
   1303      * @return an int
   1304      */
   1305     public static int optionLength(String option) {
   1306         if ("-d".equals(option)) {
   1307             return 2;
   1308         }
   1309         if ("-f".equals(option)) {
   1310             return 2;
   1311         } else {
   1312             return 0;
   1313         }
   1314     }
   1315 
   1316     /**
   1317      * Called by JavaDoc to query a specific command line argument. Part of the
   1318      * JavaDoc API.
   1319      */
   1320     private static String getOption(RootDoc root, String option, int index,
   1321             String defValue) {
   1322         String[][] allOptions = root.options();
   1323         for (int i = 0; i < allOptions.length; i++) {
   1324             if (allOptions[i][0].equals(option)) {
   1325                 return allOptions[i][index];
   1326             }
   1327         }
   1328         return defValue;
   1329     }
   1330 
   1331     /**
   1332      * Called by JavaDoc to find out which Java version we claim to support.
   1333      * Part of the JavaDoc API.
   1334      *
   1335      * @return the version of the language
   1336      */
   1337     public static LanguageVersion languageVersion() {
   1338         return LanguageVersion.JAVA_1_5;
   1339     }
   1340 
   1341     /**
   1342      * The main entry point called by JavaDoc after all required information has
   1343      * been collected. Part of the JavaDoc API.
   1344      *
   1345      * @param root
   1346      * @return whether an error occurred
   1347      */
   1348     public static boolean start(RootDoc root) {
   1349         try {
   1350             String target = getOption(root, "-d", 1, ".");
   1351             TestCoverageDoclet doclet = new TestCoverageDoclet(target);
   1352             doclet.process(root);
   1353 
   1354             File file = new File(target, "index.html");
   1355             System.out.println("Please see complete report in " +
   1356                     file.getAbsolutePath());
   1357 
   1358         } catch (Exception ex) {
   1359             ex.printStackTrace();
   1360             return false;
   1361         }
   1362         return true;
   1363     }
   1364 
   1365 }
   1366