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