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