Project Report: fawkez

Packagesummary org.jcoderz.phoenix.report

org.jcoderz.phoenix.report.Java2Html

LineHitsNoteSource
1  /*
2   * $Id: Java2Html.java 1514 2009-06-10 07:38:45Z mgriffel $
3   *
4   * Copyright 2006, The jCoderZ.org Project. All rights reserved.
5   *
6   * Redistribution and use in source and binary forms, with or without
7   * modification, are permitted provided that the following conditions are
8   * met:
9   *
10   *    * Redistributions of source code must retain the above copyright
11   *      notice, this list of conditions and the following disclaimer.
12   *    * Redistributions in binary form must reproduce the above
13   *      copyright notice, this list of conditions and the following
14   *      disclaimer in the documentation and/or other materials
15   *      provided with the distribution.
16   *    * Neither the name of the jCoderZ.org Project nor the names of
17   *      its contributors may be used to endorse or promote products
18   *      derived from this software without specific prior written
19   *      permission.
20   *
21   * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" AND
22   * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
24   * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS
25   * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
26   * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
27   * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
28   * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
29   * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
30   * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
31   * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32   */
33  package org.jcoderz.phoenix.report;
34  
35  import java.io.BufferedWriter;
36  import java.io.File;
37  import java.io.FileInputStream;
38  import java.io.FileNotFoundException;
39  import java.io.FileOutputStream;
40  import java.io.IOException;
41  import java.io.InputStream;
42  import java.io.OutputStream;
43  import java.io.OutputStreamWriter;
44  import java.io.Serializable;
45  import java.io.Writer;
46  import java.nio.charset.Charset;
47  import java.util.ArrayList;
48  import java.util.Arrays;
49  import java.util.Calendar;
50  import java.util.Collection;
51  import java.util.Collections;
52  import java.util.Comparator;
53  import java.util.HashMap;
54  import java.util.HashSet;
55  import java.util.Iterator;
56  import java.util.LinkedList;
57  import java.util.List;
58  import java.util.Map;
59  import java.util.Set;
60  import java.util.TimeZone;
61  import java.util.TreeSet;
62  import java.util.logging.Level;
63  import java.util.logging.Logger;
64  
65  import javax.xml.bind.JAXBContext;
66  import javax.xml.bind.JAXBException;
67  import javax.xml.bind.Unmarshaller;
68  
69  import org.jcoderz.commons.types.Date;
70  import org.jcoderz.commons.util.ArraysUtil;
71  import org.jcoderz.commons.util.Assert;
72  import org.jcoderz.commons.util.Constants;
73  import org.jcoderz.commons.util.EmptyIterator;
74  import org.jcoderz.commons.util.FileUtils;
75  import org.jcoderz.commons.util.IoUtil;
76  import org.jcoderz.commons.util.LoggingUtils;
77  import org.jcoderz.commons.util.ObjectUtil;
78  import org.jcoderz.commons.util.StringUtil;
79  import org.jcoderz.commons.util.XmlUtil;
80  import org.jcoderz.phoenix.report.jaxb.Item;
81  import org.jcoderz.phoenix.report.jaxb.Report;
82  
83  /**
84 (1) * TODO: Link to current build
85 (2) * TODO: Link to CC home
86 (3) * TODO: Add @media printer???
87 (4) * TODO: Refactor, split class.
88   *
89   * @author Andreas Mandel
90   */
91  public final class Java2Html
92  {
93     /** property name for the wiki url prefix. */
94     public static final String WIKI_BASE_PROPERTY = "report.wiki-prefix";
95  
96     /** Pattern helper to generate css style names of the listings. */
97100    private static final String[] PATTERN = {"odd", "even"};
98  
99     /** Size of the pattern. */
100100    private static final int PATTERN_SIZE = PATTERN.length;
101  
102     /** Name of this class. */
103100    private static final String CLASSNAME = Java2Html.class.getName();
104  
105     /** The logger used for technical logging inside this class. */
106100    private static final Logger logger = Logger.getLogger(CLASSNAME);
107  
108     /** String used as line separator in the output html. */
109     private static final String NEWLINE = "\n";
110  
111     /** Name of the index page with content sorted by package name. */
112     private static final String SORT_BY_PACKAGE_INDEX = "index.html";
113     /** Name of the index page with content sorted by quality. */
114     private static final String SORT_BY_QUALITY_INDEX = "index_q.html";
115     /** Name of the index page with content sorted by coverage. */
116     private static final String SORT_BY_COVERAGE_INDEX = "index_c.html";
117  
118     /** Marker for ccs styles used as the last row in a table. */
119     private static final String LAST_MARKER = "_last";
120  
121     private static final String DEFAULT_STYLESHEET = "reportstyle.css";
122  
123     private static final int NUMBER_OF_AGE_SEGMENTS = 5;
124  
125     /** Name to be used for unnamed package. */
126     private static final String UNNAMED_PACKAGE_NAME = "unnamed-package";
127  
128     /**
129      * Only findings that span at maximum this number of lines are 
130      * highlighted in the code.
131      */
132     private static final int MAX_LINES_WITH_INLINE_MARK = 3;
133  
134     private static final int ABSOLUTE_END_OF_LINE = 9999;
135  
136     /** Default tab with to use. */
137     private static final int DEFAULT_TAB_WIDTH = 8;
138  
139     /** Collects a List of all <code>FileSummary</code>s of the report. */
1400    private final List<FileSummary> mAllFiles
141         = new ArrayList<FileSummary>();
142  
143     /** Map of package name + FileSummary for this package. */
1440    private final Map<String, FileSummary> mPackageSummary
145         = new HashMap<String, FileSummary>();
1460    private final Map<String, List<FileSummary>> mAllPackages
147         = new HashMap<String, List<FileSummary>>();
148     /**
149      * Collects findings in the current file.
150      * Maps from the line number (Integer) to a List of Item objects.
151      */
1520    private final Map<Integer, List<Item>> mFindingsInFile
153         = new HashMap<Integer, List<Item>>();
1540    private final Set<Item> mFindingsInCurrentLine
155         = new HashSet<Item>();
1560    private final List<Item> mCurrentFindings = new ArrayList<Item>();
1570    private final List<Item> mHandledFindings
158         = new ArrayList<Item>();
159     /** List of findings with no (available) file assignment */
1600    private final List<org.jcoderz.phoenix.report.jaxb.File> mGlobalFindings
161         = new ArrayList<org.jcoderz.phoenix.report.jaxb.File>();
162     /** file summary for all files */
163     private FileSummary mGlobalSummary;
164  
165  
1660    private String mProjectName = "";
1670    private String mWebVcBase = null;
1680    private String mWebVcSuffix = "";
169     private String mProjectHome;
1700    private String mTimestamp = null;
1710    private String mStyle = DEFAULT_STYLESHEET; // the CSS stuff to use
172     private String mClassname;
173     private String mPackage;
174     private String mPackageBase;
1750    private final StringBuilder mStringBuilder
176         = new StringBuilder();
177     /** String buffer to be used by the getIcons method. */
1780    private final StringBuilder mGetIconsStringBuffer
179         = new StringBuilder();
180  
181     private java.io.File mInputData;
182     private java.io.File mOutDir;
183  
1840    private boolean mCoverageData = true;
185  
1860    private Level mLogLevel = Level.INFO;
187  
188     /**
189      * Holds a list of items that are currently active for the
190      * current file and line.
191      */
1920    private final List<Item> mActiveItems
193         = new LinkedList<Item>();
194  
1950    private Charset mCharSet = Charset.defaultCharset();
196  
1970    private int mTabWidth = DEFAULT_TAB_WIDTH;
198  
199     /** The full Report. */
200     private Report mReport;
201  
2020    private final Map<Item, org.jcoderz.phoenix.report.jaxb.File> mItemToFileMap
203         = new HashMap<Item, org.jcoderz.phoenix.report.jaxb.File>();
204     
205     /**
206      * Constructor.
207      *
208      * @throws IOException In case the current working directory cannot be
209      *       determined.
210      */
211     public Java2Html ()
212           throws IOException
2130    {
2140       mProjectHome = new java.io.File(".").getCanonicalPath();
2150    }
216  
217     /**
218      * Main entry point.
219      *
220      * @param args The command line arguments.
221      * @throws IOException an io exception occurs.
222      * @throws JAXBException if the xml can not be parsed.
223      */
224     public static void main (String[] args)
225           throws IOException, JAXBException
226     {
2270       final Java2Html engine = new Java2Html();
228  
2290       engine.parseArguments(args);
230        // Turn on logging
2310       Logger.getLogger("org.jcoderz.phoenix.report").setLevel(Level.FINEST);
2320       engine.process();
2330    }
234  
235     /**
236      * Returns the string "odd" or "even", depending on the number given.
237      * @param number the number to check if it'S odd or even.
238      * @return the string "odd" if the given number is odd, "even"
239      *     otherwise.
240      */
241     public static String toOddEvenString (int number)
242     {
2430        return PATTERN[number % PATTERN_SIZE];
244     }
245  
246     /**
247      * Set the name of the package that should be treated as project "root"
248      * package.
249      * @param packageBase the project base package name.
250      */
251     public void setPackageBase (String packageBase)
252     {
2530       mPackageBase = packageBase;
2540       logger.config("Package base set to '" + mPackageBase + "'.");
2550    }
256  
257     /**
258      * Set the timestamp when the report has been initiated.
259      *
260      * @param timestamp the report creation timestamp.
261      */
262     public void setTimestamp (String timestamp)
263     {
2640       mTimestamp = timestamp;
2650       logger.config("Timestamp set to '" + mTimestamp + "'.");
2660    }
267  
268     /**
269      * Sets the flag if coverage data is available and should be taken
270      * into account.
271      * @param coverageDataAvailable true, if coverage data is available and
272      * should be taken into account.
273      */
274     public void setCoverageData (boolean coverageDataAvailable)
275     {
2760       mCoverageData = coverageDataAvailable;
2770       logger.config("Coverage data set to '" + mCoverageData + "'.");
2780    }
279  
280     /**
281      * Sets the css file to be used can be relative to report path or
282      * absolute.
283      * @param style the css file to be used can be relative to report path or
284      * absolute.
285      */
286     public void setStyle (String style)
287     {
2880       mStyle = style;
2890       logger.config("Style set to '" + mStyle + "'.");
2900    }
291  
292     /**
293      * Base url to the wiki to use.
294      * Finding pages link to a wiki page combined of this url and the
295      * finding type.
296      * @param wikiBase url to the wiki to use.
297      */
298     public void setWikiBase (String wikiBase)
299     {
3000       logger.config("Wiki base set to '" + wikiBase + "'.");
3010       System.getProperties().setProperty(WIKI_BASE_PROPERTY,
302              wikiBase);
3030    }
304  
305     /**
306      * Base path (url) to the cvs repository used to create links to
307      * the cvs.
308      * @param cvsBase url that points to a web cvs of the project.
309      */
310     public void setCvsBase (String cvsBase)
311     {
3120       mWebVcBase = cvsBase;
3130       logger.config("CVS base set to '" + mWebVcBase + "'.");
3140    }
315  
316     /**
317      * Suffix to be added at the end of web vc links.
318      * @param suffix String, to be added at the end of web vc links.
319      */
320     public void setCvsSuffix (String suffix)
321     {
3220        mWebVcSuffix = suffix;
3230        logger.config("CVS suffix set to '" + suffix + "'.");
3240    }
325  
326     /**
327      * Returns the name of the project.
328      * @return the name of the project.
329      */
330     public String getProjectName ()
331     {
3320       return mProjectName;
333     }
334  
335     /**
336      * Sets the name of the project used as readable string at several
337      * places in the report.
338      * @param name the name of the project used as readable string at several
339      *    places in the report.
340      */
341     public void setProjectName (String name)
342     {
3430       Assert.notNull(name, "name");
3440       mProjectName = name;
3450       logger.config("Project name set to '" + getProjectName() + "'.");
3460    }
347  
348     /**
349      * Sets the tab with to be used when calculating the position in the 
350      * current line.
351      * @param tabWidth the tab width to assume in input files.
352      */
353     public void setTabwidth (String tabWidth)
354     {
3550        Assert.notNull(tabWidth, "width");
3560        mTabWidth = Integer.parseInt(tabWidth);
3570        logger.config("Source tab width set to '" + mTabWidth + "'.");
3580    }
359  
360     /**
361      * The log level to be used when processing the input. 
362      * Level must be parseable by {@link Level#parse(String)}.
363      * @param loglevel the log level to set.
364      */
365     public void setLoglevel (String loglevel)
366     {
3670        mLogLevel = Level.parse(loglevel);
3680        LoggingUtils.setGlobalHandlerLogLevel(Level.ALL);
3690        logger.fine("Setting log level: " + mLogLevel);
3700        logger.setLevel(mLogLevel);
3710    }
372  
373     /**
374      * Sets the char-set used for the source files.
375      * @param charset The char-set in which the source files are expected.
376      */
377     public void setSourceCharset (Charset charset)
378     {
3790       Assert.notNull(charset, "charset");
3800       mCharSet = charset;
3810       logger.config("Source charset set to '" + charset + "'.");
3820    }
383  
384     /**
385      * The base path where the source files can be found.
386      * @param file base path where the source files can be found.
387      * @throws IOException if access to the path fails.
388      */
389     public void setProjectHome (java.io.File file) throws IOException
390     {
3910      final java.io.File projectHomeFile = file.getCanonicalFile();
3920      mProjectHome = projectHomeFile.getCanonicalPath();
3930      if (!projectHomeFile.isDirectory())
394       {
3950         throw new RuntimeException("'projectHome' must be a directory '"
396                + projectHomeFile + "'.");
397       }
3980      logger.config("Using project home " + mProjectHome + ".");
3990    }
400  
401     /**
402      * The input file containing the jcoderz report.
403      * @param file input file containing the jcoderz report.
404      * @throws IOException if access to the file fails.
405      */
406     public void setInputFile (java.io.File file) throws IOException
407     {
4080      mInputData = file.getCanonicalFile();
4090      if (!mInputData.canRead())
410       {
4110         throw new RuntimeException("Can not read report file '"
412                + mInputData + "'.");
413       }
4140      logger.config("Using report file " + mInputData + ".");
4150    }
416  
417     /**
418      * The output directory where the report should be written to.
419      * If the directory does not exist it is created.
420      * @param dir the output directory where the report should be written to.
421      * @throws IOException if access or creation of the directory fails.
422      */
423     public void setOutDir (java.io.File dir) throws IOException
424     {
4250      mOutDir = dir.getCanonicalFile();
4260      if (!mOutDir.exists())
427       {
4280         if (!mOutDir.mkdir())
429          {
4300            throw new RuntimeException("Could not create 'outDir' '"
431                   + mOutDir + "'.");
432          }
433       }
4340      if (!mOutDir.isDirectory())
435       {
4360         throw new RuntimeException("'outDir' must be a directory '"
437                + mOutDir + "'.");
438       }
4390      logger.config("Using out dir " + mOutDir + ".");
4400    }
441  
442     /**
443      * Starts the actual generation process.
444      * @throws JAXBException if the xmp parsing fails.
445      * @throws IOException if a IO problem occurs.
446      */
447     public void process ()
448           throws JAXBException, IOException
449     {
4500       final JAXBContext jaxbContext
451              = JAXBContext.newInstance("org.jcoderz.phoenix.report.jaxb",
452                    this.getClass().getClassLoader());
4530       final Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
4540(5)      unmarshaller.setValidating(true);
4550       mReport = (Report) unmarshaller.unmarshal(mInputData);
4560       mGlobalSummary = new FileSummary();
457  
4580       initialiteFindingTypes();
459        
460        for (final org.jcoderz.phoenix.report.jaxb.File file
4610(6)          : (List<org.jcoderz.phoenix.report.jaxb.File>) mReport.getFile())
462        {
463           try
464           {
4650              if (file.getName() != null)
466               {
4670                  java2html(new java.io.File(file.getName()), file);
468               }
469               else
470               {
4710                  mGlobalFindings.add(file);
472               }
473           }
4740          catch (Exception ex)
475           {
4760             if (file.getItem().isEmpty())
477              {
4780                 logger.log(Level.FINE,
479                      "No report for file without items '" + file.getName()
480                          + "'.", ex);
481              }
482              else
483              {
4840                 logger.log(Level.SEVERE,
485                      "Failed to generate report for '" + file.getName()
486                      + "'.", ex);
4870                 mGlobalFindings.add(file);
488              }
4890          }
490        }
491  
492        // create package summary
4930       for (final List<FileSummary> pkg : mAllPackages.values())
494        {
4950          createPackageSummary(pkg);
496        }
4970       createFullSummary();
498  
4990       createFindingsSummary();
5000       createPerFindingSummary();
5010       createAgeSummary();
502  
5030       logger.fine("Charts.");
504  
505        try
506        {
507            final StatisticCollector sc;
5080           if (mPackageBase == null)
509            {
5100             sc = new StatisticCollector(mReport, mOutDir, mTimestamp);
511            }
512            else
513            {
5140             sc = new StatisticCollector(
515                  mReport, mPackageBase, mOutDir, mTimestamp);
516            }
5170           sc.createCharts();
518        }
519        // we already created the report. There is no way
520        // to flag this as finding, so log it as a error
5210       catch (Exception ex)
522        {
5230           logger.log(Level.SEVERE,
524                "Failed to create charts for report. "
525                + ex.getMessage(), ex);
5260       }
527  
5280       copyStylesheet();
5290       copyIcons();
530  
5310       logger.fine("Done.");
5320    }
533  
534      private void initialiteFindingTypes ()
535      {
536          for (final org.jcoderz.phoenix.report.jaxb.File file
5370(7)            : (List<org.jcoderz.phoenix.report.jaxb.File>) mReport.getFile())
538          {
5390(8)            for (Item i : (List<Item>) file.getItem())
540              {
541                  try
542                  {
5430                     FindingType.initialize(i.getOrigin());
544                  }
5450                 catch (Exception ex)
546                  {
5470                     logger.log(Level.WARNING,
548                          "Could not initialize finding type "
549                          + i.getFindingType());
5500                 }
551              }
552           }
553          
5540     }
555  
556     private void createAgeSummary ()
557         throws IOException
558     {
5590        final List<Item> items = new ArrayList<Item>();
560         for (final org.jcoderz.phoenix.report.jaxb.File file
5610(9)           : (List<org.jcoderz.phoenix.report.jaxb.File>) mReport.getFile())
562         {
5630(10)          for (Item i : (List<Item>) file.getItem())
564            {
5650               if (i.isSetSince())
566                {
5670                   items.add(i);
5680                   mItemToFileMap.put(i, file);
569                }
570            }
571         }
5720        Collections.sort(items,
573             Collections.reverseOrder(new ItemAgeComperator()));
574  
5750        renderAgePage(items, Date.FUTURE_DATE, ReportInterval.BUILD);
5760        renderAgePage(items, Date.FUTURE_DATE, ReportInterval.DAY);
5770        renderAgePage(items, Date.FUTURE_DATE, ReportInterval.WEEK);
5780        final Date oldest
579             = renderAgePage(items, Date.FUTURE_DATE, ReportInterval.MONTH);
5800        renderAgePage(items, oldest, ReportInterval.OLD);
5810    }
582  
583     private void copyIcons ()
584         throws IOException
585     {
586        // create images sub-folder
5870       final File outDir = new File(mOutDir, "images");
5880       FileUtils.mkdirs(outDir);
589  
5900       for (int i = 0; i < Severity.VALUES.size(); i++)
591        {
5920           final Severity s = Severity.fromInt(i);
5930           if (s.equals(Severity.COVERAGE))
594            {
5950               continue;
596            }
5970           copyImage(outDir, "icon_" + s.toString() + ".gif");
5980           copyImage(outDir, "bg-" + s.toString() + ".gif");
599        }
6000    }
601  
602      private void copyImage (final File outDir, final String name)
603      {
6040         final InputStream in
605              = this.getClass().getResourceAsStream(name);
606          try
607          {
6080             if (in != null)
609              {
6100                 copyResource(in, name, outDir);
611              }
612              else
613              {
6140                 logger.warning(
615                      "Could not find resource '" + name + "'!");
616              }
617          }
618          finally
619          {
6200             IoUtil.close(in);
6210         }
6220     }
623  
624     private void copyResource (InputStream in, String resource, File outDir)
625     {
626        // Copy it to the output folder
6270       OutputStream out = null;
628        try
629        {
6300          out = new FileOutputStream(new File(outDir, resource));
6310          FileUtils.copy(in, out);
632        }
6330       catch (FileNotFoundException ex)
634        {
6350          throw new RuntimeException("Can not find output folder '"
636              + mOutDir + "'.", ex);
637        }
6380       catch (IOException ex)
639        {
6400          throw new RuntimeException("Could not copy resource '"
641              + resource + "'.", ex);
642        }
643        finally
644        {
6450          IoUtil.close(out);
6460       }
6470    }
648  
649     private void copyStylesheet ()
650     {
651        // 1. Try to read the stylesheet from the jar (default stylesheet)
652        // 2. Try to open it from a user-defined location
653        // 3. Use the default one if the user-defined is not found
6540       InputStream in = this.getClass().getResourceAsStream(mStyle);
655        try
656        {
6570           if (in == null)
658            {
659               try
660               {
6610                 final File style = new File(mStyle);
6620                 in = new FileInputStream(style);
663               }
6640              catch (FileNotFoundException ex)
665               {
6660                 IoUtil.close(in);
6670                 in = this.getClass().getResourceAsStream(DEFAULT_STYLESHEET);
6680                 if (in == null)
669                  {
6700                    throw new RuntimeException("Can not find stylesheet file '"
671                           + mStyle + "'.", ex);
672                  }
6730              }
674            }
6750           copyResource(in, DEFAULT_STYLESHEET, mOutDir);
676        }
677        finally
678        {
6790           IoUtil.close(in);
6800       }
6810    }
682  
683 (11)   private void parseArguments (String[] args)
684     {
685        try
686        {
6870          for (int i = 0; i < args.length; )
688           {
6890             if ("-outDir".equals(args[i]))
690              {
6910                setOutDir(new java.io.File(args[i + 1]));
692              }
6930             else if ("-report".equals(args[i]))
694              {
6950                setInputFile(new java.io.File(args[i + 1]));
696              }
6970             else if ("-projectHome".equals(args[i]))
698              {
6990                setProjectHome(new java.io.File(args[i + 1]));
700              }
7010             else if ("-projectName".equals(args[i]))
702              {
7030                setProjectName(args[i + 1]);
704              }
7050             else if ("-cvsBase".equals(args[i]))
706              {
7070                setCvsBase(args[i + 1]);
708              }
7090             else if ("-cvsSuffix".equals(args[i]))
710              {
7110                setCvsSuffix(args[i + 1]);
712              }
7130             else if ("-timestamp".equals(args[i]))
714              {
7150                setTimestamp(args[i + 1]);
716              }
7170             else if ("-wikiBase".equals(args[i]))
718              {
7190                setWikiBase(args[i + 1]);
720              }
7210             else if ("-reportStyle".equals(args[i]))
722              {
7230                setStyle(args[i + 1]);
724              }
7250             else if ("-noCoverage".equals(args[i]))
726              {
7270                setCoverageData(false);
7280                i -= 1;
729              }
7300             else if ("-sourceEncoding".equals(args[i]))
731              {
7320                setSourceCharset(Charset.forName(args[i + 1]));
733              }
7340             else if ("-packageBase".equals(args[i]))
735              {
7360               setPackageBase(args[i + 1]);
737              }
7380             else if ("-loglevel".equals(args[i]))
739              {
7400                setLoglevel(args[i + 1]);
741              }
7420             else if ("-tabwidth".equals(args[i]))
743              {
7440                setTabwidth(args[i + 1]);
745              }
746              else
747              {
7480                throw new IllegalArgumentException(
749                         "Invalid argument '" + args[i] + "'");
750              }
7510             i += 1 /* command */ + 1 /* argument */;
752           }
753        }
7540       catch (IndexOutOfBoundsException e)
755        {
7560          final IllegalArgumentException ex
757                 = new IllegalArgumentException("Missing value for "
758                    + args[args.length - 1]);
7590          ex.initCause(e);
7600          throw ex;
761        }
7620       catch (Exception e)
763        {
7640          final IllegalArgumentException ex = new IllegalArgumentException(
765                 "Problem with arument value for " + args[args.length - 1]
766                 + "Argument line was " + ArraysUtil.toString(args));
7670          ex.initCause(e);
7680          throw ex;
7690       }
7700    }
771  
772     /**
773      *
774      */
775     private void createPerFindingSummary () throws IOException
776     {
777        for (final FindingsSummary.FindingSummary summary
7780           : FindingsSummary.getFindingsSummary().getFindings().values())
779        {
7800          final String filename = summary.createFindingDetailFilename();
7810          final BufferedWriter out = openWriter(filename);
782           try
783           {
7840              htmlHeader(out, "Finding-" + summary.getFindingType().getSymbol()
785                        + "-report " + mProjectName, "");
7860              summary.createFindingTypeContent(out);
7870              out.write("</body></html>");
788           }
789           finally
790           {
7910              IoUtil.close(out);
7920          }
7930       }
7940    }
795  
796     private void createFindingsSummary () throws IOException
797     {
7980       final BufferedWriter out = openWriter("findings.html");
799        try
800        {
8010           htmlHeader(out, "Finding report " + mProjectName, "");
8020           out.write("<h1><a href='index.html'>View by Classes</a></h1>");
8030           out.write("<h1><a href='age-Build.html'>View by Age per Build</a> "
804                + "<a href='age-Day.html'> Day</a> "
805                + "<a href='age-Week.html'> Week</a> "
806                + "<a href='age-Month.html'> Month</a> "
807                + "<a href='age-Old.html'> Old</a></h1>");
8080           out.write("<h1>Findings - Overview</h1>");
8090           createNewFindingsList(out);
8100           createOldFindingsList(out);
8110           FindingsSummary.createOverallContent(out);
8120           out.write("</body></html>");
813        }
814        finally
815        {
8160           IoUtil.close(out);
8170       }
8180    }
819  
820      private void createNewFindingsList (BufferedWriter out)
821          throws IOException
822      {
8230         int row = 0;
824          for (org.jcoderz.phoenix.report.jaxb.File file
8250(12)            : (List<org.jcoderz.phoenix.report.jaxb.File>) mReport.getFile())
826          {
8270(13)            for (Item item : (List<Item>) file.getItem())
828              {
8290                 if (item.isNew())
830                  {
8310                     if (row == 0)
832                      {
8330                         openTable(out, "New");
834                      }
8350                     createRow(out, file, item, row);
8360                     row++;
837                  }
838              }
839          }
8400         if (row > 0)
841          {
8420             closeTable(out);
843          }
8440     }
845  
846      private void createOldFindingsList (BufferedWriter out)
847          throws IOException
848      {
8490         int row = 0;
850          for (org.jcoderz.phoenix.report.jaxb.File file
8510(14)            : (List<org.jcoderz.phoenix.report.jaxb.File>) mReport.getFile())
852          {
8530(15)            for (Item item : (List<Item>) file.getItem())
854              {
8550                 if (item.isOld())
856                  {
8570                     if (row == 0)
858                      {
8590                         openTable(out, "Fixed");
860                      }
8610                     createRow(out, file, item, row);
8620                     row++;
863                  }
864              }
865          }
8660         if (row > 0)
867          {
8680             closeTable(out);
869          }
8700     }
871      
872      private void createRow (Writer bw,
873          org.jcoderz.phoenix.report.jaxb.File file, Item item, int rowCounter)
874          throws IOException
875      {
876          // Don't be to colorful, use the OK coloring
877          // for all entries.
8780         bw.write("<tr class='findings-");
8790         bw.write(Java2Html.toOddEvenString(rowCounter));
8800         bw.write("row'><td class='findings-image'>");
8810         appendSeverityImage(bw, item, "");
8820         bw.write("</td><td width='100%' class='findings-data'>");
8830         bw.write("<a href='");
8840         bw.write(createReportLink(file));
8850         bw.write("#LINE");
8860         bw.write(String.valueOf(item.getLine()));
8870         bw.write("'>");
8880         bw.write(XmlUtil.escape(file.getClassname()));
8890         bw.write(": ");
8900         appendItemMessage(bw, item);
8910         bw.write("</a></td><td>");
8920         if (item.isSetSince())
893          {
8940             bw.write(item.getSince().toDateString());
895          }
8960         bw.write("</td></tr>\n");
897          
8980     }
899  
900      private void openTable (Writer writer, String string)
901          throws IOException
902      {
9030         writer.write("<h2 class='severity-header'>");
9040         writer.write(string);
9050         writer.write(" Findings</h2>");
9060         writer.write("<table width='95%' cellpadding='2' cellspacing='0' "
907                + "border='0'>");
9080     }
909  
910      private void closeTable (Writer bw)
911          throws IOException
912      {
9130         bw.append("</table>");
9140     }
915      
916      /**
917      * converts a java source to HTML
918      * with syntax highlighting for the
919      * comments, keywords, strings and chars
920      */
921 (16)   private void java2html (java.io.File inFile,
922           org.jcoderz.phoenix.report.jaxb.File data)
923     {
924  
9250       mCurrentFindings.clear();
9260       mHandledFindings.clear();
9270       mFindingsInFile.clear();
9280       mFindingsInCurrentLine.clear();
9290       mActiveItems.clear();
9300(17)      mCurrentFindings.addAll(data.getItem());
9310       fillFindingsInFile(data);
9320       logger.finest("Processing file " + inFile);
933  
9340       BufferedWriter bw = null;
9350       String file = null;
936        try
937        {
9380          mPackage = data.getPackage();
9390          if (StringUtil.isEmptyOrNull(mPackage))
940           {
9410              mPackage = UNNAMED_PACKAGE_NAME;
942           }
9430          mClassname = data.getClassname();
944  
945           // If no class name is reported take the filename.
9460          if (StringUtil.isEmptyOrNull(mClassname))
947           {
9480              mClassname = inFile.getName();
949           }
950           
9510          final String subdir = mPackage.replaceAll("\\.", "/");
9520          final java.io.File dir = new java.io.File(mOutDir, subdir);
9530          FileUtils.mkdirs(dir);
954  
9550          bw = openWriter(dir, mClassname + ".html");
956  
9570          final Syntax src = new Syntax(inFile, mCharSet, mTabWidth);
9580          final FileSummary summary
959                 = createFileSummary(src.getNumberOfLines(), subdir);
9600          addSummary(summary);
961  
9620          file = mPackage + "." + mClassname;
963  
9640          htmlHeader(bw, mClassname, mPackage);
965  
9660          bw.write("<h1><a href='");
9670          bw.write(relativeRoot(mPackage));
9680          bw.write("'>Project Report: ");
9690          bw.write(mProjectName);
9700          bw.write("</a></h1>" + NEWLINE);
9710          bw.write("<h2><a href ='index.html'>Packagesummary ");
9720          bw.write(mPackage);
9730          bw.write("</a></h2>" + NEWLINE);
974  
9750          final String cvsLink = getCvsLink(inFile.getAbsolutePath());
9760          if (cvsLink != null)
977           {
9780             bw.write("<h3><a href='" + cvsLink
979                 + "' class='cvs' title='cvs version'>" + file
980                 + "</a></h3>" + NEWLINE);
981           }
982           else
983           {
9840             bw.write("<h3>" + file + "</h3>" + NEWLINE);
985           }
986  
987           // create header!!!!
9880          bw.write("<table border='0' cellpadding='2' cellspacing='0' "
989                    + "width='95%'>");
9900          bw.write("<thead><tr><th>Line</th><th>Hits</th><th>Note</th>"
991                    + "<th class='remainder'>Source</th></tr></thead>");
9920          bw.write("<tbody>");
993  
994           // PASS 2
9950          final int lastLine = src.getNumberOfLines();
9960          for (int currentLine = 1; currentLine <= lastLine; currentLine++)
997           {
9980             bw.write("<tr class='"
999                 + errorLevel(currentLine)
1000                 + Java2Html.toOddEvenString(currentLine) + "'>");
10010             bw.write("<td align='right' class='lineno");
10020             final boolean isLast = currentLine == lastLine;
10030             appendIf(bw, isLast, LAST_MARKER);
10040             bw.write("'><a name='LINE" + currentLine + "' />");
10050             bw.write(String.valueOf(currentLine));
10060             bw.write("</td>");
10070             hitsCell(bw, String.valueOf(getHits(currentLine)), isLast);
10080             bw.write("<td class='note");
10090             appendIf(bw, isLast, LAST_MARKER);
10100             bw.write("'>");
10110             bw.write(getIcons(currentLine));
10120             bw.write("</td><td class='code-");
10130             bw.write(Java2Html.toOddEvenString(currentLine));
10140             appendIf(bw, isLast, LAST_MARKER);
10150             bw.write("'>");
10160             createCodeLine(bw, src);
10170             bw.write("</td></tr>\n");
1018           }
10190          bw.write("</tbody>");
10200          bw.write("</table>\n");
1021  
1022           // findings table
10230          bw.write("<h2 class='findings-header'>Findings in this File</h2>");
10240          bw.write("<table width='95%' cellpadding='0' cellspacing='0' "
1025                 + "border='0'>\n");
10260          int rowCounter = 0;
1027  
10280          final String relativeRoot = relativeRoot(mPackage, "");
1029  
10300          int pos = mHandledFindings.size();
1031           // findings with no line number or uncovered jet
10320          for (final List<Item> lineFindings : mFindingsInFile.values())
1033           {
10340              for (final Item item : lineFindings)
1035               {
10360                if (!Origin.COVERAGE.equals(item.getOrigin()))
1037                 {
10380                   pos++;
10390                   rowCounter++;
1040  
10410                   bw.write("<tr class='findings-");
10420                   bw.write(Java2Html.toOddEvenString(rowCounter));
10430                   bw.write("row'>\n");
10440                   bw.write(" <td class='findings-image'>\n");
10450                   appendSeverityImage(bw, item, relativeRoot);
10460                   bw.write(" </td>\n");
10470                   bw.write(" <td class='findings-id'>\n");
10480                   bw.write(" <a name='FINDING" + pos + "' />\n");
10490                   bw.write(" (" + pos + ")\n");
10500                   bw.write(" </td>\n");
10510                   bw.write(" <td></td><td></td><td></td>\n"); // line number
10520                   bw.write(" <td width='100%' class='findings-data'>\n");
10530                   appendItemMessage(bw, item);
10540                   bw.write(" </td>\n");
10550                   bw.write("</tr>\n");
1056                 }
1057              }
1058           }
1059  
1060           // Findings as marked in the code.
10610          pos = 0;
10620          for (final Item item : mHandledFindings)
1063           {
10640             pos++;
10650             rowCounter++;
1066  
10670             final String link = "#LINE" + item.getLine();
1068  
10690             bw.write("<tr class='findings-");
10700             bw.write(Java2Html.toOddEvenString(rowCounter));
10710             bw.write("row'>\n");
10720             bw.write(" <td class='findings-image'>\n");
10730             appendSeverityImage(bw, item, relativeRoot);
10740             bw.write(" </td>\n");
10750             bw.write(" <td class='findings-id'>\n");
10760             bw.write(" <a name='FINDING" + pos + "' />\n");
10770             bw.write(" <a href='" + link + "' title='" + item.getOrigin()
1078                    + "' >\n");
10790             bw.write(" (" + pos + ")\n");
10800             bw.write(" </a>\n");
10810             bw.write(" </td>\n");
10820             bw.write(" <td class='findings-line-number' align='right'>\n");
10830             bw.write(" <a href='" + link + "' >\n");
10840             bw.write(String.valueOf(item.getLine()));
10850             bw.write(" </a>\n");
10860             bw.write(" </td>\n");
10870             bw.write(" <td class='findings-line-number' align='center'>\n");
10880             bw.write(" <a href='" + link + "' >:</a>\n");
10890             bw.write(" </td>\n");
10900             bw.write(" <td class='findings-line-number' align='left'>\n");
10910             bw.write(" <a href='" + link + "' >\n");
10920             bw.write(String.valueOf(item.getColumn()));
10930             bw.write(" </a>\n");
10940             bw.write(" </td>\n");
10950             bw.write(" <td width='100%' class='findings-data'>\n");
10960             bw.write(" <a href='" + link + "' >\n");
10970             appendItemMessage(bw, item);
10980             bw.write("\n");
10990             bw.write(" </a>\n");
11000             bw.write(" </td>\n");
11010             bw.write("</tr>\n");
11020          }
1103  
11040          bw.write("</table>\n");
11050          bw.write("\n</body>\n</html>");
1106        }
11070       catch (FileNotFoundException fnfe)
1108        {
11090          logger.log(Level.WARNING, "Source file '" + file + "' not found.",
1110                 fnfe);
1111        }
11120       catch (IOException ioe)
1113        {
11140          logger.log(Level.WARNING, "Problem with '" + file + "'.", ioe);
1115        }
1116        finally
1117        {
11180          IoUtil.close(bw);
11190       }
11200    }
1121  
1122      private void appendItemMessage (Writer bw, final Item item)
1123          throws IOException
1124      {
11250         if (item.isOld())
1126          {
11270             bw.write("Fixed: ");
1128          }
11290         if (item.isNew())
1130          {
11310             bw.write("New: ");
1132          }
11330         bw.write(XmlUtil.escape(item.getMessage()));
11340         if (item.getSeverityReason() != null)
1135          {
11360             bw.write(' ');
11370             bw.write(XmlUtil.escape(item.getSeverityReason()));
1138          }
11390     }
1140  
1141  
1142     /**
1143      * Generates a image related to the severity of the given Item.
1144      * The image links back to the general finding page of the item.
1145      * @param w the writer where to write the output to.
1146      * @param item the item to be documented.
1147      * @param root the relative path from the page generated to the root dir.
1148      * @throws IOException if the datas could not be written to the given
1149      *     writer.
1150      */
1151     private void appendSeverityImage (Writer w, Item item, String root)
1152         throws IOException
1153     {
11540        w.write("<a href='");
11550        w.write(root);
11560        w.write(FindingsSummary.getFindingsSummary()
1157                 .getFindingSummary(item).createFindingDetailFilename());
11580        w.write("'><img border='0' title='");
11590        w.write(String.valueOf(item.getSeverity()));
11600        w.write(" [");
11610        w.write(String.valueOf(item.getOrigin()));
11620        w.write("]' alt='");
11630        w.write(item.getSeverity().toString().substring(0, 1));
11640        w.write("' src='");
11650        w.write(root);
11660        w.write(getImage(item.getSeverity()));
11670        w.write("' /></a>\n");
11680    }
1169  
1170     private String getImage (Severity severity)
1171     {
11720       return "images/icon_" + severity.toString() + ".gif";
1173     }
1174  
1175     private FileSummary createFileSummary (final int linesCount,
1176                                            final String subdir)
1177     {
1178        // Create file summary info
11790       final FileSummary summary = new FileSummary(mClassname, mPackage,
1180              subdir + "/" + mClassname + ".html", linesCount, mCoverageData);
11810       for (final Item item : mCurrentFindings)
1182        {
11830          FindingsSummary.addFinding(item, summary);
11840          if (Origin.COVERAGE.equals(item.getOrigin()))
1185           {
11860             if (item.getCounter() != 0)
1187              {
11880                summary.addCoveredLine();
1189              }
1190              else
1191              {
11920                summary.addViolation(Severity.COVERAGE);
1193              }
1194           }
1195           else
1196           {
11970             summary.addViolation(item.getSeverity());
1198           }
1199        }
12000       return summary;
1201     }
1202  
1203     private void fillFindingsInFile (
1204           org.jcoderz.phoenix.report.jaxb.File data)
1205     {
12060(18)      for (final Item item : (List<Item>) data.getItem())
1207        {
12080          final int lineNumber = item.getLine();
12090          List<Item> itemsInLine = mFindingsInFile.get(lineNumber);
12100          if (itemsInLine == null)
1211           {
12120             itemsInLine = new ArrayList<Item>();
12130             mFindingsInFile.put(lineNumber, itemsInLine);
1214           }
12150          itemsInLine.add(item);
12160       }
12170    }
1218  
1219     /**
1220      * Generates the stylesheet link for html output.
1221      * @param packageName the package of the current generated file. Used
1222      *                    to generate a relative link.
1223      * @param style the style to use (relative to report path or absolute)
1224      * @return the style link as to be placed in the head of the generated
1225      *         html file.
1226      */
1227     private static String createStyle (String packageName, String style)
1228     {
1229 (19)      // TODO: use the default stylesheet if not explicitly specified
12300       String result = "";
12310       if (style != null)
1232        {
1233           final String styleLink;
12340          if (style.indexOf("//") != -1)
1235           { // absolute style
12360             styleLink = style;
1237           }
1238           else
1239           {
12400             styleLink = relativeRoot(packageName, style);
1241           }
12420          result = "<link rel='stylesheet' type='text/css' href='"
1243              + styleLink + "' />";
1244        }
12450       return result;
1246     }
1247  
1248     private Severity errorLevel (int line)
1249     {
12500       Severity severity = Severity.OK;
1251  
12520       final Iterator<Item> active = mActiveItems.iterator();
1253  
12540       while (active.hasNext())
1255        {
12560          final Item item = active.next();
12570          if (item.getEndLine() < line)
1258           {
12590             active.remove();
1260           }
1261           else
1262           {
12630             severity = severity.max(item.getSeverity());
1264           }
12650       }
1266  
1267  
12680       final Iterator<Item> items = findingsInLine(line);
12690       while (items.hasNext())
1270        {
12710          final Item item = items.next();
12720          if (item.getOrigin().equals(Origin.COVERAGE))
1273           {
12740             if (item.getCounter() == 0)
1275              {
12760                severity = severity.max(Severity.COVERAGE);
1277              }
1278           }
1279           else
1280           {
12810             severity = severity.max(item.getSeverity());
12820             if (item.getEndLine() > line)
1283              {
12840                mActiveItems.add(item);
1285              }
1286           }
12870       }
12880       return severity;
1289     }
1290  
1291     private Iterator<Item> findingsInLine (int line)
1292     {
12930       final List<Item> findingsInLine = mFindingsInFile.get(line);
1294        final Iterator<Item> result;
12950       if (findingsInLine == null)
1296        {
12970(20)         result = EmptyIterator.EMPTY_ITERATOR;
1298        }
1299        else
1300        {
13010          result = findingsInLine.iterator();
1302        }
13030       return result;
1304     }
1305  
1306     private String getHits (int line)
1307     {
13080       String hits = "&nbsp;";
1309  
13100       final Iterator<Item> items = findingsInLine(line);
1311  
13120       while (items.hasNext())
1313        {
13140          final Item item = items.next();
13150          if (Origin.COVERAGE.equals(item.getOrigin()))
1316           {
13170             hits = String.valueOf(item.getCounter());
13180             break;
1319           }
13200       }
13210       return hits;
1322     }
1323  
1324     /**
1325      * Fills the 'Note' column for the given line.
1326      * @param line the line under inspection.
1327      * @return the content to be put in the notes column for the given line.
1328      */
1329     private String getIcons (int line)
1330     {
13310       final StringBuilder icons = new StringBuilder();
1332  
1333        // collect relevant findings
13340       final Iterator<Item> items = findingsInLine(line);
13350       while (items.hasNext())
1336        {
13370          final Item item = items.next();
13380          if (Origin.COVERAGE.equals(item.getOrigin()))
1339           {
13400             if (item.getCounter() == 0)
1341              {
13420                items.remove(); // will never see this again!
1343              }
1344           }
13450          else if (item.getSeverity() == Severity.FILTERED
1346               || item.isOld())
1347           {
1348              // not listen with the code but in the global section below the
1349              // code.
1350           }
1351           else
1352           {
13530             mHandledFindings.add(item);
1354              // create the magic icon string with a hyperlink
13550             mGetIconsStringBuffer.setLength(0);
13560             mGetIconsStringBuffer.append("<a href='#FINDING");
13570             mGetIconsStringBuffer.append(mHandledFindings.size());
13580             mGetIconsStringBuffer.append("' title='");
13590             mGetIconsStringBuffer.append(
1360                    XmlUtil.attributeEscape(item.getMessage()));
13610             mGetIconsStringBuffer.append("'><span class='");
13620             mGetIconsStringBuffer.append(item.getOrigin());
13630             mGetIconsStringBuffer.append("note'>(");
13640             mGetIconsStringBuffer.append(mHandledFindings.size());
13650             mGetIconsStringBuffer.append(")</span></a>");
13660             icons.append(mGetIconsStringBuffer);
13670             if (item.isSetColumn()
1368                  && (!item.isSetEndLine()
1369                      || item.getEndLine() - line <= MAX_LINES_WITH_INLINE_MARK))
1370              {
13710                 mFindingsInCurrentLine.add(item);
1372              }
13730            items.remove(); // item was handled fully
1374           }
13750       }
13760       if (icons.length() == 0)
1377        {
13780          icons.append("&nbsp;");
1379        }
13800       return icons.toString();
1381     }
1382  
1383  
1384     /**
1385      * Replaces leading whitespace by a none breakable html string
1386      * (entity).
1387      * Uses <code>mStringBuffer</code> as temporary string buffer.
1388      * @param in The string to modify.
1389      * @return The string with leading white spaces replaced.
1390      */
1391     private String replaceLeadingSpaces (String in)
1392     {
1393        final String result;
13940       if (in == null || in.length() == 0)
1395        {
13960          result = "&nbsp;";
1397        }
13980       else if (in.charAt(0) == ' ' || in.charAt(0) == '\t')
1399        {
14000          mStringBuilder.setLength(0);
1401           int i;
14020(21)         int pos = 0;
14030          for (i = 0; i < in.length()
14040              && (in.charAt(i) == ' ' || in.charAt(i) == '\t'); i++)
1405           {
14060              if (in.charAt(i) == ' ')
1407               {
14080                  mStringBuilder.append("&nbsp;");
14090                  pos++;
1410               }
14110              else if (in.charAt(i) == '\t')
1412               {
14130                  mStringBuilder.append("&nbsp;");
14140                  pos++;
14150                  while (pos % mTabWidth != 0)
1416                   {
14170                      mStringBuilder.append("&nbsp;");
14180                      pos++;
1419                   }
1420               }
1421               
1422           }
14230          mStringBuilder.append(in.substring(i));
14240          result = mStringBuilder.toString();
14250       }
1426        else
1427        {
14280          result = in;
1429        }
14300       return result;
1431     }
1432  
1433     /**
1434      * Adds the file summary to all summary lists.
1435      */
1436     private void addSummary (FileSummary summary)
1437     {
14380       mAllFiles.add(summary);
1439  
14400       List<FileSummary> packageList
1441            = mAllPackages.get(summary.getPackage());
1442  
14430       if (packageList == null)
1444        {
14450          packageList = new ArrayList<FileSummary>();
14460          mAllPackages.put(summary.getPackage(), packageList);
1447        }
14480       packageList.add(summary);
1449  
14500       FileSummary packageSummary
1451           = mPackageSummary.get(summary.getPackage());
14520       if (packageSummary == null)
1453        {
14540          packageSummary = new FileSummary(mPackage);
14550          mPackageSummary.put(summary.getPackage(), packageSummary);
1456        }
14570       packageSummary.add(summary);
14580       mGlobalSummary.add(summary);
14590    }
1460  
1461     private void createPackageSummary (List<FileSummary> pkg)
1462           throws IOException
1463     {
14640       createPackageSummary(new FileSummary.SortByPackage(), pkg);
14650       createPackageSummary(new FileSummary.SortByQuality(), pkg);
14660       createPackageSummary(new FileSummary.SortByCoverage(), pkg);
14670    }
1468  
1469     private void createPackageSummary (Comparator<FileSummary> order,
1470         List<FileSummary> pkg)
1471        throws IOException
1472     {
14730       final String filename = fileNameForOrder(order);
14740       final String packageName = pkg.get(0).getPackage();
14750       final String subdir = packageName.replaceAll("\\.", "/");
14760       final java.io.File dir = new java.io.File(mOutDir, subdir);
14770       FileUtils.mkdirs(dir);
1478  
14790       final BufferedWriter bw = openWriter(dir, filename);
1480        try
1481        {
14820           htmlHeader(bw, packageName, packageName);
14830           bw.write("<h1><a href='" + relativeRoot(packageName, filename)
1484               + "'>Project-Report "
1485               + mProjectName + "</a></h1>");
14860           bw.write("<h2>Packagesummary " + packageName + "</h2>");
14870           createClassListTable(bw, pkg, false, order);
14880           bw.write("</body></html>");
1489        }
1490        finally
1491        {
14920           IoUtil.close(bw);
14930       }
14940    }
1495  
1496     private void createFullSummary ()
1497           throws IOException
1498     {
14990       createFullSummary(new FileSummary.SortByPackage());
15000       createFullSummary(new FileSummary.SortByQuality());
15010       createFullSummary(new FileSummary.SortByCoverage());
15020    }
1503  
1504 (22)   private void createFullSummary (Comparator<FileSummary> order)
1505           throws IOException
1506     {
15070       final String filename = fileNameForOrder(order);
15080       final BufferedWriter bw = openWriter(filename);
1509        try
1510        {
15110           htmlHeader(bw, "Project Report " + mProjectName, "");
1512  
15130           bw.write("<h1>Project Report " + mProjectName + "</h1>");
1514  
15150           bw.write("<table border='0' cellpadding='2' cellspacing='0' "
1516                  + "width='95%'>");
15170           bw.write("<thead><tr><th>");
15180(23)          if (filename != SORT_BY_PACKAGE_INDEX)
1519            {
15200              bw.write("<a href='" + SORT_BY_PACKAGE_INDEX
1521                     + "' title='Sort by name'>");
1522            }
15230           bw.write("Package");
15240           if (filename != SORT_BY_PACKAGE_INDEX)
1525            {
15260              bw.write("</a>");
1527            }
15280           bw.write("</th><th>findings</th>");
15290           bw.write("<th>files</th><th>lines</th>");
15300           if (mCoverageData)
1531            {
15320              bw.write("<th>%</th><th>");
15330              if (filename != SORT_BY_COVERAGE_INDEX)
1534               {
15350                 bw.write("<a href='" + SORT_BY_COVERAGE_INDEX
1536                        + "' title='Sort by coverage'>");
1537               }
15380              bw.write("Coverage");
15390              if (filename != SORT_BY_COVERAGE_INDEX)
1540               {
15410                 bw.write("</a>");
1542               }
15430              bw.write("</th>");
1544            }
15450           bw.write("<th>%</th><th class='remainder'>");
15460           if (filename != SORT_BY_QUALITY_INDEX)
1547            {
15480              bw.write("<a href='" + SORT_BY_QUALITY_INDEX
1549                     + "' title='Sort by quality'>");
1550            }
15510           bw.write("Quality");
15520           if (filename != SORT_BY_QUALITY_INDEX)
1553            {
15540              bw.write("</a>");
1555            }
15560           bw.write("</th></tr></thead>");
15570           bw.write("<tbody>");
15580           bw.write(NEWLINE);
15590           bw.write("<tr class='odd'><td class='classname" + LAST_MARKER + "'>");
15600           bw.write("Overall summary");
15610           bw.write("</td>");
15620           hitsCell(bw, String.valueOf(mGlobalSummary.getNumberOfFindings()),
1563                    true);
15640           hitsCell(bw, String.valueOf(mGlobalSummary.getNumberOfFiles()), true);
15650           hitsCell(bw, String.valueOf(mGlobalSummary.getLinesOfCode()), true);
15660           if (mCoverageData)
1567            {
15680              hitsCell(bw, String.valueOf(mGlobalSummary.getCoverageAsString()),
1569                       true);
15700              bw.write("<td valign='middle' class='hits" + LAST_MARKER
1571                     + "' width='100'>");
15720              bw.write(mGlobalSummary.getCoverageBar());
15730              bw.write("</td>");
1574            }
15750           hitsCell(bw, String.valueOf(mGlobalSummary.getQuality()) + "%", true);
15760           bw.write("<td valign='middle' class='code" + LAST_MARKER
1577                  + "' width='100'>");
15780           bw.write(mGlobalSummary.getPercentBar());
15790           bw.write("</td></tr>");
15800           bw.write(NEWLINE);
15810           bw.write("</tbody>");
15820           bw.write("</table>");
1583  
15840           bw.write("<h1><a href='findings.html'>View by Finding</a></h1>");
15850           bw.write("<h1><a href='age-Build.html'>View by Age per Build</a> "
1586                + "<a href='age-Day.html'> Day</a> "
1587                + "<a href='age-Week.html'> Week</a> "
1588                + "<a href='age-Month.html'> Month</a> "
1589                + "<a href='age-Old.html'> Old</a></h1>");
1590  
15910           bw.write("<h1>Packages</h1>");
15920           bw.write("<table border='0' cellpadding='2' cellspacing='0' "
1593                  + "width='95%'>");
15940           bw.write("<thead><tr><th>Package</th>"
1595               + "<th>findings</th><th>files</th><th>lines</th>");
15960           if (mCoverageData)
1597            {
15980              bw.write("<th>%</th><th>Coverage</th>");
1599            }
16000           bw.write("<th>%</th><th class='remainder'>Quality</th></tr></thead>");
16010           bw.write("<tbody>");
16020           bw.write(NEWLINE);
1603  
16040           final Set<FileSummary> packages = new TreeSet<FileSummary>(order);
16050           packages.addAll(mPackageSummary.values());
1606  
16070           int pos = 0;
16080           final Iterator<FileSummary> i = packages.iterator();
16090           while (i.hasNext())
1610            {
16110              pos++;
16120              final FileSummary pkg = i.next();
16130              final boolean isLast = !i.hasNext();
16140              appendPackageLink(bw, pkg, filename, pos, isLast);
16150           }
16160           bw.write("</tbody></table>\n");
1617  
1618            // findings with no line number...
16190           createUnassignedFindingsTable(bw);
1620  
16210           bw.write("<h1>Java Files</h1>");
16220           createClassListTable(bw, mAllFiles, true, order);
1623  
16240           bw.write("</body></html>");
1625        }
1626        finally
1627        {
16280           IoUtil.close(bw);
16290       }
16300    }
1631  
1632      private void createUnassignedFindingsTable (final BufferedWriter bw)
1633          throws IOException
1634      {
16350         boolean tableOpened = false;
16360         int row = 0;
16370         for (final org.jcoderz.phoenix.report.jaxb.File file : mGlobalFindings)
1638          {
16390(24)            for (final Item item : (List<Item>) file.getItem())
1640               {
16410                 row++;
16420                 if (!tableOpened)
1643                  {
16440                     bw.write("<h1>Unassigned findings</h1>");
16450                     bw.write("<table border='0' cellpadding='0' "
1646                          + "cellspacing='0' width='95%'>");
16470                     tableOpened = true;
1648                  }
16490                 bw.write("<tr class='");
16500                 bw.write(item.getSeverity().toString());
16510                 bw.write(Java2Html.toOddEvenString(row));
16520                 bw.write("'><td class='unassigned-origin'>");
16530                 bw.write(ObjectUtil.toStringOrEmpty(item.getOrigin()));
16540                 bw.write("</td><td class='unassigned-filename'>");
16550                 bw.write(cutPath(file.getName()));
16560                 bw.write("</td><td class='unassigned-data' width='100%'>");
16570                 bw.write(item.getMessage());
16580                 bw.write("</td></tr>");
1659  
16600                 FindingsSummary.addFinding(item, mGlobalSummary);
1661               }
1662           }
16630         if (tableOpened)
1664          {
16650             bw.write("</table>");
1666          }
16670     }
1668  
1669      private void appendPackageLink (final BufferedWriter bw,
1670              final FileSummary pkg, final String filename, final int pos,
1671              final boolean isLast)
1672          throws IOException
1673      {
16740          final String name = pkg.getPackage();
16750          final String subdir = name.replaceAll("\\.", "/");
16760          bw.write("<tr class='" + Java2Html.toOddEvenString(pos)
1677                 + "'><td class='classname");
16780          appendIf(bw, isLast, LAST_MARKER);
16790          bw.write("'><a href='" + subdir + "/" + filename + "'>");
16800          bw.write(pkg.getPackage());
16810          bw.write("</a></td>");
16820          hitsCell(bw, String.valueOf(pkg.getNumberOfFindings()), isLast);
16830          hitsCell(bw, String.valueOf(pkg.getNumberOfFiles()), isLast);
16840          hitsCell(bw, String.valueOf(pkg.getLinesOfCode()), isLast);
16850          if (mCoverageData)
1686           {
16870             hitsCell(bw, String.valueOf(pkg.getCoverageAsString()), isLast);
16880             bw.write("<td valign='middle' class='hits");
16890             appendIf(bw, isLast, LAST_MARKER);
16900             bw.write("' width='100'>");
16910             bw.write(pkg.getCoverageBar());
16920             bw.write("</td>");
1693           }
16940          hitsCell(bw, String.valueOf(pkg.getQuality()) + "%", isLast);
16950          bw.write("<td valign='middle' class='code");
16960          appendIf(bw, isLast, LAST_MARKER);
16970          bw.write("' width='100'>");
16980          bw.write(pkg.getPercentBar());
16990          bw.write("</td></tr>" + NEWLINE);
17000     }
1701  
1702     /**
1703      * Writes a html header to the given output stream.
1704      * @param bw the stream to use for output
1705      * @param title the title of the page. Should be the package name for
1706      *        sub packages.
1707      */
1708     private void htmlHeader (Writer bw, String title, String packageName)
1709          throws IOException
1710     {
17110        bw.write("<?xml version='1.0' encoding='UTF-8'?>" + NEWLINE
1712           + "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" "
1713           + NEWLINE
1714           + "\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">"
1715           + NEWLINE
1716           + "<html xmlns='http://www.w3.org/1999/xhtml' xml:lang='en'"
1717           + " lang='en'>" + NEWLINE
1718           + "<head>" + NEWLINE
1719           + "\t<title>");
17200       bw.write(title);
17210       bw.write("</title>" + NEWLINE
1722           + "\t<meta name='author' content='jCoderZ java2html' />" + NEWLINE);
17230       bw.write(createStyle(packageName, mStyle));
17240       bw.write(NEWLINE + "</head>" + NEWLINE + "<body>" + NEWLINE);
17250    }
1726     
1727     
1728     
1729     
1730  
1731     private Date renderAgePage (
1732         List<Item> findings, Date startDate, ReportInterval periode)
1733         throws IOException
1734     {
17350        final Writer bw
1736             = openWriter(mOutDir, "age-" + periode.toString() + ".html");
17370        htmlHeader(bw, "Finding in " + periode.toString(), "");
1738         
17390        bw.write("<h1><a href='index.html'>View by Classes</a></h1>");
17400        bw.write("<h1><a href='findings.html'>View by Finding</a></h1>");
17410        bw.write("<h1><a href='age-Build.html'>View by Age per Build</a> "
1742             + "<a href='age-Day.html'> Day</a> "
1743             + "<a href='age-Week.html'> Week</a> "
1744             + "<a href='age-Month.html'> Month</a> "
1745             + "<a href='age-Old.html'> Old</a></h1>");
1746         
17470        if (ReportInterval.OLD != periode)
1748         {
17490            bw.write("<h1>Current Findings by " + periode.toString() + "</h1>");
1750         }
1751         else
1752         {
17530            bw.write("<h1>Old findings since " + startDate + "</h1>");
1754         }
1755         
1756         
17570        int numberOfSegments = 0;
17580        Date iStart = null;
1759         try
1760         {
17610            Date iEnd = null;
17620            final List<Item> interval = new ArrayList<Item>();
17630            for (Item i : findings)
1764             {
17650                if (iEnd == null)
1766                 {
17670                    if (i.getSince().before(startDate))
1768                     {
17690                        iStart = getPeriodStart(periode, i.getSince());
17700                        iEnd = getPeriodEnd(periode, i.getSince());
1771                     }
1772                     else
1773                     {
1774                         continue;
1775                     }
1776                 }
17770                if (i.getSince().before(iStart))
1778                 {
17790                    Collections.sort(interval, new ItemSeverityComperator());
17800                    createSegment(bw, interval, iStart, iEnd);
17810                    interval.clear();
17820                    numberOfSegments++;
17830                    if (numberOfSegments > NUMBER_OF_AGE_SEGMENTS)
1784                     {
17850                        break;
1786                     }
17870                    iStart = getPeriodStart(periode, i.getSince());
17880                    iEnd = getPeriodEnd(periode, i.getSince());
1789                 }
17900                interval.add(i);
1791             }
1792             
17930            if (!interval.isEmpty())
1794             {
17950                Collections.sort(interval, new ItemSeverityComperator());
17960                createSegment(bw, interval, iStart, iEnd);
17970                interval.clear();
1798             }
1799         }
1800         finally
1801         {
18020            IoUtil.close(bw);
18030        }
18040        return iStart == null ? startDate : iStart;
1805     }
1806     
1807     
1808  
1809     private void createSegment (
1810         Writer bw, List<Item> items, Date start, Date end)
1811         throws IOException
1812     {
18130        if (!items.isEmpty())
1814         {
18150            openTable(bw, "Findings " + start
1816                 + (start.equals(end) ? "" : (" - " + end))
1817                 + " " + items.size());
1818         }
18190        int pos = 0;
18200        for (final Item item : items)
1821         {
18220            createRow(bw, getFile(item), item, pos);
18230            pos++;
1824         }
18250        if (!items.isEmpty())
1826         {
18270            closeTable(bw);
1828         }
1829         
18300    }
1831  
1832      private org.jcoderz.phoenix.report.jaxb.File getFile (Item item)
1833      {
18340         return mItemToFileMap.get(item);
1835      }
1836  
1837      /*private*/ static Date getPeriodEnd (
1838          ReportInterval periode, Date actualStart)
1839      {
1840         final Date result;
1841100        if (ReportInterval.BUILD == periode)
1842         {
18430            result = actualStart.plus(1);
1844         }
1845100        else if (ReportInterval.DAY == periode)
1846         {
18470            final Calendar cal
1848                 = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
18490            cal.setTimeInMillis(actualStart.getTime());
18500            cal.set(Calendar.HOUR_OF_DAY, 0);
18510            cal.set(Calendar.MINUTE, 0);
18520            cal.set(Calendar.SECOND, 0);
18530            cal.set(Calendar.MILLISECOND, 0);
18540            cal.add(Calendar.DAY_OF_MONTH, 1);
18550            result = new Date(cal.getTimeInMillis());
18560        }
1857100        else if (ReportInterval.WEEK == periode)
1858         {
1859100            final Calendar cal
1860                 = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
1861100            cal.setFirstDayOfWeek(Calendar.MONDAY);
1862100            cal.setTimeInMillis(actualStart.getTime());
1863100            cal.set(Calendar.HOUR_OF_DAY, 0);
1864100            cal.set(Calendar.MINUTE, 0);
1865100            cal.set(Calendar.SECOND, 0);
1866100            cal.set(Calendar.MILLISECOND, 0);
1867             
1868100            int dayOffset
1869                 = Calendar.MONDAY - cal.get(Calendar.DAY_OF_WEEK);
1870100            if (dayOffset <= 0)
1871             {
1872100                dayOffset += cal.getMaximum(Calendar.DAY_OF_WEEK);
1873             }
1874100            cal.add(Calendar.DAY_OF_MONTH, dayOffset);
1875100            result = new Date(cal.getTimeInMillis());
1876100        }
18770        else if (ReportInterval.MONTH == periode)
1878         {
18790            final Calendar cal
1880                 = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
18810            cal.setFirstDayOfWeek(Calendar.MONDAY);
18820            cal.setTimeInMillis(actualStart.getTime());
18830            cal.set(Calendar.HOUR_OF_DAY, 0);
18840            cal.set(Calendar.MINUTE, 0);
18850            cal.set(Calendar.SECOND, 0);
18860            cal.set(Calendar.MILLISECOND, 0);
18870            cal.set(Calendar.DAY_OF_MONTH, 1);
18880            cal.add(Calendar.MONTH, 1);
18890            result = new Date(cal.getTimeInMillis());
18900        }
18910        else if (ReportInterval.OLD == periode)
1892         {
18930            result = actualStart;
1894         }
1895         else
1896         {
18970            Assert.fail("Unsupported value for ReportInterval " + periode);
18980            result = null; // never reached
1899         }
1900100        return result;
1901     }
1902  
1903      /*private*/ static Date getPeriodStart (ReportInterval periode, Date pos)
1904      {
1905         final Date result;
1906100        if (ReportInterval.BUILD == periode)
1907         {
19080            result = pos;
1909         }
1910100        else if (ReportInterval.DAY == periode)
1911         {
19120            final Calendar cal
1913                 = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
19140            cal.setTimeInMillis(pos.getTime());
19150            cal.set(Calendar.HOUR_OF_DAY, 0);
19160            cal.set(Calendar.MINUTE, 0);
19170            cal.set(Calendar.SECOND, 0);
19180            cal.set(Calendar.MILLISECOND, 0);
19190            result = new Date(cal.getTimeInMillis());
19200        }
1921100        else if (ReportInterval.WEEK == periode)
1922         {
1923100            final Calendar cal
1924                 = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
1925100            cal.setFirstDayOfWeek(Calendar.MONDAY);
1926100            cal.setTimeInMillis(pos.getTime());
1927100            cal.set(Calendar.HOUR_OF_DAY, 0);
1928100            cal.set(Calendar.MINUTE, 0);
1929100            cal.set(Calendar.SECOND, 0);
1930100            cal.set(Calendar.MILLISECOND, 0);
1931100            int dayOffset
1932                 = Calendar.MONDAY - cal.get(Calendar.DAY_OF_WEEK);
1933100            if (dayOffset > 0)
1934             {
19350                dayOffset -= cal.getMaximum(Calendar.DAY_OF_WEEK);
1936             }
1937100            cal.add(Calendar.DAY_OF_MONTH, dayOffset);
1938100            result = new Date(cal.getTimeInMillis());
1939100        }
19400        else if (ReportInterval.MONTH == periode)
1941         {
19420            final Calendar cal
1943                 = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
19440            cal.setFirstDayOfWeek(Calendar.MONDAY);
19450            cal.setTimeInMillis(pos.getTime());
19460            cal.set(Calendar.HOUR_OF_DAY, 0);
19470            cal.set(Calendar.MINUTE, 0);
19480            cal.set(Calendar.SECOND, 0);
19490            cal.set(Calendar.MILLISECOND, 0);
19500            cal.set(Calendar.DAY_OF_MONTH, 1);
19510            result = new Date(cal.getTimeInMillis());
19520        }
19530        else if (ReportInterval.OLD == periode)
1954         {
19550            result = Date.OLD_DATE;
1956         }
1957         else
1958         {
19590            Assert.fail("Unsupported value for ReportInterval " + periode);
19600            result = null; // never reached
1961         }
1962100        return result;
1963     }
1964      
1965  
1966     private static String relativeRoot (String currentPackage)
1967     {
19680       return relativeRoot(currentPackage, "index.html");
1969     }
1970  
1971     private static String relativeRoot (String currentPackage, String page)
1972     {
19730       final StringBuilder rootDir = new StringBuilder();
1974  
19750       if (currentPackage.length() != 0)
1976        {
19770          rootDir.append("../");
1978        }
1979  
19800       for (int i = 0; i < currentPackage.length(); i++)
1981        {
19820          if (currentPackage.charAt(i) == '.')
1983           {
19840             rootDir.append("../");
1985           }
1986        }
19870       rootDir.append(page);
1988  
19890       return rootDir.toString().replaceAll("//", "/");
1990     }
1991  
1992     private String cutPath (String fileName)
1993     {
19940       String result = ObjectUtil.toStringOrEmpty(fileName);
19950       if (fileName != null && fileName.toLowerCase(Constants.SYSTEM_LOCALE)
1996                .startsWith(mProjectHome.toLowerCase(Constants.SYSTEM_LOCALE)))
1997        {
19980          result = fileName.substring(mProjectHome.length());
1999        }
20000       return result;
2001     }
2002  
2003     private String getCvsLink (String absFile)
2004     {
2005        String result;
20060       if (mWebVcBase == null || mProjectHome == null)
2007        {
20080          result = null;
2009        }
20100       else if (absFile.toLowerCase(Constants.SYSTEM_LOCALE)
2011                .startsWith(mProjectHome.toLowerCase(Constants.SYSTEM_LOCALE)))
2012        {
20130          result = absFile.substring(mProjectHome.length());
2014        }
2015        else
2016        {
20170          final String absPath
2018               = (new java.io.File(absFile)).getAbsolutePath();
20190          if (absPath.toLowerCase(Constants.SYSTEM_LOCALE).startsWith(
2020               mProjectHome.toLowerCase(Constants.SYSTEM_LOCALE)))
2021           {
20220             result = absPath.substring(mProjectHome.length());
2023           }
2024           else
2025           {
20260             result = null;
2027           }
2028  
2029        }
20300       if (result != null)
2031        {
20320          result = mWebVcBase + result + mWebVcSuffix;
20330          result = result.replaceAll("\\\\", "/");
2034        }
20350       return result;
2036     }
2037  
2038     /**
2039      * Create the marked html output for the current line.
2040      * @param out the writer to generate the output to.
2041      * @param src the source to read the line data from.
2042      * @throws IOException if IO operations fail writing to the
2043      *   given writer.
2044      */
2045     private void createCodeLine (Writer out, Syntax src)
2046         throws IOException
2047     {
20480        String text = src.nextToken();
20490        String lastTokenType = null;
20500        Item lastFinding = null;
20510        boolean isFirst = true;
20520        while (null != src.getCurrentTokenType())
2053         {
20540            final Item currentFinding
2055                 = getCurrentFinding(text, src.getCurrentLineNumber(),
2056                     src.getCurrentLinePos(), src.getCurrentTokenLength());
20570            if (currentFinding != lastFinding)
2058             {
20590                if (lastTokenType != null)
2060                 {
20610                    out.write("</span>"); // close token type
20620                    lastTokenType = null;
2063                 }
20640                if (lastFinding != null)
2065                 {
20660                    out.write("</span>"); // close last finding
2067                 }
20680                if (currentFinding != null)
2069                 {
20700                    out.write("<span class='finding-");
20710                    out.write(currentFinding.getSeverity().toString());
20720                    out.write("' title='");
20730                    out.write(
2074                         XmlUtil.attributeEscape(currentFinding.getMessage()));
20750                    out.write("'>");
2076                 }
20770                lastFinding = currentFinding;
2078             }
20790            final String tokenType = src.getCurrentTokenType();
20800            if (!tokenType.equals(lastTokenType))
2081             {
20820                if (lastTokenType != null)
2083                 {
20840                    out.write("</span>");
20850                    isFirst = false;
2086                 }
20870                out.write("<span class='code-");
20880                out.write(tokenType);
20890                out.write("'>");
2090             }
20910            String xml = XmlUtil.escape(text);
20920            if (isFirst)
2093             {
20940                xml = replaceLeadingSpaces(xml);
2095             }
20960            out.write(xml);
2097             
20980            lastTokenType = tokenType;
20990            text = src.nextToken();
21000        }
21010        if (lastTokenType != null)
2102         {
21030            out.write("</span>");
2104         }
21050        if (lastFinding != null)
2106         {
21070            out.write("</span>");
2108         }
21090    }
2110  
2111 (25)   private Item getCurrentFinding (
2112         String text, int currentLineNumber, int currentLinePos, int tokenLen)
2113     {
21140        Item theFinding = null;
21150        final Iterator<Item> i = mFindingsInCurrentLine.iterator();
21160        while (i.hasNext())
2117         {
21180            final Item finding = i.next();
2119             
21200            if (finding.getEndLine() < currentLineNumber
2121                 && finding.getLine() < currentLineNumber)
2122             {
21230                i.remove();
21240                continue;
2125             }
2126             
21270            final int start =
2128                 finding.getLine() == currentLineNumber
2129                     ? finding.getColumn() : 0;
21300            int end =
2131                 finding.getEndLine() == currentLineNumber
2132                     ? finding.getEndColumn() : ABSOLUTE_END_OF_LINE;
21330            if (finding.getEndLine() == 0)
2134             {
21350                end = finding.getEndColumn();
2136             }
2137             
2138 (26)           // TODO Add code to find pos by symbol!
2139                  // finding at current pos
21400            if ((start == currentLinePos && end == 0)
2141                  // or in range that contains the current pos
2142                 || (start <= currentLinePos && end >= currentLinePos)
2143                  // or inside the token
2144                 || (start >= currentLinePos
2145                     && start < currentLinePos + tokenLen))
2146             {
21470                if (theFinding == null
2148                     || finding.getSeverity().compareTo(
2149                         theFinding.getSeverity()) > 0)
2150                 {
21510                    theFinding = finding;
2152                 }
2153             }
21540        }
21550        return theFinding;
2156     }
2157  
2158  
2159     /**
2160      * Creates a list of all classes as html table and appends it to the
2161      * given bw.
2162      */
2163     private void createClassListTable (BufferedWriter bw,
2164         Collection<FileSummary> files, boolean fullPackageNames,
2165         Comparator<FileSummary> order)
2166           throws IOException
2167     {
2168        // Do not create a table if no classes are here.
21690       if (!files.isEmpty())
2170        {
21710          final String filename = fileNameForOrder(order);
21720          final FileSummary[] summaries =
2173              files.toArray(new FileSummary[files.size()]);
2174  
21750          Arrays.sort(summaries, order);
2176  
21770          bw.write("<table border='0' cellpadding='2' cellspacing='0' "
2178                 + "width='95%'>");
21790          bw.write("<thead><tr><th>");
21800(27)         if (filename != SORT_BY_PACKAGE_INDEX)
2181           {
21820             bw.write("<a href='");
21830             bw.write(SORT_BY_PACKAGE_INDEX);
21840             bw.write("' title='Sort by name'>");
2185           }
21860          bw.write("Classfile");
21870          if (filename != SORT_BY_PACKAGE_INDEX)
2188           {
21890             bw.write("</a>");
2190           }
21910          bw.write("</th><th>findings</th><th>lines</th>");
21920          if (mCoverageData)
2193           {
21940             bw.write("<th>%</th><th>");
21950             if (filename != SORT_BY_COVERAGE_INDEX)
2196              {
21970                bw.write("<a href='");
21980                bw.write(SORT_BY_COVERAGE_INDEX);
21990                bw.write("' title='Sort by coverage'>");
2200              }
22010             bw.write("Coverage");
22020             if (filename != SORT_BY_COVERAGE_INDEX)
2203              {
22040                bw.write("</a>");
2205              }
22060             bw.write("</th>");
2207           }
22080          bw.write("<th>%</th><th class='remainder'>");
22090          if (filename != SORT_BY_QUALITY_INDEX)
2210           {
22110             bw.write("<a href='");
22120             bw.write(SORT_BY_QUALITY_INDEX);
22130             bw.write("' title='Sort by quality'>");
2214           }
22150          bw.write("Quality");
22160          if (filename != SORT_BY_QUALITY_INDEX)
2217           {
22180             bw.write("</a>");
2219           }
22200          bw.write("</th></tr></thead>");
22210          bw.write("<tbody>");
22220          bw.write(NEWLINE);
2223  
22240          int pos = 0;
22250          final Iterator<FileSummary> i = Arrays.asList(summaries).iterator();
22260          while (i.hasNext())
2227           {
22280             pos++;
22290             final FileSummary file = i.next();
22300             final boolean isLast = !i.hasNext();
22310             appendClassLink(bw, file, fullPackageNames, pos, isLast);
22320          }
22330          bw.write("</tbody>");
22340          bw.write("</table>");
22350       }
2236        else
2237        {
22380          bw.write("EMPTY!?");
2239        }
22400    }
2241  
2242      private void appendClassLink (BufferedWriter bw, final FileSummary file,
2243              boolean fullPackageNames, int pos, final boolean isLast)
2244          throws IOException
2245      {
2246          final String name;
2247          final String link;
22480         if (fullPackageNames)
2249          {
22500            name = file.getPackage() + '.' + file.getClassName();
22510            link = file.getPackage().replaceAll("\\.", "/") + "/"
2252                     + file.getClassName() + ".html";
2253          }
2254          else
2255          {
22560            name = file.getClassName();
22570            link = file.getClassName() + ".html";
2258          }
22590         bw.write("<tr class='");
22600         bw.write(Java2Html.toOddEvenString(pos));
22610         bw.write("'><td class='classname");
22620         appendIf(bw, isLast, LAST_MARKER);
22630         bw.write("'><a href='");
22640         bw.write(link);
22650         bw.write("'>");
22660         bw.write(name);
22670         bw.write("</a></td>");
22680         hitsCell(bw, String.valueOf(file.getNumberOfFindings()), isLast);
22690         hitsCell(bw, String.valueOf(file.getLinesOfCode()), isLast);
22700         if (mCoverageData)
2271          {
22720            hitsCell(bw, file.getCoverageAsString(), isLast);
22730            bw.write("<td valign='middle' class='hits");
22740            appendIf(bw, isLast, LAST_MARKER);
22750            bw.write("' width='100'>");
22760            bw.write(file.getCoverageBar());
22770            bw.write("</td>");
2278          }
22790         hitsCell(bw, String.valueOf(file.getQuality()) + "%", isLast);
22800         bw.write("<td valign='middle' class='code");
22810         appendIf(bw, isLast, LAST_MARKER);
22820         bw.write("' width='100'>");
22830         bw.write(file.getPercentBar());
22840         bw.write("</td></tr>");
22850         bw.write(NEWLINE);
22860     }
2287  
2288     private static String fileNameForOrder (Comparator<FileSummary> order)
2289     {
2290        String filename;
22910(28)      if (order instanceof FileSummary.SortByPackage)
2292        {
22930          filename = SORT_BY_PACKAGE_INDEX;
2294        }
22950       else if (order instanceof FileSummary.SortByQuality)
2296        {
22970          filename = SORT_BY_QUALITY_INDEX;
2298        }
22990       else if (order instanceof FileSummary.SortByCoverage)
2300        {
23010          filename = SORT_BY_COVERAGE_INDEX;
2302        }
2303        else
2304        {
23050          throw new RuntimeException("Order not expected " + order);
2306        }
23070       return filename;
2308     }
2309  
2310     /**
2311      * Generates the content (including surounding elements) of a cell
2312      * of hits type.
2313      * @param bw the writer to write to.
2314      * @param content the cell contend
2315      * @param isLast true if this sell is in the last row.
2316      * @throws IOException if the write fails.
2317      */
2318     private static void hitsCell (BufferedWriter bw,
2319           String content, boolean isLast)
2320           throws IOException
2321     {
23220       bw.write("<td valign='middle' class='hits");
23230       appendIf(bw, isLast, LAST_MARKER);
23240       bw.write("'>");
23250       bw.write(content);
23260       bw.write("</td>");
23270    }
2328  
2329     private static void appendIf (BufferedWriter bw, boolean condition,
2330           String str)
2331           throws IOException
2332     {
23330       if (condition)
2334        {
23350          bw.write(str);
2336        }
23370    }
2338  
2339     private BufferedWriter openWriter (String filename)
2340         throws IOException
2341     {
23420        return openWriter(mOutDir, filename);
2343     }
2344  
2345     private BufferedWriter openWriter (File dir, String filename)
2346         throws IOException
2347     {
23480        FileOutputStream fos = null;
23490        OutputStreamWriter osw = null;
23500        BufferedWriter result = null;
2351         try
2352         {
23530            fos = new FileOutputStream(new java.io.File(dir, filename));
23540            osw = new OutputStreamWriter(fos, Constants.ENCODING_UTF8);
23550            result = new BufferedWriter(osw);
2356         }
23570        catch (RuntimeException ex)
2358         {
23590            IoUtil.close(result);
23600            IoUtil.close(osw);
23610            IoUtil.close(fos);
23620            throw ex;
2363         }
23640        catch (IOException ex)
2365         {
23660            IoUtil.close(result);
23670            IoUtil.close(osw);
23680            IoUtil.close(fos);
23690            throw ex;
23700        }
23710        return result;
2372     }
2373     
2374     private static String createReportLink (
2375         org.jcoderz.phoenix.report.jaxb.File file)
2376     {
23770        final String pkg
2378             = StringUtil.isEmptyOrNull(file.getPackage())
2379                 ? UNNAMED_PACKAGE_NAME : file.getPackage();
23800        String clazzName = ObjectUtil.toStringOrEmpty(file.getClassname());
2381  
2382         // If no class name is reported take the filename.
23830        if (StringUtil.isEmptyOrNull(clazzName))
2384         {
23850            if (!StringUtil.isEmptyOrNull(file.getName()))
2386             {
23870                clazzName = new File(file.getName()).getName();
2388             }
2389             else
2390             {
23910                clazzName = "";
2392             }
2393         }
2394         
23950        final String subdir = pkg.replaceAll("\\.", "/");
2396         
23970        return subdir + "/" + clazzName + ".html";
2398     }
2399     
24000    private static class ItemSeverityComperator
2401         implements Comparator<Item>, Serializable
2402     {
2403         private static final long serialVersionUID = 1L;
2404         
2405      public int compare (Item o1, Item o2)
2406      {
24070         int result = 0;
24080         result = -(o1.getSeverity().compareTo(o2.getSeverity()));
24090         if (result == 0)
2410          {
24110             result = o1.getSince().compareTo(o2.getSince());
2412          }
24130         if (result == 0)
2414          {
24150             if (o1.getLine() > o2.getLine())
2416              {
24170                 result = 1;
2418              }
24190             else if (o1.getLine() < o2.getLine())
2420              {
24210                 result = -1;
2422              }
2423              else
2424              {
24250                 result = 0;
2426              }
2427          }
24280         if (result == 0)
2429          {
24300             result = o1.getFindingType().compareTo(o2.getFindingType());
2431          }
24320         return result;
2433      }
2434     }
2435     
24360     private static class ItemAgeComperator
2437          implements Comparator<Item>, Serializable
2438      {
2439          private static final long serialVersionUID = 1L;
2440  
2441          public int compare (Item o1, Item o2)
2442          {
24430             final Date since1
2444                  = o1.isSetSince() ? o1.getSince() : Date.OLD_DATE;
24450             final Date since2
2446                  = o2.isSetSince() ? o2.getSince() : Date.OLD_DATE;
24470             return since1.compareTo(since2);
2448          }
2449      }
2450  }

