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 }