001 /* 002 * Cobertura - http://cobertura.sourceforge.net/ 003 * 004 * Copyright (C) 2005 Mark Doliner 005 * Copyright (C) 2005 Grzegorz Lukasik 006 * Copyright (C) 2005 Jeremy Thomerson 007 * Copyright (C) 2006 Naoki Iwami 008 * 009 * Cobertura is free software; you can redistribute it and/or modify 010 * it under the terms of the GNU General Public License as published 011 * by the Free Software Foundation; either version 2 of the License, 012 * or (at your option) any later version. 013 * 014 * Cobertura is distributed in the hope that it will be useful, but 015 * WITHOUT ANY WARRANTY; without even the implied warranty of 016 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 017 * General Public License for more details. 018 * 019 * You should have received a copy of the GNU General Public License 020 * along with Cobertura; if not, write to the Free Software 021 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 022 * USA 023 */ 024 025 package net.sourceforge.cobertura.reporting.html; 026 027 import java.io.BufferedReader; 028 import java.io.File; 029 import java.io.FileInputStream; 030 import java.io.FileNotFoundException; 031 import java.io.IOException; 032 import java.io.InputStreamReader; 033 import java.io.PrintWriter; 034 import java.io.UnsupportedEncodingException; 035 import java.text.DateFormat; 036 import java.text.DecimalFormat; 037 import java.text.NumberFormat; 038 import java.util.Collection; 039 import java.util.Collections; 040 import java.util.Date; 041 import java.util.Iterator; 042 import java.util.SortedSet; 043 import java.util.TreeSet; 044 import java.util.Vector; 045 046 import net.sourceforge.cobertura.coveragedata.ClassData; 047 import net.sourceforge.cobertura.coveragedata.CoverageData; 048 import net.sourceforge.cobertura.coveragedata.LineData; 049 import net.sourceforge.cobertura.coveragedata.PackageData; 050 import net.sourceforge.cobertura.coveragedata.ProjectData; 051 import net.sourceforge.cobertura.coveragedata.SourceFileData; 052 import net.sourceforge.cobertura.reporting.ComplexityCalculator; 053 import net.sourceforge.cobertura.reporting.html.files.CopyFiles; 054 import net.sourceforge.cobertura.util.FileFinder; 055 import net.sourceforge.cobertura.util.Header; 056 import net.sourceforge.cobertura.util.IOUtil; 057 import net.sourceforge.cobertura.util.StringUtil; 058 059 import org.apache.log4j.Logger; 060 061 public class HTMLReport 062 { 063 064 private static final Logger LOGGER = Logger.getLogger(HTMLReport.class); 065 066 private File destinationDir; 067 068 private FileFinder finder; 069 070 private ComplexityCalculator complexity; 071 072 private ProjectData projectData; 073 074 /** 075 * Create a coverage report 076 */ 077 public HTMLReport(ProjectData projectData, File outputDir, 078 FileFinder finder, ComplexityCalculator complexity) 079 throws Exception 080 { 081 this.destinationDir = outputDir; 082 this.finder = finder; 083 this.complexity = complexity; 084 this.projectData = projectData; 085 086 CopyFiles.copy(outputDir); 087 generatePackageList(); 088 generateSourceFileLists(); 089 generateOverviews(); 090 generateSourceFiles(); 091 } 092 093 private String generatePackageName(PackageData packageData) 094 { 095 if (packageData.getName().equals("")) 096 return "(default)"; 097 return packageData.getName(); 098 } 099 100 private void generatePackageList() throws IOException 101 { 102 File file = new File(destinationDir, "frame-packages.html"); 103 PrintWriter out = null; 104 105 try 106 { 107 out = IOUtil.getPrintWriter(file); 108 109 out 110 .println("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\""); 111 out 112 .println(" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">"); 113 114 out 115 .println("<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">"); 116 out.println("<head>"); 117 out 118 .println("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />"); 119 out.println("<title>Coverage Report</title>"); 120 out 121 .println("<link title=\"Style\" type=\"text/css\" rel=\"stylesheet\" href=\"css/main.css\" />"); 122 out.println("</head>"); 123 out.println("<body>"); 124 out.println("<h5>Packages</h5>"); 125 out.println("<table width=\"100%\">"); 126 out.println("<tr>"); 127 out 128 .println("<td nowrap=\"nowrap\"><a href=\"frame-summary.html\" onclick='parent.sourceFileList.location.href=\"frame-sourcefiles.html\"' target=\"summary\">All</a></td>"); 129 out.println("</tr>"); 130 131 Iterator iter = projectData.getPackages().iterator(); 132 while (iter.hasNext()) 133 { 134 PackageData packageData = (PackageData)iter.next(); 135 String url1 = "frame-summary-" + packageData.getName() 136 + ".html"; 137 String url2 = "frame-sourcefiles-" + packageData.getName() 138 + ".html"; 139 out.println("<tr>"); 140 out.println("<td nowrap=\"nowrap\"><a href=\"" + url1 141 + "\" onclick='parent.sourceFileList.location.href=\"" 142 + url2 + "\"' target=\"summary\">" 143 + generatePackageName(packageData) + "</a></td>"); 144 out.println("</tr>"); 145 } 146 out.println("</table>"); 147 out.println("</body>"); 148 out.println("</html>"); 149 } 150 finally 151 { 152 if (out != null) 153 { 154 out.close(); 155 } 156 } 157 } 158 159 private void generateSourceFileLists() throws IOException 160 { 161 generateSourceFileList(null); 162 Iterator iter = projectData.getPackages().iterator(); 163 while (iter.hasNext()) 164 { 165 PackageData packageData = (PackageData)iter.next(); 166 generateSourceFileList(packageData); 167 } 168 } 169 170 private void generateSourceFileList(PackageData packageData) 171 throws IOException 172 { 173 String filename; 174 Collection sourceFiles; 175 if (packageData == null) 176 { 177 filename = "frame-sourcefiles.html"; 178 sourceFiles = projectData.getSourceFiles(); 179 } 180 else 181 { 182 filename = "frame-sourcefiles-" + packageData.getName() + ".html"; 183 sourceFiles = packageData.getSourceFiles(); 184 } 185 186 // sourceFiles may be sorted, but if so it's sorted by 187 // the full path to the file, and we only want to sort 188 // based on the file's basename. 189 Vector sortedSourceFiles = new Vector(); 190 sortedSourceFiles.addAll(sourceFiles); 191 Collections.sort(sortedSourceFiles, 192 new SourceFileDataBaseNameComparator()); 193 194 File file = new File(destinationDir, filename); 195 PrintWriter out = null; 196 try 197 { 198 out = IOUtil.getPrintWriter(file); 199 200 out 201 .println("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\""); 202 out 203 .println(" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">"); 204 205 out.println("<html>"); 206 out.println("<head>"); 207 out 208 .println("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"/>"); 209 out.println("<title>Coverage Report Classes</title>"); 210 out 211 .println("<link title=\"Style\" type=\"text/css\" rel=\"stylesheet\" href=\"css/main.css\"/>"); 212 out.println("</head>"); 213 out.println("<body>"); 214 out.println("<h5>"); 215 out.println(packageData == null ? "All Packages" 216 : generatePackageName(packageData)); 217 out.println("</h5>"); 218 out.println("<div class=\"separator\"> </div>"); 219 out.println("<h5>Classes</h5>"); 220 if (!sortedSourceFiles.isEmpty()) 221 { 222 out.println("<table width=\"100%\">"); 223 out.println("<tbody>"); 224 225 for (Iterator iter = sortedSourceFiles.iterator(); iter 226 .hasNext();) 227 { 228 SourceFileData sourceFileData = (SourceFileData)iter.next(); 229 out.println("<tr>"); 230 String percentCovered; 231 if (sourceFileData.getNumberOfValidLines() > 0) 232 percentCovered = getPercentValue(sourceFileData 233 .getLineCoverageRate()); 234 else 235 percentCovered = "N/A"; 236 out 237 .println("<td nowrap=\"nowrap\"><a target=\"summary\" href=\"" 238 + sourceFileData.getNormalizedName() 239 + ".html\">" 240 + sourceFileData.getBaseName() 241 + "</a> <i>(" 242 + percentCovered 243 + ")</i></td>"); 244 out.println("</tr>"); 245 } 246 out.println("</tbody>"); 247 out.println("</table>"); 248 } 249 250 out.println("</body>"); 251 out.println("</html>"); 252 } 253 finally 254 { 255 if (out != null) 256 { 257 out.close(); 258 } 259 } 260 } 261 262 private void generateOverviews() throws IOException 263 { 264 generateOverview(null); 265 Iterator iter = projectData.getPackages().iterator(); 266 while (iter.hasNext()) 267 { 268 PackageData packageData = (PackageData)iter.next(); 269 generateOverview(packageData); 270 } 271 } 272 273 private void generateOverview(PackageData packageData) throws IOException 274 { 275 Iterator iter; 276 277 String filename; 278 if (packageData == null) 279 { 280 filename = "frame-summary.html"; 281 } 282 else 283 { 284 filename = "frame-summary-" + packageData.getName() + ".html"; 285 } 286 File file = new File(destinationDir, filename); 287 PrintWriter out = null; 288 289 try 290 { 291 out = IOUtil.getPrintWriter(file);; 292 293 out 294 .println("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\""); 295 out 296 .println(" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">"); 297 298 out.println("<html>"); 299 out.println("<head>"); 300 out 301 .println("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"/>"); 302 out.println("<title>Coverage Report</title>"); 303 out 304 .println("<link title=\"Style\" type=\"text/css\" rel=\"stylesheet\" href=\"css/main.css\"/>"); 305 out 306 .println("<link title=\"Style\" type=\"text/css\" rel=\"stylesheet\" href=\"css/sortabletable.css\"/>"); 307 out 308 .println("<script type=\"text/javascript\" src=\"js/popup.js\"></script>"); 309 out 310 .println("<script type=\"text/javascript\" src=\"js/sortabletable.js\"></script>"); 311 out 312 .println("<script type=\"text/javascript\" src=\"js/customsorttypes.js\"></script>"); 313 out.println("</head>"); 314 out.println("<body>"); 315 316 out.print("<h5>Coverage Report - "); 317 out.print(packageData == null ? "All Packages" 318 : generatePackageName(packageData)); 319 out.println("</h5>"); 320 out.println("<div class=\"separator\"> </div>"); 321 out.println("<table class=\"report\" id=\"packageResults\">"); 322 out.println(generateTableHeader("Package", true)); 323 out.println("<tbody>"); 324 325 SortedSet packages; 326 if (packageData == null) 327 { 328 // Output a summary line for all packages 329 out.println(generateTableRowForTotal()); 330 331 // Get packages 332 packages = projectData.getPackages(); 333 } 334 else 335 { 336 // Get subpackages 337 packages = projectData.getSubPackages(packageData.getName()); 338 } 339 340 // Output a line for each package or subpackage 341 iter = packages.iterator(); 342 while (iter.hasNext()) 343 { 344 PackageData subPackageData = (PackageData)iter.next(); 345 out.println(generateTableRowForPackage(subPackageData)); 346 } 347 348 out.println("</tbody>"); 349 out.println("</table>"); 350 out.println("<script type=\"text/javascript\">"); 351 out 352 .println("var packageTable = new SortableTable(document.getElementById(\"packageResults\"),"); 353 out 354 .println(" [\"String\", \"Number\", \"Percentage\", \"Percentage\", \"FormattedNumber\"]);"); 355 out.println("packageTable.sort(0);"); 356 out.println("</script>"); 357 358 // Get the list of source files in this package 359 Collection sourceFiles; 360 if (packageData == null) 361 { 362 PackageData defaultPackage = (PackageData)projectData 363 .getChild(""); 364 if (defaultPackage != null) 365 { 366 sourceFiles = defaultPackage.getSourceFiles(); 367 } 368 else 369 { 370 sourceFiles = new TreeSet(); 371 } 372 } 373 else 374 { 375 sourceFiles = packageData.getSourceFiles(); 376 } 377 378 // Output a line for each source file 379 if (sourceFiles.size() > 0) 380 { 381 out.println("<div class=\"separator\"> </div>"); 382 out.println("<table class=\"report\" id=\"classResults\">"); 383 out.println(generateTableHeader("Classes in this Package", 384 false)); 385 out.println("<tbody>"); 386 387 iter = sourceFiles.iterator(); 388 while (iter.hasNext()) 389 { 390 SourceFileData sourceFileData = (SourceFileData)iter.next(); 391 out.println(generateTableRowsForSourceFile(sourceFileData)); 392 } 393 394 out.println("</tbody>"); 395 out.println("</table>"); 396 out.println("<script type=\"text/javascript\">"); 397 out 398 .println("var classTable = new SortableTable(document.getElementById(\"classResults\"),"); 399 out 400 .println(" [\"String\", \"Percentage\", \"Percentage\", \"FormattedNumber\"]);"); 401 out.println("classTable.sort(0);"); 402 out.println("</script>"); 403 } 404 405 out.println(generateFooter()); 406 407 out.println("</body>"); 408 out.println("</html>"); 409 } 410 finally 411 { 412 if (out != null) 413 { 414 out.close(); 415 } 416 } 417 } 418 419 private void generateSourceFiles() 420 { 421 Iterator iter = projectData.getSourceFiles().iterator(); 422 while (iter.hasNext()) 423 { 424 SourceFileData sourceFileData = (SourceFileData)iter.next(); 425 try 426 { 427 generateSourceFile(sourceFileData); 428 } 429 catch (IOException e) 430 { 431 LOGGER.info("Could not generate HTML file for source file " 432 + sourceFileData.getName() + ": " 433 + e.getLocalizedMessage()); 434 } 435 } 436 } 437 438 private void generateSourceFile(SourceFileData sourceFileData) 439 throws IOException 440 { 441 if (!sourceFileData.containsInstrumentationInfo()) 442 { 443 LOGGER.info("Data file does not contain instrumentation " 444 + "information for the file " + sourceFileData.getName() 445 + ". Ensure this class was instrumented, and this " 446 + "data file contains the instrumentation information."); 447 } 448 449 String filename = sourceFileData.getNormalizedName() + ".html"; 450 File file = new File(destinationDir, filename); 451 PrintWriter out = null; 452 453 try 454 { 455 out = IOUtil.getPrintWriter(file); 456 457 out 458 .println("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\""); 459 out 460 .println(" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">"); 461 462 out.println("<html>"); 463 out.println("<head>"); 464 out 465 .println("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"/>"); 466 out.println("<title>Coverage Report</title>"); 467 out 468 .println("<link title=\"Style\" type=\"text/css\" rel=\"stylesheet\" href=\"css/main.css\"/>"); 469 out 470 .println("<script type=\"text/javascript\" src=\"js/popup.js\"></script>"); 471 out.println("</head>"); 472 out.println("<body>"); 473 out.print("<h5>Coverage Report - "); 474 String classPackageName = sourceFileData.getPackageName(); 475 if ((classPackageName != null) && classPackageName.length() > 0) 476 { 477 out.print(classPackageName + "."); 478 } 479 out.print(sourceFileData.getBaseName()); 480 out.println("</h5>"); 481 482 // Output the coverage summary for this class 483 out.println("<div class=\"separator\"> </div>"); 484 out.println("<table class=\"report\">"); 485 out.println(generateTableHeader("Classes in this File", false)); 486 out.println(generateTableRowsForSourceFile(sourceFileData)); 487 out.println("</table>"); 488 489 // Output the coverage summary for methods in this class 490 // TODO 491 492 // Output this class's source code with syntax and coverage highlighting 493 out.println("<div class=\"separator\"> </div>"); 494 out.println(generateHtmlizedJavaSource(sourceFileData)); 495 496 out.println(generateFooter()); 497 498 out.println("</body>"); 499 out.println("</html>"); 500 } 501 finally 502 { 503 if (out != null) 504 { 505 out.close(); 506 } 507 } 508 } 509 510 private String generateBranchInfo(LineData lineData, String content) { 511 boolean hasBranch = (lineData != null) ? lineData.hasBranch() : false; 512 if (hasBranch) 513 { 514 StringBuffer ret = new StringBuffer(); 515 ret.append("<a title=\"Line ").append(lineData.getLineNumber()).append(": Conditional coverage ") 516 .append(lineData.getConditionCoverage()); 517 if (lineData.getConditionSize() > 1) 518 { 519 ret.append(" [each condition: "); 520 for (int i = 0; i < lineData.getConditionSize(); i++) 521 { 522 if (i > 0) 523 ret.append(", "); 524 ret.append(lineData.getConditionCoverage(i)); 525 } 526 ret.append("]"); 527 } 528 ret.append(".\">").append(content).append("</a>"); 529 return ret.toString(); 530 } 531 else 532 { 533 return content; 534 } 535 } 536 537 private String generateHtmlizedJavaSource(SourceFileData sourceFileData) 538 { 539 File sourceFile = null; 540 try 541 { 542 sourceFile = finder.getFileForSource(sourceFileData.getName()); 543 } 544 catch (IOException e) 545 { 546 return "<p>Unable to locate " + sourceFileData.getName() 547 + ". Have you specified the source directory?</p>"; 548 } 549 550 BufferedReader br = null; 551 try 552 { 553 br = new BufferedReader(new InputStreamReader(new FileInputStream(sourceFile), "UTF-8")); 554 } 555 catch (UnsupportedEncodingException e) 556 { 557 return "<p>Unable to open " + sourceFile.getAbsolutePath() 558 + ": The encoding 'UTF-8' is not supported by your JVM.</p>"; 559 } 560 catch (FileNotFoundException e) 561 { 562 return "<p>Unable to open " + sourceFile.getAbsolutePath() + "</p>"; 563 } 564 565 StringBuffer ret = new StringBuffer(); 566 ret 567 .append("<table cellspacing=\"0\" cellpadding=\"0\" class=\"src\">\n"); 568 try 569 { 570 String lineStr; 571 JavaToHtml javaToHtml = new JavaToHtml(); 572 int lineNumber = 1; 573 while ((lineStr = br.readLine()) != null) 574 { 575 ret.append("<tr>"); 576 if (sourceFileData.isValidSourceLineNumber(lineNumber)) 577 { 578 LineData lineData = sourceFileData.getLineCoverage(lineNumber); 579 ret.append(" <td class=\"numLineCover\"> " 580 + lineNumber + "</td>"); 581 if ((lineData != null) && (lineData.isCovered())) 582 { 583 ret.append(" <td class=\"nbHitsCovered\">" 584 + generateBranchInfo(lineData, " " + ((lineData != null) ? lineData.getHits() : 0)) 585 + "</td>"); 586 ret 587 .append(" <td class=\"src\"><pre class=\"src\"> " 588 + generateBranchInfo(lineData, javaToHtml.process(lineStr)) 589 + "</pre></td>"); 590 } 591 else 592 { 593 ret.append(" <td class=\"nbHitsUncovered\">" 594 + generateBranchInfo(lineData, " " + ((lineData != null) ? lineData.getHits() : 0)) 595 + "</td>"); 596 ret 597 .append(" <td class=\"src\"><pre class=\"src\"><span class=\"srcUncovered\"> " 598 + generateBranchInfo(lineData, javaToHtml.process(lineStr)) 599 + "</span></pre></td>"); 600 } 601 } 602 else 603 { 604 ret.append(" <td class=\"numLine\"> " + lineNumber 605 + "</td>"); 606 ret.append(" <td class=\"nbHits\"> </td>\n"); 607 ret.append(" <td class=\"src\"><pre class=\"src\"> " 608 + javaToHtml.process(lineStr) + "</pre></td>"); 609 } 610 ret.append("</tr>\n"); 611 lineNumber++; 612 } 613 } 614 catch (IOException e) 615 { 616 ret.append("<tr><td>Error reading from file " 617 + sourceFile.getAbsolutePath() + ": " 618 + e.getLocalizedMessage() + "</td></tr>\n"); 619 } 620 finally 621 { 622 try 623 { 624 br.close(); 625 } 626 catch (IOException e) 627 { 628 } 629 } 630 631 ret.append("</table>\n"); 632 633 return ret.toString(); 634 } 635 636 private static String generateFooter() 637 { 638 return "<div class=\"footer\">Report generated by " 639 + "<a href=\"http://cobertura.sourceforge.net/\" target=\"_top\">Cobertura</a> " 640 + Header.version() + " on " 641 + DateFormat.getInstance().format(new Date()) + ".</div>"; 642 } 643 644 private static String generateTableHeader(String title, 645 boolean showColumnForNumberOfClasses) 646 { 647 StringBuffer ret = new StringBuffer(); 648 ret.append("<thead>"); 649 ret.append("<tr>"); 650 ret.append(" <td class=\"heading\">" + title + "</td>"); 651 if (showColumnForNumberOfClasses) 652 { 653 ret.append(" <td class=\"heading\"># Classes</td>"); 654 } 655 ret.append(" <td class=\"heading\">" 656 + generateHelpURL("Line Coverage", 657 "The percent of lines executed by this test run.") 658 + "</td>"); 659 ret.append(" <td class=\"heading\">" 660 + generateHelpURL("Branch Coverage", 661 "The percent of branches executed by this test run.") 662 + "</td>"); 663 ret 664 .append(" <td class=\"heading\">" 665 + generateHelpURL( 666 "Complexity", 667 "Average McCabe's cyclomatic code complexity for all methods. This is basically a count of the number of different code paths in a method (incremented by 1 for each if statement, while loop, etc.)") 668 + "</td>"); 669 ret.append("</tr>"); 670 ret.append("</thead>"); 671 return ret.toString(); 672 } 673 674 private static String generateHelpURL(String text, String description) 675 { 676 StringBuffer ret = new StringBuffer(); 677 boolean popupTooltips = false; 678 if (popupTooltips) 679 { 680 ret 681 .append("<a class=\"hastooltip\" href=\"help.html\" onclick=\"popupwindow('help.html'); return false;\">"); 682 ret.append(text); 683 ret.append("<span>" + description + "</span>"); 684 ret.append("</a>"); 685 } 686 else 687 { 688 ret 689 .append("<a class=\"dfn\" href=\"help.html\" onclick=\"popupwindow('help.html'); return false;\">"); 690 ret.append(text); 691 ret.append("</a>"); 692 } 693 return ret.toString(); 694 } 695 696 private String generateTableRowForTotal() 697 { 698 StringBuffer ret = new StringBuffer(); 699 double ccn = complexity.getCCNForProject(projectData); 700 701 ret.append(" <tr>"); 702 ret.append("<td><b>All Packages</b></td>"); 703 ret.append("<td class=\"value\">" 704 + projectData.getNumberOfSourceFiles() + "</td>"); 705 ret.append(generateTableColumnsFromData(projectData, ccn)); 706 ret.append("</tr>"); 707 return ret.toString(); 708 } 709 710 private String generateTableRowForPackage(PackageData packageData) 711 { 712 StringBuffer ret = new StringBuffer(); 713 String url1 = "frame-summary-" + packageData.getName() + ".html"; 714 String url2 = "frame-sourcefiles-" + packageData.getName() + ".html"; 715 double ccn = complexity.getCCNForPackage(packageData); 716 717 ret.append(" <tr>"); 718 ret.append("<td><a href=\"" + url1 719 + "\" onclick='parent.sourceFileList.location.href=\"" + url2 720 + "\"'>" + generatePackageName(packageData) + "</a></td>"); 721 ret.append("<td class=\"value\">" + packageData.getNumberOfChildren() 722 + "</td>"); 723 ret.append(generateTableColumnsFromData(packageData, ccn)); 724 ret.append("</tr>"); 725 return ret.toString(); 726 } 727 728 private String generateTableRowsForSourceFile(SourceFileData sourceFileData) 729 { 730 StringBuffer ret = new StringBuffer(); 731 String sourceFileName = sourceFileData.getNormalizedName(); 732 // TODO: ccn should be calculated per-class, not per-file 733 double ccn = complexity.getCCNForSourceFile(sourceFileData); 734 735 Iterator iter = sourceFileData.getClasses().iterator(); 736 while (iter.hasNext()) 737 { 738 ClassData classData = (ClassData)iter.next(); 739 ret 740 .append(generateTableRowForClass(classData, sourceFileName, 741 ccn)); 742 } 743 744 return ret.toString(); 745 } 746 747 private String generateTableRowForClass(ClassData classData, 748 String sourceFileName, double ccn) 749 { 750 StringBuffer ret = new StringBuffer(); 751 752 ret.append(" <tr>"); 753 // TODO: URL should jump straight to the class (only for inner classes?) 754 ret.append("<td><a href=\"" + sourceFileName 755 + ".html\">" + classData.getBaseName() + "</a></td>"); 756 ret.append(generateTableColumnsFromData(classData, ccn)); 757 ret.append("</tr>\n"); 758 return ret.toString(); 759 } 760 761 /** 762 * Return a string containing three HTML table cells. The first 763 * cell contains a graph showing the line coverage, the second 764 * cell contains a graph showing the branch coverage, and the 765 * third cell contains the code complexity. 766 * 767 * @param ccn The code complexity to display. This should be greater 768 * than 1. 769 * @return A string containing the HTML for three table cells. 770 */ 771 private static String generateTableColumnsFromData(CoverageData coverageData, 772 double ccn) 773 { 774 int numLinesCovered = coverageData.getNumberOfCoveredLines(); 775 int numLinesValid = coverageData.getNumberOfValidLines(); 776 int numBranchesCovered = coverageData.getNumberOfCoveredBranches(); 777 int numBranchesValid = coverageData.getNumberOfValidBranches(); 778 779 // The "hidden" CSS class is used below to write the ccn without 780 // any formatting so that the table column can be sorted correctly 781 return "<td>" + generatePercentResult(numLinesCovered, numLinesValid) 782 +"</td><td>" 783 + generatePercentResult(numBranchesCovered, numBranchesValid) 784 + "</td><td class=\"value\"><span class=\"hidden\">" 785 + ccn + ";</span>" + getDoubleValue(ccn) + "</td>"; 786 } 787 788 /** 789 * This is crazy complicated, and took me a while to figure out, 790 * but it works. It creates a dandy little percentage meter, from 791 * 0 to 100. 792 * @param dividend The number of covered lines or branches. 793 * @param divisor The number of valid lines or branches. 794 * @return A percentage meter. 795 */ 796 private static String generatePercentResult(int dividend, int divisor) 797 { 798 StringBuffer sb = new StringBuffer(); 799 800 sb.append("<table cellpadding=\"0px\" cellspacing=\"0px\" class=\"percentgraph\"><tr class=\"percentgraph\"><td align=\"right\" class=\"percentgraph\" width=\"40\">"); 801 if (divisor > 0) 802 sb.append(getPercentValue((double)dividend / divisor)); 803 else 804 sb.append(generateHelpURL( 805 "N/A", 806 "Line coverage and branch coverage will appear as \"Not Applicable\" when Cobertura can not find line number information in the .class file. This happens for stub and skeleton classes, interfaces, or when the class was not compiled with \"debug=true.\"")); 807 sb.append("</td><td class=\"percentgraph\"><div class=\"percentgraph\">"); 808 if (divisor > 0) 809 { 810 sb.append("<div class=\"greenbar\" style=\"width:" 811 + (dividend * 100 / divisor) + "px\">"); 812 sb.append("<span class=\"text\">"); 813 sb.append(dividend); 814 sb.append("/"); 815 sb.append(divisor); 816 } 817 else 818 { 819 sb.append("<div class=\"na\" style=\"width:100px\">"); 820 sb.append("<span class=\"text\">"); 821 sb.append(generateHelpURL( 822 "N/A", 823 "Line coverage and branch coverage will appear as \"Not Applicable\" when Cobertura can not find line number information in the .class file. This happens for stub and skeleton classes, interfaces, or when the class was not compiled with \"debug=true.\"")); 824 } 825 sb.append("</span></div></div></td></tr></table>"); 826 827 return sb.toString(); 828 } 829 830 private static String getDoubleValue(double value) 831 { 832 NumberFormat formatter; 833 formatter = new DecimalFormat(); 834 return formatter.format(value); 835 } 836 837 private static String getPercentValue(double value) 838 { 839 return StringUtil.getPercentValue(value); 840 } 841 842 }