Findings in this File

i (1) 84 : 0 Comment matches to-do format '(TODO|FIXME|CHECKME)'.
i (2) 85 : 0 Comment matches to-do format '(TODO|FIXME|CHECKME)'.
i (3) 86 : 0 Comment matches to-do format '(TODO|FIXME|CHECKME)'.
i (4) 87 : 0 Comment matches to-do format '(TODO|FIXME|CHECKME)'.
d (5) 454 : 19 [deprecation] setValidating(boolean) in javax.xml.bind.Unmarshaller has been deprecated
c (6) 461 : 73 [unchecked] unchecked cast found : java.util.List required: java.util.List<org.jcoderz.phoenix.report.jaxb.File>
c (7) 537 : 75 [unchecked] unchecked cast found : java.util.List required: java.util.List<org.jcoderz.phoenix.report.jaxb.File>
c (8) 539 : 52 [unchecked] unchecked cast found : java.util.List required: java.util.List<org.jcoderz.phoenix.report.jaxb.Item>
c (9) 561 : 74 [unchecked] unchecked cast found : java.util.List required: java.util.List<org.jcoderz.phoenix.report.jaxb.File>
c (10) 563 : 50 [unchecked] unchecked cast found : java.util.List required: java.util.List<org.jcoderz.phoenix.report.jaxb.Item>
c (11) 683 : 4 Cyclomatic Complexity is 18 (max allowed is 12).
c (12) 825 : 75 [unchecked] unchecked cast found : java.util.List required: java.util.List<org.jcoderz.phoenix.report.jaxb.File>
c (13) 827 : 55 [unchecked] unchecked cast found : java.util.List required: java.util.List<org.jcoderz.phoenix.report.jaxb.Item>
c (14) 851 : 75 [unchecked] unchecked cast found : java.util.List required: java.util.List<org.jcoderz.phoenix.report.jaxb.File>
c (15) 853 : 55 [unchecked] unchecked cast found : java.util.List required: java.util.List<org.jcoderz.phoenix.report.jaxb.Item>
d (16) 921 : 4 Method length is 198 lines (max allowed is 100).
d (17) 930 : 43 [unchecked] unchecked conversion found : java.util.List required: java.util.Collection<? extends org.jcoderz.phoenix.report.jaxb.Item>
c (18) 1206 : 55 [unchecked] unchecked cast found : java.util.List required: java.util.List<org.jcoderz.phoenix.report.jaxb.Item>
i (19) 1229 : 0 Comment matches to-do format '(TODO|FIXME|CHECKME)'.
d (20) 1297 : 32 [unchecked] unchecked conversion found : java.util.Iterator required: java.util.Iterator<org.jcoderz.phoenix.report.jaxb.Item>
w (21) 1402 : 0 Method org.jcoderz.phoenix.report.Java2Html.replaceLeadingSpaces(String) assigns a variable in a larger scope then is needed
d (22) 1504 : 4 Method length is 125 lines (max allowed is 100).
i (23) 1518 : 0 Comparison of String objects using == or != in org.jcoderz.phoenix.report.Java2Html.createFullSummary(Comparator)
c (24) 1639 : 61 [unchecked] unchecked cast found : java.util.List required: java.util.List<org.jcoderz.phoenix.report.jaxb.Item>
c (25) 2111 : 4 Cyclomatic Complexity is 15 (max allowed is 12).
i (26) 2138 : 0 Comment matches to-do format '(TODO|FIXME|CHECKME)'.
i (27) 2180 : 0 Comparison of String objects using == or != in org.jcoderz.phoenix.report.Java2Html.createClassListTable(BufferedWriter, Collection, boolean, Comparator)
w (28) 2291 : 0 Method org.jcoderz.phoenix.report.Java2Html.fileNameForOrder(Comparator) uses instanceof on multiple types to arbitrate logic