• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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> {
compare(Doc elem1, Doc elem2)85         public int compare(Doc elem1, Doc elem2) {
86             return elem1.name().compareTo(elem2.name());
87         }
88 
equals(Doc elem)89         public boolean equals(Doc elem) {
90             return this == elem;
91         }
92     }
93 
94     private class MemberComparator implements Comparator<ExecutableMemberDoc> {
compare(ExecutableMemberDoc mem1, ExecutableMemberDoc mem2)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      */
TestCoverageDoclet(String directory)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      */
openFile(String filename, String title)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      */
closeFile(PrintWriter printer)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 
TablePrinter(PrintWriter pr)166         public TablePrinter(PrintWriter pr) {
167             this.pr = pr;
168         }
169 
printRow(String... columns)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 
printPlain(String val)178         public void printPlain(String val) {
179             pr.print(val);
180         }
181 
printTableStart()182         public void printTableStart() {
183             pr.print("<table>");
184         }
185 
printTableEnd()186         public void printTableEnd() {
187             pr.print("</table>");
188         }
189 
printPlainLn(String val)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      */
process(RootDoc root)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      */
implementingMethods(MethodDoc doc)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      */
implementedMethod0(List<MethodDoc> resultmethods, MethodDoc doc, ClassDoc testClass, boolean testMethods)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 
processTestPackage(PackageDoc packageDoc)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 
processPackage(PackageDoc packageDoc)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 
hasHideFlag(Doc doc)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 
processTestClass(ClassDoc clazz)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 
linkTargets(List<TestTargetNew> targets)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 
isGreen(TestMethodInformation.Level level)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 
processClass(ClassDoc clazz)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 
getTargetClass(ClassDoc classDoc)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 
collectAllTestMethods(ClassDoc classDoc)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 
getJunitTestMethods(ClassDoc classDoc)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 
getName()827         public String getName() {
828             return name;
829         }
830 
getLink()831         public String getLink() {
832             return link;
833         }
834 
ColorStat(String name, String link)835         public ColorStat(String name, String link) {
836             this.name = name;
837             this.link = link;
838         }
839 
add(ColorStat subStat)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 
incDisabledTestsCnt()850         public void incDisabledTestsCnt() {
851             disabledTestsCnt++;
852         }
853 
incBrokenTestCnt()854         public void incBrokenTestCnt() {
855             brokenTestCnt++;
856         }
857 
incToBeFixedCnt()858         public void incToBeFixedCnt() {
859             toBeFixedCnt++;
860         }
861 
incKnownFailureCnt()862         public void incKnownFailureCnt() {
863             knownFailureCnt++;
864         }
865 
incColor(TestMethodInformation.Color color)866         public void incColor(TestMethodInformation.Color color) {
867             cntCol[color.ordinal()]++;
868         }
869 
getColorCnt(TestMethodInformation.Color color)870         public int getColorCnt(TestMethodInformation.Color color) {
871             return cntCol[color.ordinal()];
872         }
873 
addMethodInfo(TestMethodInformation minfo)874         public void addMethodInfo(TestMethodInformation minfo) {
875             TestMethodInformation.Color c = minfo.getColor();
876             int ord = c.ordinal();
877             cntCol[ord]++;
878         }
879 
setExtra(String extra)880         public void setExtra(String extra) {
881             this.extra = extra;
882         }
883 
getExtra()884         public String getExtra() {
885             return extra;
886         }
887 
getDisabledTestsCnt()888         public int getDisabledTestsCnt() {
889             return disabledTestsCnt;
890         }
891 
getBrokenTestCnt()892         public int getBrokenTestCnt() {
893             return brokenTestCnt;
894         }
895 
getToBeFixedCnt()896         public int getToBeFixedCnt() {
897             return toBeFixedCnt;
898         }
899 
getKnownFailureCnt()900         public int getKnownFailureCnt() {
901             return knownFailureCnt;
902         }
903     }
904 
getAnnotationPointer( ExecutableMemberDoc targetMethod, boolean create)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 
addToClassTargets(ClassDoc targetClass, TestTargetNew ttn)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 
getTargetsFor(ClassDoc targetClass)924     private List<TestTargetNew> getTargetsFor(ClassDoc targetClass) {
925         return classToSpecialTargets.get(targetClass);
926     }
927 
extendsJUnitTestCase(ClassDoc classDoc)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      */
processElement(ExecutableMemberDoc method)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 
getMethodString(ExecutableMemberDoc method)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 
getClassString(ClassDoc clazz)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 
printTestStats(TablePrinter printer, ColorStat stat, boolean wantLink)1213     private void printTestStats(TablePrinter printer, ColorStat stat,
1214             boolean wantLink) {
1215         printStats(printer, stat, wantLink);
1216     }
1217 
printStats(TablePrinter printer, ColorStat stat, boolean wantLink)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 
getDots(int cnt)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 
getPackageBaseLink(PackageDoc pack)1286     private String getPackageBaseLink(PackageDoc pack) {
1287         return pack.name().replace('.', '/');
1288     }
1289 
getPackageDir(String prefix, PackageDoc pack)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      */
optionLength(String option)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      */
getOption(RootDoc root, String option, int index, String defValue)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      */
languageVersion()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      */
start(RootDoc root)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