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