001    /*
002     * Cobertura - http://cobertura.sourceforge.net/
003     *
004     * Copyright (C) 2005 Mark Doliner <thekingant@users.sourceforge.net>
005     *
006     * Cobertura is free software; you can redistribute it and/or modify
007     * it under the terms of the GNU General Public License as published
008     * by the Free Software Foundation; either version 2 of the License,
009     * or (at your option) any later version.
010     *
011     * Cobertura is distributed in the hope that it will be useful, but
012     * WITHOUT ANY WARRANTY; without even the implied warranty of
013     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014     * General Public License for more details.
015     *
016     * You should have received a copy of the GNU General Public License
017     * along with Cobertura; if not, write to the Free Software
018     * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
019     * USA
020     */
021    
022    package net.sourceforge.cobertura.reporting.html;
023    
024    import java.io.BufferedReader;
025    import java.io.File;
026    import java.io.FileOutputStream;
027    import java.io.FileReader;
028    import java.io.IOException;
029    import java.io.PrintStream;
030    import java.text.DecimalFormat;
031    import java.text.NumberFormat;
032    import java.util.Iterator;
033    import java.util.Set;
034    import java.util.TreeSet;
035    
036    import net.sourceforge.cobertura.reporting.Clazz;
037    import net.sourceforge.cobertura.reporting.CoverageReport;
038    import net.sourceforge.cobertura.reporting.Package;
039    import net.sourceforge.cobertura.reporting.Util;
040    import net.sourceforge.cobertura.reporting.html.files.CopyFiles;
041    
042    public class HTMLReport
043    {
044    
045            private File outputDir;
046    
047            private File sourceDir;
048    
049            private CoverageReport coverage;
050    
051            /**
052             * Create a coverage report
053             */
054            public HTMLReport(CoverageReport coverage, File outputDir, File sourceDir)
055                            throws Exception
056            {
057                    this.outputDir = outputDir;
058                    this.sourceDir = sourceDir;
059                    this.coverage = coverage;
060    
061                    removeNonexistantClasses();
062                    CopyFiles.copy(outputDir);
063                    generatePackageList();
064                    generateClassLists();
065                    generateOverviews();
066                    generateSourceFiles();
067            }
068    
069            /**
070             * Go through the classes in our coverage data and remove any class
071             * for which we don't have source code.  These classes are usually
072             * RMI stubs or inner classes.
073             */
074            private void removeNonexistantClasses()
075            {
076                    Iterator iter = coverage.getClasses().iterator();
077                    while (iter.hasNext())
078                    {
079                            Clazz clazz = (Clazz)iter.next();
080                            File file = new File(sourceDir, clazz.getLongFileName());
081                            if (!file.isFile())
082                            {
083                                    coverage.removeClass(clazz);
084                            }
085                    }
086            }
087    
088            private void generatePackageList() throws IOException
089            {
090                    File file = new File(outputDir, "frame-packages.html");
091                    PrintStream out = null;
092    
093                    try
094                    {
095                            out = new PrintStream(new FileOutputStream(file));
096    
097                            out.println("<html>");
098                            out.println("<head>");
099                            out.println("<title>Coverage Report</title>");
100                            out
101                                            .println("<link title=\"Style\" type=\"text/css\" rel=\"stylesheet\" href=\"css/main.css\" />");
102                            out.println("</head>");
103                            out.println("<body>");
104                            out.println("<h5>Packages</h5>");
105                            out.println("<table width=\"100%\">");
106                            out.println("<tr>");
107                            out
108                                            .println("<td nowrap=\"nowrap\"><a href=\"frame-summary.html\" onClick='parent.classList.location.href=\"frame-classes.html\"' target=\"summary\">All</a></td>");
109                            out.println("</tr>");
110                            Iterator iter = coverage.getPackages().iterator();
111                            while (iter.hasNext())
112                            {
113                                    Package pkg = (Package)iter.next();
114                                    String url1 = "frame-summary-" + pkg.getName() + ".html";
115                                    String url2 = "frame-classes-" + pkg.getName() + ".html";
116                                    out.println("<tr>");
117                                    out.println("<td nowrap=\"nowrap\"><a href=\"" + url1
118                                                    + "\" onClick='parent.classList.location.href=\""
119                                                    + url2 + "\"' target=\"summary\">" + pkg.getName()
120                                                    + "</a></td>");
121                                    out.println("</tr>");
122                            }
123                            out.println("</table>");
124                            out.println("</body>");
125                            out.println("</html>");
126                    }
127                    finally
128                    {
129                            if (out != null)
130                            {
131                                    out.close();
132                            }
133                    }
134            }
135    
136            private void generateClassLists() throws IOException
137            {
138                    generateClassList(null);
139                    Iterator iter = coverage.getPackages().iterator();
140                    while (iter.hasNext())
141                    {
142                            Package pkg = (Package)iter.next();
143                            generateClassList(pkg);
144                    }
145            }
146    
147            private void generateClassList(Package pkg) throws IOException
148            {
149                    String filename;
150                    Set classes;
151                    if (pkg == null)
152                    {
153                            filename = "frame-classes.html";
154                            classes = coverage.getClasses();
155                    }
156                    else
157                    {
158                            filename = "frame-classes-" + pkg.getName() + ".html";
159                            classes = pkg.getClasses();
160                    }
161                    File file = new File(outputDir, filename);
162                    PrintStream out = null;
163    
164                    try
165                    {
166                            out = new PrintStream(new FileOutputStream(file));
167    
168                            out.println("<html>");
169                            out.println("<head>");
170                            out.println("<title>Coverage Report Classes</title>");
171                            out
172                                            .println("<link title=\"Style\" type=\"text/css\" rel=\"stylesheet\" href=\"css/main.css\" />");
173                            out.println("</head>");
174                            out.println("<body>");
175                            out.println("<h5>");
176                            out.println(pkg == null ? "All Packages" : pkg.getName());
177                            out.println("</h5>");
178                            out.println("<h5>Classes</h5>");
179                            out.println("<table width=\"100%\">");
180    
181                            for (Iterator iter = classes.iterator(); iter.hasNext();)
182                            {
183                                    Clazz clazz = (Clazz)iter.next();
184                                    out.println("<tr>");
185                                    String percentCovered;
186                                    if (clazz.getNumberOfLines() > 0)
187                                            percentCovered = getPercentValue(clazz
188                                                            .getLineCoverageRate());
189                                    else
190                                            percentCovered = "N/A";
191                                    out
192                                                    .println("<td nowrap=\"nowrap\"><a target=\"summary\" href=\""
193                                                                    + clazz.getLongName()
194                                                                    + ".html\">"
195                                                                    + clazz.getName()
196                                                                    + "</a> <i>("
197                                                                    + percentCovered + ")</i></td>");
198                                    out.println("</tr>");
199                            }
200    
201                            out.println("</td>");
202                            out.println("</tr>");
203                            out.println("</table>");
204                            out.println("</body>");
205                            out.println("</html>");
206                    }
207                    finally
208                    {
209                            if (out != null)
210                            {
211                                    out.close();
212                            }
213                    }
214            }
215    
216            private void generateOverviews() throws IOException
217            {
218                    generateOverview(null);
219                    Iterator iter = coverage.getPackages().iterator();
220                    while (iter.hasNext())
221                    {
222                            Package pkg = (Package)iter.next();
223                            generateOverview(pkg);
224                    }
225            }
226    
227            private void generateOverview(Package pkg) throws IOException
228            {
229                    String filename;
230                    if (pkg == null)
231                    {
232                            filename = "frame-summary.html";
233                    }
234                    else
235                    {
236                            filename = "frame-summary-" + pkg.getName() + ".html";
237                    }
238                    File file = new File(outputDir, filename);
239                    PrintStream out = null;
240    
241                    try
242                    {
243                            out = new PrintStream(new FileOutputStream(file));
244    
245                            out.println("<html>");
246                            out.println("<head>");
247                            out.println("<title>Coverage Report</title>");
248                            out
249                                            .println("<link title=\"Style\" type=\"text/css\" rel=\"stylesheet\" href=\"css/main.css\" />");
250                            out
251                                            .println("<link title=\"Style\" type=\"text/css\" rel=\"stylesheet\" href=\"css/sortabletable.css\" />");
252                            out
253                                            .println("<script type=\"text/javascript\" src=\"js/popup.js\"></script>");
254                            out
255                                            .println("<script type=\"text/javascript\" src=\"js/sortabletable.js\"></script>");
256                            out
257                                            .println("<script type=\"text/javascript\" src=\"js/percentagesorttype.js\"></script>");
258                            out.println("</head>");
259                            out.println("<body>");
260    
261                            out.print("<h5>Coverage Report - ");
262                            out.print(pkg == null ? "All Packages" : pkg.getName());
263                            out.println("</h5>");
264                            out.println("<p>");
265                            out.println("<table class=\"report\" id=\"packageResults\">");
266                            out.println("<thead>");
267                            out.println("<tr>");
268                            out.println("  <td class=\"heading\">Package</td>");
269                            out.println("  <td class=\"heading\"># Classes</td>");
270                            out.println(generateCommonTableColumns());
271                            out.println("</tr>");
272                            out.println("</thead>");
273                            out.println("<tbody>");
274    
275                            Set packages;
276                            if (pkg == null)
277                            {
278                                    // Output a summary line for all packages
279                                    out.println(generateTableRowForTotal());
280    
281                                    // Get packages
282                                    packages = coverage.getPackages();
283    
284                            }
285                            else
286                            {
287                                    // Output a line for the current package
288                                    out.println(generateTableRowForPackage(pkg));
289    
290                                    // Get subpackages
291                                    packages = coverage.getSubPackages(pkg);
292                            }
293    
294                            // Output a line for each package or subpackage
295                            if (packages.size() > 0)
296                            {
297                                    Iterator iter = packages.iterator();
298                                    while (iter.hasNext())
299                                    {
300                                            Package subpkg = (Package)iter.next();
301                                            out.println(generateTableRowForPackage(subpkg));
302                                    }
303                            }
304    
305                            out.println("</tbody>");
306                            out.println("</table>");
307                            out.println("<script type=\"text/javascript\">");
308                            out
309                                            .println("var packageTable = new SortableTable(document.getElementById(\"packageResults\"),");
310                            out
311                                            .println("    [\"String\", \"Number\", \"Percentage\", \"Percentage\", \"Number\", \"Number\", \"Number\"]);");
312                            out.println("packageTable.sort(0);");
313                            out.println("</script>");
314                            out.println("</p>");
315    
316                            // Get the list of classes in this package
317                            Set classes;
318                            if (pkg == null)
319                            {
320                                    classes = new TreeSet();
321                                    if (coverage.getClasses().size() > 0)
322                                    {
323                                            Iterator iter = coverage.getClasses().iterator();
324                                            while (iter.hasNext())
325                                            {
326                                                    Clazz clazz = (Clazz)iter.next();
327                                                    if (clazz.getPackageName() == null)
328                                                    {
329                                                            classes.add(clazz);
330                                                    }
331                                            }
332                                    }
333                            }
334                            else
335                            {
336                                    classes = pkg.getClasses();
337                            }
338    
339                            // Output a line for each class
340                            if (classes.size() > 0)
341                            {
342                                    out.println("<p>");
343                                    out.println("<table class=\"report\" id=\"classResults\">");
344                                    out.println(generateTableHeaderForClasses());
345                                    out.println("<tbody>");
346    
347                                    Iterator iter = classes.iterator();
348                                    while (iter.hasNext())
349                                    {
350                                            Clazz clazz = (Clazz)iter.next();
351                                            out.println(generateTableRowForClass(clazz));
352                                    }
353    
354                                    out.println("</tbody>");
355                                    out.println("</table>");
356                                    out.println("<script type=\"text/javascript\">");
357                                    out
358                                                    .println("var classTable = new SortableTable(document.getElementById(\"classResults\"),");
359                                    out
360                                                    .println("    [\"String\", \"Percentage\", \"Percentage\", \"Number\", \"Number\", \"Number\"]);");
361                                    out.println("classTable.sort(0);");
362                                    out.println("</script>");
363                                    out.println("</p>");
364                            }
365    
366                            out.println("<div class=\"footer\">");
367                            out
368                                            .println("Reports generated by <a href=\"http://cobertura.sourceforge.net/\" target=\"_top\">Cobertura</a>.");
369                            out.println("</div>");
370    
371                            out.println("</body>");
372                            out.println("</html>");
373                    }
374                    finally
375                    {
376                            if (out != null)
377                            {
378                                    out.close();
379                            }
380                    }
381            }
382    
383            private void generateSourceFiles() throws IOException
384            {
385                    Iterator iter = coverage.getClasses().iterator();
386                    while (iter.hasNext())
387                    {
388                            Clazz clazz = (Clazz)iter.next();
389                            generateSourceFile(clazz);
390                    }
391            }
392    
393            private void generateSourceFile(Clazz clazz) throws IOException
394            {
395                    String filename = clazz.getLongName() + ".html";
396                    File file = new File(outputDir, filename);
397                    PrintStream out = null;
398    
399                    try
400                    {
401                            out = new PrintStream(new FileOutputStream(file));
402    
403                            out.println("<html>");
404                            out.println("<head>");
405                            out.println("<title>Coverage Report</title>");
406                            out
407                                            .println("<link title=\"Style\" type=\"text/css\" rel=\"stylesheet\" href=\"css/main.css\" />");
408                            out
409                                            .println("<script type=\"text/javascript\" src=\"js/popup.js\"></script>");
410                            out.println("</head>");
411                            out.println("<body>");
412                            out.print("<h5>Coverage Report - ");
413                            if (clazz.getPackageName() != null)
414                            {
415                                    out.print(clazz.getPackageName() + ".");
416                            }
417                            out.print(clazz.getName());
418                            out.println("</h5>");
419    
420                            // Output the coverage summary for this class
421                            out.println("<p>");
422                            out.println("<table class=\"report\">");
423                            out.println(generateTableHeaderForClasses());
424                            out.println(generateTableRowForClass(clazz));
425                            out.println("</table>");
426                            out.println("</p>");
427    
428                            // Output this class's source code with syntax and coverage highlighting
429                            out.println("<p>");
430                            out
431                                            .println("<table cellspacing=\"0\" cellpadding=\"0\" class=\"src\">");
432                            BufferedReader br = null;
433                            try
434                            {
435                                    br = new BufferedReader(new FileReader(new File(sourceDir,
436                                                    clazz.getLongFileName())));
437                                    String lineStr;
438                                    JavaToHtml javaToHtml = new JavaToHtml();
439                                    int lineNumber = 1;
440                                    while ((lineStr = br.readLine()) != null)
441                                    {
442                                            out.println("<tr>");
443                                            if (clazz.isValidSourceLine(lineNumber))
444                                            {
445                                                    long numberOfHits = clazz.getNumberOfHits(lineNumber);
446                                                    out.println("  <td class=\"numLineCover\"> "
447                                                                    + lineNumber + "</td>");
448                                                    if (numberOfHits > 0)
449                                                    {
450                                                            out
451                                                                            .println("  <td class=\"nbHitsCovered\"> "
452                                                                                            + numberOfHits + "</td>");
453                                                            out
454                                                                            .println("  <td class=\"src\"><pre class=\"src\"> "
455                                                                                            + javaToHtml.process(lineStr)
456                                                                                            + "</pre></td>");
457                                                    }
458                                                    else
459                                                    {
460                                                            out
461                                                                            .println("  <td class=\"nbHitsUncovered\"> "
462                                                                                            + numberOfHits + "</td>");
463                                                            out
464                                                                            .println("  <td class=\"src\"><pre class=\"src\"><span class=\"srcUncovered\"> "
465                                                                                            + javaToHtml.process(lineStr)
466                                                                                            + "</span></pre></td>");
467                                                    }
468                                            }
469                                            else
470                                            {
471                                                    out.println("  <td class=\"numLine\"> "
472                                                                    + lineNumber + "</td>");
473                                                    out.println("  <td class=\"nbHits\"> </td>");
474                                                    out
475                                                                    .println("  <td class=\"src\"><pre class=\"src\"> "
476                                                                                    + javaToHtml.process(lineStr)
477                                                                                    + "</pre></td>");
478                                            }
479                                            out.println("</tr>");
480                                            lineNumber++;
481                                    }
482                            }
483                            finally
484                            {
485                                    if (br != null)
486                                    {
487                                            br.close();
488                                    }
489                            }
490                            out.println("</table>");
491                            out.println("</p>");
492    
493                            out.println("<div class=\"footer\">");
494                            out
495                                            .println("Reports generated by <a href=\"http://cobertura.sourceforge.net/\" target=\"_top\">Cobertura</a>.");
496                            out.println("</div>");
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 static String generateHelpURL(String text, String description)
511            {
512                    StringBuffer ret = new StringBuffer();
513                    boolean popupTooltips = false;
514                    if (popupTooltips)
515                    {
516                            ret
517                                            .append("<a class=\"hastooltip\" href=\"help.html\" onClick=\"popupwindow('help.html'); return false;\">");
518                            ret.append(text);
519                            ret.append("<span>" + description + "</span>");
520                            ret.append("</a>");
521                    }
522                    else
523                    {
524                            ret
525                                            .append("<a class=\"dfn\" href=\"help.html\" onClick=\"popupwindow('help.html'); return false;\">");
526                            ret.append(text);
527                            ret.append("</a>");
528                    }
529                    return ret.toString();
530            }
531    
532            private static String generateCommonTableColumns()
533            {
534                    StringBuffer ret = new StringBuffer();
535                    ret.append("  <td class=\"heading\" width=\"20%\">"
536                                    + generateHelpURL("Line Coverage",
537                                                    "The percent of lines executed by this test run.")
538                                    + "</td>");
539                    ret.append("  <td class=\"heading\" width=\"20%\">"
540                                    + generateHelpURL("Branch Coverage",
541                                                    "The percent of branches executed by this test run.")
542                                    + "</td>");
543                    ret
544                                    .append("  <td class=\"heading\" width=\"10%\">"
545                                                    + generateHelpURL(
546                                                                    "Complexity",
547                                                                    "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.)")
548                                                    + "</td>");
549                    return ret.toString();
550            }
551    
552            private static String generateTableHeaderForClasses()
553            {
554                    StringBuffer ret = new StringBuffer();
555                    ret.append("<thead>");
556                    ret.append("<tr>");
557                    ret.append("  <td class=\"heading\">Classes in this Package</td>");
558                    ret.append(generateCommonTableColumns());
559                    ret.append("</tr>");
560                    ret.append("</thead>");
561                    return ret.toString();
562            }
563    
564            private static String generateNAPercent()
565            {
566                    StringBuffer sb = new StringBuffer();
567                    sb
568                                    .append("<table cellpadding=\"0\" cellspacing=\"0\" align=\"right\">");
569                    sb.append("<tr>");
570                    sb
571                                    .append("<td>"
572                                                    + generateHelpURL(
573                                                                    "N/A",
574                                                                    "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.\"")
575                                                    + " </td>");
576                    sb.append("<td>");
577                    sb
578                                    .append("<table class=\"percentGraph\" cellpadding=\"0\" cellspacing=\"0\">");
579                    sb.append("<tr><td class=\"NA\" width=\"100\"></td></tr>");
580                    sb.append("</table>");
581                    sb.append("</td>");
582                    sb.append("</tr>");
583                    sb.append("</table>");
584                    return sb.toString();
585            }
586    
587            private static String generateTableColumnsForNA(double ccn)
588            {
589                    return "<td class=\"value\">" + generateNAPercent() + "</td>"
590                                    + "<td class=\"value\">" + generateNAPercent() + "</td>"
591                                    + "<td class=\"value\">" + getDoubleValue(ccn) + "</td>";
592    
593            }
594    
595            private static String generateTableColumnsFromData(double lineCoverage,
596                            double branchCoverage, double ccn)
597            {
598                    return "<td class=\"value\">" + generatePercentResult(lineCoverage)
599                                    + "</td>" + "<td class=\"value\">"
600                                    + generatePercentResult(branchCoverage) + "</td>"
601                                    + "<td class=\"value\">" + getDoubleValue(ccn) + "</td>";
602    
603            }
604    
605            private String generateTableRowForTotal()
606            {
607                    StringBuffer ret = new StringBuffer();
608                    double lineCoverage = coverage.getLineCoverageRate();
609                    double branchCoverage = coverage.getBranchCoverageRate();
610                    double ccn = Util.getCCN(sourceDir, true);
611                    ret.append("  <tr>");
612                    ret.append("<td class=\"text\"><b>All Packages</b></td>");
613                    ret.append("<td class=\"value\">" + coverage.getNumberOfClasses()
614                                    + "</td>");
615                    ret.append(generateTableColumnsFromData(lineCoverage, branchCoverage,
616                                    ccn));
617                    ret.append("</tr>");
618                    return ret.toString();
619            }
620    
621            private String generateTableRowForPackage(Package pkg)
622            {
623                    StringBuffer ret = new StringBuffer();
624                    String url1 = "frame-summary-" + pkg.getName() + ".html";
625                    String url2 = "frame-classes-" + pkg.getName() + ".html";
626                    double lineCoverage = pkg.getLineCoverageRate();
627                    double branchCoverage = pkg.getBranchCoverageRate();
628                    double ccn = Util.getCCN(new File(sourceDir, pkg.getFileName()),
629                                    false);
630                    ret.append("  <tr>");
631                    ret.append("<td class=\"text\"><a href=\"" + url1
632                                    + "\" onClick='parent.classList.location.href=\"" + url2
633                                    + "\"'>" + pkg.getName() + "</a></td>");
634                    ret
635                                    .append("<td class=\"value\">" + pkg.getClasses().size()
636                                                    + "</td>");
637                    ret.append(generateTableColumnsFromData(lineCoverage, branchCoverage,
638                                    ccn));
639                    ret.append("</tr>");
640                    return ret.toString();
641            }
642    
643            private String generateTableRowForClass(Clazz clazz)
644            {
645                    StringBuffer ret = new StringBuffer();
646                    double lineCoverage = clazz.getLineCoverageRate();
647                    double branchCoverage = clazz.getBranchCoverageRate();
648                    double ccn = Util.getCCN(
649                                    new File(sourceDir, clazz.getLongFileName()), false);
650                    ret.append("  <tr>");
651                    ret.append("<td class=\"text\"><a href=\"" + clazz.getLongName()
652                                    + ".html\">" + clazz.getName() + "</a></td>");
653                    if (clazz.getNumberOfLines() == 0)
654                    {
655                            ret.append(generateTableColumnsForNA(ccn));
656                    }
657                    else
658                    {
659                            ret.append(generateTableColumnsFromData(lineCoverage,
660                                            branchCoverage, ccn));
661                    }
662                    ret.append("</tr>");
663                    return ret.toString();
664            }
665    
666            private static String generatePercentResult(double percentValue)
667            {
668                    double rest = 1d - percentValue;
669                    StringBuffer sb = new StringBuffer();
670                    sb
671                                    .append("<table cellpadding=\"0\" cellspacing=\"0\" align=\"right\">");
672                    sb.append("<tr>");
673                    sb.append("<td>" + getPercentValue(percentValue) + " </td>");
674                    sb.append("<td>");
675                    sb
676                                    .append("<table class=\"percentGraph\" cellpadding=\"0\" cellspacing=\"0\">");
677                    sb.append("<tr>");
678                    sb.append("<td class=\"covered\" width=\""
679                                    + (int)(percentValue * 100) + "\"></td>");
680                    sb.append("<td class=\"uncovered\" width=\"" + (int)(rest * 100)
681                                    + "\"></td>");
682                    sb.append("</tr>");
683                    sb.append("</table>");
684                    sb.append("</td>");
685                    sb.append("</tr>");
686                    sb.append("</table>");
687                    return sb.toString();
688            }
689    
690            private static String getDoubleValue(double value)
691            {
692                    NumberFormat formatter;
693                    formatter = new DecimalFormat();
694                    return formatter.format(value);
695            }
696    
697            private static String getPercentValue(double value)
698            {
699                    NumberFormat formatter;
700                    formatter = NumberFormat.getPercentInstance();
701                    return formatter.format(value);
702            }
703    
704    }