root/trunk/src/java/org/jcoderz/phoenix/report/Java2Html.java

Revision 1514, 76.7 kB (checked in by mgriffel, 3 years ago)

#74: Added missing icon for severity "ok"

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
Line 
1/*
2 * $Id$
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 */
33package org.jcoderz.phoenix.report;
34
35import java.io.BufferedWriter;
36import java.io.File;
37import java.io.FileInputStream;
38import java.io.FileNotFoundException;
39import java.io.FileOutputStream;
40import java.io.IOException;
41import java.io.InputStream;
42import java.io.OutputStream;
43import java.io.OutputStreamWriter;
44import java.io.Serializable;
45import java.io.Writer;
46import java.nio.charset.Charset;
47import java.util.ArrayList;
48import java.util.Arrays;
49import java.util.Calendar;
50import java.util.Collection;
51import java.util.Collections;
52import java.util.Comparator;
53import java.util.HashMap;
54import java.util.HashSet;
55import java.util.Iterator;
56import java.util.LinkedList;
57import java.util.List;
58import java.util.Map;
59import java.util.Set;
60import java.util.TimeZone;
61import java.util.TreeSet;
62import java.util.logging.Level;
63import java.util.logging.Logger;
64
65import javax.xml.bind.JAXBContext;
66import javax.xml.bind.JAXBException;
67import javax.xml.bind.Unmarshaller;
68
69import org.jcoderz.commons.types.Date;
70import org.jcoderz.commons.util.ArraysUtil;
71import org.jcoderz.commons.util.Assert;
72import org.jcoderz.commons.util.Constants;
73import org.jcoderz.commons.util.EmptyIterator;
74import org.jcoderz.commons.util.FileUtils;
75import org.jcoderz.commons.util.IoUtil;
76import org.jcoderz.commons.util.LoggingUtils;
77import org.jcoderz.commons.util.ObjectUtil;
78import org.jcoderz.commons.util.StringUtil;
79import org.jcoderz.commons.util.XmlUtil;
80import org.jcoderz.phoenix.report.jaxb.Item;
81import org.jcoderz.phoenix.report.jaxb.Report;
82
83/**
84 * TODO: Link to current build
85 * TODO: Link to CC home
86 * TODO: Add @media printer???
87 * TODO: Refactor, split class.
88 *
89 * @author Andreas Mandel
90 */
91public 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. */
97   private static final String[] PATTERN = {"odd", "even"};
98
99   /** Size of the pattern. */
100   private static final int PATTERN_SIZE = PATTERN.length;
101
102   /** Name of this class. */
103   private static final String CLASSNAME = Java2Html.class.getName();
104
105   /** The logger used for technical logging inside this class. */
106   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. */
140   private final List<FileSummary> mAllFiles
141       = new ArrayList<FileSummary>();
142
143   /** Map of package name + FileSummary for this package. */
144   private final Map<String, FileSummary> mPackageSummary
145       = new HashMap<String, FileSummary>();
146   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    */
152   private final Map<Integer, List<Item>> mFindingsInFile
153       = new HashMap<Integer, List<Item>>();
154   private final Set<Item> mFindingsInCurrentLine
155       = new HashSet<Item>();
156   private final List<Item> mCurrentFindings = new ArrayList<Item>();
157   private final List<Item> mHandledFindings
158       = new ArrayList<Item>();
159   /** List of findings with no (available) file assignment */
160   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
166   private String mProjectName = "";
167   private String mWebVcBase = null;
168   private String mWebVcSuffix = "";
169   private String mProjectHome;
170   private String mTimestamp = null;
171   private String mStyle = DEFAULT_STYLESHEET; // the CSS stuff to use
172   private String mClassname;
173   private String mPackage;
174   private String mPackageBase;
175   private final StringBuilder mStringBuilder
176       = new StringBuilder();
177   /** String buffer to be used by the getIcons method. */
178   private final StringBuilder mGetIconsStringBuffer
179       = new StringBuilder();
180
181   private java.io.File mInputData;
182   private java.io.File mOutDir;
183
184   private boolean mCoverageData = true;
185
186   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    */
192   private final List<Item> mActiveItems
193       = new LinkedList<Item>();
194
195   private Charset mCharSet = Charset.defaultCharset();
196
197   private int mTabWidth = DEFAULT_TAB_WIDTH;
198
199   /** The full Report. */
200   private Report mReport;
201
202   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
213   {
214      mProjectHome = new java.io.File(".").getCanonicalPath();
215   }
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   {
227      final Java2Html engine = new Java2Html();
228
229      engine.parseArguments(args);
230      // Turn on logging
231      Logger.getLogger("org.jcoderz.phoenix.report").setLevel(Level.FINEST);
232      engine.process();
233   }
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   {
243       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   {
253      mPackageBase = packageBase;
254      logger.config("Package base set to '" + mPackageBase + "'.");
255   }
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   {
264      mTimestamp = timestamp;
265      logger.config("Timestamp set to '" + mTimestamp + "'.");
266   }
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   {
276      mCoverageData = coverageDataAvailable;
277      logger.config("Coverage data set to '" + mCoverageData + "'.");
278   }
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   {
288      mStyle = style;
289      logger.config("Style set to '" + mStyle + "'.");
290   }
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   {
300      logger.config("Wiki base set to '" + wikiBase + "'.");
301      System.getProperties().setProperty(WIKI_BASE_PROPERTY,
302            wikiBase);
303   }
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   {
312      mWebVcBase = cvsBase;
313      logger.config("CVS base set to '" + mWebVcBase + "'.");
314   }
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   {
322       mWebVcSuffix = suffix;
323       logger.config("CVS suffix set to '" + suffix + "'.");
324   }
325
326   /**
327    * Returns the name of the project.
328    * @return the name of the project.
329    */
330   public String getProjectName ()
331   {
332      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   {
343      Assert.notNull(name, "name");
344      mProjectName = name;
345      logger.config("Project name set to '" + getProjectName() + "'.");
346   }
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   {
355       Assert.notNull(tabWidth, "width");
356       mTabWidth = Integer.parseInt(tabWidth);
357       logger.config("Source tab width set to '" + mTabWidth + "'.");
358   }
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   {
367       mLogLevel = Level.parse(loglevel);
368       LoggingUtils.setGlobalHandlerLogLevel(Level.ALL);
369       logger.fine("Setting log level: " + mLogLevel);
370       logger.setLevel(mLogLevel);
371   }
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   {
379      Assert.notNull(charset, "charset");
380      mCharSet = charset;
381      logger.config("Source charset set to '" + charset + "'.");
382   }
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   {
391     final java.io.File projectHomeFile = file.getCanonicalFile();
392     mProjectHome = projectHomeFile.getCanonicalPath();
393     if (!projectHomeFile.isDirectory())
394     {
395        throw new RuntimeException("'projectHome' must be a directory '"
396              + projectHomeFile + "'.");
397     }
398     logger.config("Using project home " + mProjectHome + ".");
399   }
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   {
408     mInputData = file.getCanonicalFile();
409     if (!mInputData.canRead())
410     {
411        throw new RuntimeException("Can not read report file '"
412              + mInputData + "'.");
413     }
414     logger.config("Using report file " + mInputData + ".");
415   }
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   {
425     mOutDir = dir.getCanonicalFile();
426     if (!mOutDir.exists())
427     {
428        if (!mOutDir.mkdir())
429        {
430           throw new RuntimeException("Could not create 'outDir' '"
431                 + mOutDir + "'.");
432        }
433     }
434     if (!mOutDir.isDirectory())
435     {
436        throw new RuntimeException("'outDir' must be a directory '"
437              + mOutDir + "'.");
438     }
439     logger.config("Using out dir " + mOutDir + ".");
440   }
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   {
450      final JAXBContext jaxbContext
451            = JAXBContext.newInstance("org.jcoderz.phoenix.report.jaxb",
452                  this.getClass().getClassLoader());
453      final Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
454      unmarshaller.setValidating(true);
455      mReport = (Report) unmarshaller.unmarshal(mInputData);
456      mGlobalSummary = new FileSummary();
457
458      initialiteFindingTypes();
459     
460      for (final org.jcoderz.phoenix.report.jaxb.File file
461          : (List<org.jcoderz.phoenix.report.jaxb.File>) mReport.getFile())
462      {
463         try
464         {
465             if (file.getName() != null)
466             {
467                 java2html(new java.io.File(file.getName()), file);
468             }
469             else
470             {
471                 mGlobalFindings.add(file);
472             }
473         }
474         catch (Exception ex)
475         {
476            if (file.getItem().isEmpty())
477            {
478                logger.log(Level.FINE,
479                    "No report for file without items '" + file.getName() 
480                        + "'.", ex);
481            }
482            else
483            {
484                logger.log(Level.SEVERE,
485                    "Failed to generate report for '" + file.getName() 
486                    + "'.", ex);
487                mGlobalFindings.add(file);
488            }
489         }
490      }
491
492      // create package summary
493      for (final List<FileSummary> pkg : mAllPackages.values())
494      {
495         createPackageSummary(pkg);
496      }
497      createFullSummary();
498
499      createFindingsSummary();
500      createPerFindingSummary();
501      createAgeSummary();
502
503      logger.fine("Charts.");
504
505      try
506      {
507          final StatisticCollector sc;
508          if (mPackageBase == null)
509          {
510            sc = new StatisticCollector(mReport, mOutDir, mTimestamp);
511          }
512          else
513          {
514            sc = new StatisticCollector(
515                mReport, mPackageBase, mOutDir, mTimestamp);
516          }
517          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
521      catch (Exception ex)
522      {
523          logger.log(Level.SEVERE, 
524              "Failed to create charts for report. " 
525              + ex.getMessage(), ex);
526      }
527
528      copyStylesheet();
529      copyIcons();
530
531      logger.fine("Done.");
532   }
533
534    private void initialiteFindingTypes ()
535    {
536        for (final org.jcoderz.phoenix.report.jaxb.File file
537            : (List<org.jcoderz.phoenix.report.jaxb.File>) mReport.getFile())
538        {
539            for (Item i : (List<Item>) file.getItem())
540            {
541                try
542                {
543                    FindingType.initialize(i.getOrigin());
544                }
545                catch (Exception ex)
546                {
547                    logger.log(Level.WARNING, 
548                        "Could not initialize finding type "
549                        + i.getFindingType());
550                }
551            }
552         }
553       
554    }
555
556   private void createAgeSummary ()
557       throws IOException
558   {
559       final List<Item> items = new ArrayList<Item>();
560       for (final org.jcoderz.phoenix.report.jaxb.File file
561           : (List<org.jcoderz.phoenix.report.jaxb.File>) mReport.getFile())
562       {
563          for (Item i : (List<Item>) file.getItem())
564          { 
565              if (i.isSetSince())
566              {
567                  items.add(i);
568                  mItemToFileMap.put(i, file);
569              }
570          }
571       }
572       Collections.sort(items, 
573           Collections.reverseOrder(new ItemAgeComperator()));
574
575       renderAgePage(items, Date.FUTURE_DATE, ReportInterval.BUILD);
576       renderAgePage(items, Date.FUTURE_DATE, ReportInterval.DAY);
577       renderAgePage(items, Date.FUTURE_DATE, ReportInterval.WEEK);
578       final Date oldest
579           = renderAgePage(items, Date.FUTURE_DATE, ReportInterval.MONTH);
580       renderAgePage(items, oldest, ReportInterval.OLD);
581   }
582
583   private void copyIcons () 
584       throws IOException
585   {
586      // create images sub-folder
587      final File outDir = new File(mOutDir, "images");
588      FileUtils.mkdirs(outDir);
589
590      for (int i = 0; i < Severity.VALUES.size(); i++)
591      {
592          final Severity s = Severity.fromInt(i);
593          if (s.equals(Severity.COVERAGE))
594          {
595              continue;
596          }
597          copyImage(outDir, "icon_" + s.toString() + ".gif");
598          copyImage(outDir, "bg-" + s.toString() + ".gif");
599      }
600   }
601
602    private void copyImage (final File outDir, final String name)
603    {
604        final InputStream in
605            = this.getClass().getResourceAsStream(name);
606        try
607        {
608            if (in != null)
609            {
610                copyResource(in, name, outDir);
611            }
612            else
613            {
614                logger.warning(
615                    "Could not find resource '" + name + "'!");
616            }
617        }
618        finally
619        {
620            IoUtil.close(in);
621        }
622    }
623
624   private void copyResource (InputStream in, String resource, File outDir)
625   {
626      // Copy it to the output folder
627      OutputStream out = null;
628      try
629      {
630         out = new FileOutputStream(new File(outDir, resource));
631         FileUtils.copy(in, out);
632      }
633      catch (FileNotFoundException ex)
634      {
635         throw new RuntimeException("Can not find output folder '"
636            + mOutDir + "'.", ex);
637      }
638      catch (IOException ex)
639      {
640         throw new RuntimeException("Could not copy resource '"
641            + resource + "'.", ex);
642      }
643      finally
644      {
645         IoUtil.close(out);
646      }
647   }
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
654      InputStream in = this.getClass().getResourceAsStream(mStyle);
655      try
656      {
657          if (in == null)
658          {
659             try
660             {
661                final File style = new File(mStyle);
662                in = new FileInputStream(style);
663             }
664             catch (FileNotFoundException ex)
665             {
666                IoUtil.close(in);
667                in = this.getClass().getResourceAsStream(DEFAULT_STYLESHEET);
668                if (in == null)
669                {
670                   throw new RuntimeException("Can not find stylesheet file '"
671                         + mStyle + "'.", ex);
672                }
673             }
674          }
675          copyResource(in, DEFAULT_STYLESHEET, mOutDir);
676      }
677      finally
678      {
679          IoUtil.close(in);
680      }
681   }
682
683   private void parseArguments (String[] args)
684   {
685      try
686      {
687         for (int i = 0; i < args.length; )
688         {
689            if ("-outDir".equals(args[i]))
690            {
691               setOutDir(new java.io.File(args[i + 1]));
692            }
693            else if ("-report".equals(args[i]))
694            {
695               setInputFile(new java.io.File(args[i + 1]));
696            }
697            else if ("-projectHome".equals(args[i]))
698            {
699               setProjectHome(new java.io.File(args[i + 1]));
700            }
701            else if ("-projectName".equals(args[i]))
702            {
703               setProjectName(args[i + 1]);
704            }
705            else if ("-cvsBase".equals(args[i]))
706            {
707               setCvsBase(args[i + 1]);
708            }
709            else if ("-cvsSuffix".equals(args[i]))
710            {
711               setCvsSuffix(args[i + 1]);
712            }
713            else if ("-timestamp".equals(args[i]))
714            {
715               setTimestamp(args[i + 1]);
716            }
717            else if ("-wikiBase".equals(args[i]))
718            {
719               setWikiBase(args[i + 1]);
720            }
721            else if ("-reportStyle".equals(args[i]))
722            {
723               setStyle(args[i + 1]);
724            }
725            else if ("-noCoverage".equals(args[i]))
726            {
727               setCoverageData(false);
728               i -= 1;
729            }
730            else if ("-sourceEncoding".equals(args[i]))
731            {
732               setSourceCharset(Charset.forName(args[i + 1]));
733            }
734            else if ("-packageBase".equals(args[i]))
735            {
736              setPackageBase(args[i + 1]);
737            }
738            else if ("-loglevel".equals(args[i]))
739            {
740               setLoglevel(args[i + 1]);
741            }
742            else if ("-tabwidth".equals(args[i]))
743            {
744               setTabwidth(args[i + 1]);
745            }
746            else
747            {
748               throw new IllegalArgumentException(
749                       "Invalid argument '" + args[i] + "'");
750            }
751            i += 1 /* command */ + 1 /* argument */;
752         }
753      }
754      catch (IndexOutOfBoundsException e)
755      {
756         final IllegalArgumentException ex
757               = new IllegalArgumentException("Missing value for "
758                  + args[args.length - 1]);
759         ex.initCause(e);
760         throw ex;
761      }
762      catch (Exception e)
763      {
764         final IllegalArgumentException ex = new IllegalArgumentException(
765               "Problem with arument value for " + args[args.length - 1]
766               + "Argument line was " + ArraysUtil.toString(args));
767         ex.initCause(e);
768         throw ex;
769      }
770   }
771
772   /**
773    *
774    */
775   private void createPerFindingSummary () throws IOException
776   {
777      for (final FindingsSummary.FindingSummary summary
778          : FindingsSummary.getFindingsSummary().getFindings().values())
779      {
780         final String filename = summary.createFindingDetailFilename();
781         final BufferedWriter out = openWriter(filename);
782         try
783         {
784             htmlHeader(out, "Finding-" + summary.getFindingType().getSymbol()
785                      + "-report " + mProjectName, "");
786             summary.createFindingTypeContent(out);
787             out.write("</body></html>");
788         }
789         finally
790         {
791             IoUtil.close(out);
792         }
793      }
794   }
795
796   private void createFindingsSummary () throws IOException
797   {
798      final BufferedWriter out = openWriter("findings.html");
799      try
800      {
801          htmlHeader(out, "Finding report " + mProjectName, "");
802          out.write("<h1><a href='index.html'>View by Classes</a></h1>");
803          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>");
808          out.write("<h1>Findings - Overview</h1>");
809          createNewFindingsList(out);
810          createOldFindingsList(out);
811          FindingsSummary.createOverallContent(out);
812          out.write("</body></html>");
813      }
814      finally
815      {
816          IoUtil.close(out);
817      }
818   }
819
820    private void createNewFindingsList (BufferedWriter out)
821        throws IOException
822    {
823        int row = 0;
824        for (org.jcoderz.phoenix.report.jaxb.File file
825            : (List<org.jcoderz.phoenix.report.jaxb.File>) mReport.getFile())
826        {
827            for (Item item : (List<Item>) file.getItem())
828            {
829                if (item.isNew())
830                {
831                    if (row == 0)
832                    {
833                        openTable(out, "New");
834                    }
835                    createRow(out, file, item, row);
836                    row++;
837                }
838            }
839        }
840        if (row > 0)
841        {
842            closeTable(out);
843        }
844    }
845
846    private void createOldFindingsList (BufferedWriter out)
847        throws IOException
848    {
849        int row = 0;
850        for (org.jcoderz.phoenix.report.jaxb.File file
851            : (List<org.jcoderz.phoenix.report.jaxb.File>) mReport.getFile())
852        {
853            for (Item item : (List<Item>) file.getItem())
854            {
855                if (item.isOld())
856                {
857                    if (row == 0)
858                    {
859                        openTable(out, "Fixed");
860                    }
861                    createRow(out, file, item, row);
862                    row++;
863                }
864            }
865        }
866        if (row > 0)
867        {
868            closeTable(out);
869        }
870    }
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.
878        bw.write("<tr class='findings-");
879        bw.write(Java2Html.toOddEvenString(rowCounter));
880        bw.write("row'><td class='findings-image'>");
881        appendSeverityImage(bw, item, "");
882        bw.write("</td><td width='100%' class='findings-data'>");
883        bw.write("<a href='");
884        bw.write(createReportLink(file));
885        bw.write("#LINE");
886        bw.write(String.valueOf(item.getLine()));
887        bw.write("'>");
888        bw.write(XmlUtil.escape(file.getClassname()));
889        bw.write(": ");
890        appendItemMessage(bw, item);
891        bw.write("</a></td><td>");
892        if (item.isSetSince())
893        {
894            bw.write(item.getSince().toDateString());
895        }
896        bw.write("</td></tr>\n");
897       
898    }
899
900    private void openTable (Writer writer, String string) 
901        throws IOException
902    {
903        writer.write("<h2 class='severity-header'>");
904        writer.write(string);
905        writer.write(" Findings</h2>");
906        writer.write("<table width='95%' cellpadding='2' cellspacing='0' "
907              + "border='0'>");
908    }
909
910    private void closeTable (Writer bw) 
911        throws IOException
912    {
913        bw.append("</table>");
914    }
915   
916    /**
917    * converts a java source to HTML
918    * with syntax highlighting for the
919    * comments, keywords, strings and chars
920    */
921   private void java2html (java.io.File inFile,
922         org.jcoderz.phoenix.report.jaxb.File data)
923   {
924
925      mCurrentFindings.clear();
926      mHandledFindings.clear();
927      mFindingsInFile.clear();
928      mFindingsInCurrentLine.clear();
929      mActiveItems.clear();
930      mCurrentFindings.addAll(data.getItem());
931      fillFindingsInFile(data);
932      logger.finest("Processing file " + inFile);
933
934      BufferedWriter bw = null;
935      String file = null;
936      try
937      {
938         mPackage = data.getPackage();
939         if (StringUtil.isEmptyOrNull(mPackage))
940         {
941             mPackage = UNNAMED_PACKAGE_NAME;
942         }
943         mClassname = data.getClassname();
944
945         // If no class name is reported take the filename.
946         if (StringUtil.isEmptyOrNull(mClassname))
947         {
948             mClassname = inFile.getName();
949         }
950         
951         final String subdir = mPackage.replaceAll("\\.", "/");
952         final java.io.File dir = new java.io.File(mOutDir, subdir);
953         FileUtils.mkdirs(dir);
954
955         bw = openWriter(dir, mClassname + ".html");
956
957         final Syntax src = new Syntax(inFile, mCharSet, mTabWidth);
958         final FileSummary summary
959               = createFileSummary(src.getNumberOfLines(), subdir);
960         addSummary(summary);
961
962         file = mPackage + "." + mClassname;
963
964         htmlHeader(bw, mClassname, mPackage);
965
966         bw.write("<h1><a href='");
967         bw.write(relativeRoot(mPackage));
968         bw.write("'>Project Report: ");
969         bw.write(mProjectName);
970         bw.write("</a></h1>" + NEWLINE);
971         bw.write("<h2><a href ='index.html'>Packagesummary ");
972         bw.write(mPackage);
973         bw.write("</a></h2>" + NEWLINE);
974
975         final String cvsLink = getCvsLink(inFile.getAbsolutePath());
976         if (cvsLink != null)
977         {
978            bw.write("<h3><a href='" + cvsLink
979               + "' class='cvs' title='cvs version'>" + file
980               + "</a></h3>" + NEWLINE);
981         }
982         else
983         {
984            bw.write("<h3>" + file + "</h3>" + NEWLINE);
985         }
986
987         // create header!!!!
988         bw.write("<table border='0' cellpadding='2' cellspacing='0' "
989                  + "width='95%'>");
990         bw.write("<thead><tr><th>Line</th><th>Hits</th><th>Note</th>"
991                  + "<th class='remainder'>Source</th></tr></thead>");
992         bw.write("<tbody>");
993
994         // PASS 2
995         final int lastLine = src.getNumberOfLines();
996         for (int currentLine = 1; currentLine <= lastLine; currentLine++)
997         {
998            bw.write("<tr class='"
999               + errorLevel(currentLine)
1000               + Java2Html.toOddEvenString(currentLine) + "'>");
1001            bw.write("<td align='right' class='lineno");
1002            final boolean isLast = currentLine == lastLine;
1003            appendIf(bw, isLast, LAST_MARKER);
1004            bw.write("'><a name='LINE" + currentLine + "' />");
1005            bw.write(String.valueOf(currentLine));
1006            bw.write("</td>");
1007            hitsCell(bw, String.valueOf(getHits(currentLine)), isLast);
1008            bw.write("<td class='note");
1009            appendIf(bw, isLast, LAST_MARKER);
1010            bw.write("'>");
1011            bw.write(getIcons(currentLine));
1012            bw.write("</td><td class='code-");
1013            bw.write(Java2Html.toOddEvenString(currentLine));
1014            appendIf(bw, isLast, LAST_MARKER);
1015            bw.write("'>");
1016            createCodeLine(bw, src);
1017            bw.write("</td></tr>\n");
1018         }
1019         bw.write("</tbody>");
1020         bw.write("</table>\n");
1021
1022         // findings table
1023         bw.write("<h2 class='findings-header'>Findings in this File</h2>");
1024         bw.write("<table width='95%' cellpadding='0' cellspacing='0' "
1025               + "border='0'>\n");
1026         int rowCounter = 0;
1027
1028         final String relativeRoot = relativeRoot(mPackage, "");
1029
1030         int pos = mHandledFindings.size();
1031         // findings with no line number or uncovered jet
1032         for (final List<Item> lineFindings : mFindingsInFile.values())
1033         {
1034             for (final Item item : lineFindings)
1035             {
1036               if (!Origin.COVERAGE.equals(item.getOrigin()))
1037               {
1038                  pos++;
1039                  rowCounter++;
1040
1041                  bw.write("<tr class='findings-");
1042                  bw.write(Java2Html.toOddEvenString(rowCounter));
1043                  bw.write("row'>\n");
1044                  bw.write("   <td class='findings-image'>\n");
1045                  appendSeverityImage(bw, item, relativeRoot);
1046                  bw.write("   </td>\n");
1047                  bw.write("   <td class='findings-id'>\n");
1048                  bw.write("      <a name='FINDING" + pos + "' />\n");
1049                  bw.write("         (" + pos + ")\n");
1050                  bw.write("   </td>\n");
1051                  bw.write("   <td></td><td></td><td></td>\n"); // line number
1052                  bw.write("   <td width='100%' class='findings-data'>\n");
1053                  appendItemMessage(bw, item);
1054                  bw.write("   </td>\n");
1055                  bw.write("</tr>\n");
1056               }
1057            }
1058         }
1059
1060         // Findings as marked in the code.
1061         pos = 0;
1062         for (final Item item : mHandledFindings)
1063         {
1064            pos++;
1065            rowCounter++;
1066
1067            final String link = "#LINE" + item.getLine();
1068
1069            bw.write("<tr class='findings-");
1070            bw.write(Java2Html.toOddEvenString(rowCounter));
1071            bw.write("row'>\n");
1072            bw.write("   <td class='findings-image'>\n");
1073            appendSeverityImage(bw, item, relativeRoot);
1074            bw.write("   </td>\n");
1075            bw.write("   <td class='findings-id'>\n");
1076            bw.write("      <a name='FINDING" + pos + "' />\n");
1077            bw.write("      <a href='" + link + "' title='" + item.getOrigin()
1078                  + "' >\n");
1079            bw.write("            (" + pos + ")\n");
1080            bw.write("      </a>\n");
1081            bw.write("   </td>\n");
1082            bw.write("   <td class='findings-line-number' align='right'>\n");
1083            bw.write("      <a href='" + link + "' >\n");
1084            bw.write(String.valueOf(item.getLine()));
1085            bw.write("      </a>\n");
1086            bw.write("   </td>\n");
1087            bw.write("   <td class='findings-line-number' align='center'>\n");
1088            bw.write("      <a href='" + link + "' >:</a>\n");
1089            bw.write("   </td>\n");
1090            bw.write("   <td class='findings-line-number' align='left'>\n");
1091            bw.write("      <a href='" + link + "' >\n");
1092            bw.write(String.valueOf(item.getColumn()));
1093            bw.write("      </a>\n");
1094            bw.write("   </td>\n");
1095            bw.write("   <td width='100%' class='findings-data'>\n");
1096            bw.write("      <a href='" + link + "' >\n");
1097            appendItemMessage(bw, item);
1098            bw.write("\n");
1099            bw.write("      </a>\n");
1100            bw.write("   </td>\n");
1101            bw.write("</tr>\n");
1102         }
1103
1104         bw.write("</table>\n");
1105         bw.write("\n</body>\n</html>");
1106      }
1107      catch (FileNotFoundException fnfe)
1108      {
1109         logger.log(Level.WARNING, "Source file '" + file + "' not found.",
1110               fnfe);
1111      }
1112      catch (IOException ioe)
1113      {
1114         logger.log(Level.WARNING, "Problem with '" + file + "'.", ioe);
1115      }
1116      finally
1117      {
1118         IoUtil.close(bw);
1119      }
1120   }
1121
1122    private void appendItemMessage (Writer bw, final Item item)
1123        throws IOException
1124    {
1125        if (item.isOld())
1126        {
1127            bw.write("Fixed: ");
1128        }
1129        if (item.isNew())
1130        {
1131            bw.write("New: ");
1132        }
1133        bw.write(XmlUtil.escape(item.getMessage()));
1134        if (item.getSeverityReason() != null)
1135        {
1136            bw.write(' ');
1137            bw.write(XmlUtil.escape(item.getSeverityReason()));
1138        }
1139    }
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   {
1154       w.write("<a href='");
1155       w.write(root);
1156       w.write(FindingsSummary.getFindingsSummary()
1157               .getFindingSummary(item).createFindingDetailFilename());
1158       w.write("'><img border='0' title='");
1159       w.write(String.valueOf(item.getSeverity()));
1160       w.write(" [");
1161       w.write(String.valueOf(item.getOrigin()));
1162       w.write("]' alt='");
1163       w.write(item.getSeverity().toString().substring(0, 1));
1164       w.write("' src='");
1165       w.write(root);
1166       w.write(getImage(item.getSeverity()));
1167       w.write("' /></a>\n");
1168   }
1169
1170   private String getImage (Severity severity)
1171   {
1172      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
1179      final FileSummary summary = new FileSummary(mClassname, mPackage,
1180            subdir + "/" + mClassname + ".html", linesCount, mCoverageData);
1181      for (final Item item : mCurrentFindings)
1182      {
1183         FindingsSummary.addFinding(item, summary);
1184         if (Origin.COVERAGE.equals(item.getOrigin()))
1185         {
1186            if (item.getCounter() != 0)
1187            {
1188               summary.addCoveredLine();
1189            }
1190            else
1191            {
1192               summary.addViolation(Severity.COVERAGE);
1193            }
1194         }
1195         else
1196         {
1197            summary.addViolation(item.getSeverity());
1198         }
1199      }
1200      return summary;
1201   }
1202
1203   private void fillFindingsInFile (
1204         org.jcoderz.phoenix.report.jaxb.File data)
1205   {
1206      for (final Item item : (List<Item>) data.getItem())
1207      {
1208         final int lineNumber = item.getLine();
1209         List<Item> itemsInLine = mFindingsInFile.get(lineNumber);
1210         if (itemsInLine == null)
1211         {
1212            itemsInLine = new ArrayList<Item>();
1213            mFindingsInFile.put(lineNumber, itemsInLine);
1214         }
1215         itemsInLine.add(item);
1216      }
1217   }
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      // TODO: use the default stylesheet if not explicitly specified
1230      String result = "";
1231      if (style != null)
1232      {
1233         final String styleLink;
1234         if (style.indexOf("//") != -1)
1235         {  // absolute style
1236            styleLink = style;
1237         }
1238         else
1239         {
1240            styleLink = relativeRoot(packageName, style);
1241         }
1242         result = "<link rel='stylesheet' type='text/css' href='"
1243            + styleLink + "' />";
1244      }
1245      return result;
1246   }
1247
1248   private Severity errorLevel (int line)
1249   {
1250      Severity severity = Severity.OK;
1251
1252      final Iterator<Item> active = mActiveItems.iterator();
1253
1254      while (active.hasNext())
1255      {
1256         final Item item = active.next();
1257         if (item.getEndLine() < line)
1258         {
1259            active.remove();
1260         }
1261         else
1262         {
1263            severity = severity.max(item.getSeverity());
1264         }
1265      }
1266
1267
1268      final Iterator<Item> items = findingsInLine(line);
1269      while (items.hasNext())
1270      {
1271         final Item item = items.next();
1272         if (item.getOrigin().equals(Origin.COVERAGE))
1273         {
1274            if (item.getCounter() == 0)
1275            {
1276               severity = severity.max(Severity.COVERAGE);
1277            }
1278         }
1279         else
1280         {
1281            severity = severity.max(item.getSeverity());
1282            if (item.getEndLine() > line)
1283            {
1284               mActiveItems.add(item);
1285            }
1286         }
1287      }
1288      return severity;
1289   }
1290
1291   private Iterator<Item> findingsInLine (int line)
1292   {
1293      final List<Item> findingsInLine = mFindingsInFile.get(line);
1294      final Iterator<Item> result;
1295      if (findingsInLine == null)
1296      {
1297         result = EmptyIterator.EMPTY_ITERATOR;
1298      }
1299      else
1300      {
1301         result = findingsInLine.iterator();
1302      }
1303      return result;
1304   }
1305
1306   private String getHits (int line)
1307   {
1308      String hits = "&nbsp;";
1309
1310      final Iterator<Item> items = findingsInLine(line);
1311
1312      while (items.hasNext())
1313      {
1314         final Item item = items.next();
1315         if (Origin.COVERAGE.equals(item.getOrigin()))
1316         {
1317            hits = String.valueOf(item.getCounter());
1318            break;
1319         }
1320      }
1321      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   {
1331      final StringBuilder icons = new StringBuilder();
1332
1333      // collect relevant findings
1334      final Iterator<Item> items = findingsInLine(line);
1335      while (items.hasNext())
1336      {
1337         final Item item = items.next();
1338         if (Origin.COVERAGE.equals(item.getOrigin()))
1339         {
1340            if (item.getCounter() == 0)
1341            {
1342               items.remove(); // will never see this again!
1343            }
1344         }
1345         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         {
1353            mHandledFindings.add(item);
1354            // create the magic icon string with a hyperlink
1355            mGetIconsStringBuffer.setLength(0);
1356            mGetIconsStringBuffer.append("<a href='#FINDING");
1357            mGetIconsStringBuffer.append(mHandledFindings.size());
1358            mGetIconsStringBuffer.append("' title='");
1359            mGetIconsStringBuffer.append(
1360                  XmlUtil.attributeEscape(item.getMessage()));
1361            mGetIconsStringBuffer.append("'><span class='");
1362            mGetIconsStringBuffer.append(item.getOrigin());
1363            mGetIconsStringBuffer.append("note'>(");
1364            mGetIconsStringBuffer.append(mHandledFindings.size());
1365            mGetIconsStringBuffer.append(")</span></a>");
1366            icons.append(mGetIconsStringBuffer);
1367            if (item.isSetColumn() 
1368                && (!item.isSetEndLine() 
1369                    || item.getEndLine() - line <= MAX_LINES_WITH_INLINE_MARK))
1370            {
1371                mFindingsInCurrentLine.add(item);
1372            }
1373           items.remove(); // item was handled fully
1374         }
1375      }
1376      if (icons.length() == 0)
1377      {
1378         icons.append("&nbsp;");
1379      }
1380      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;
1394      if (in == null || in.length() == 0)
1395      {
1396         result = "&nbsp;";
1397      }
1398      else if (in.charAt(0) == ' ' || in.charAt(0) == '\t')
1399      {
1400         mStringBuilder.setLength(0);
1401         int i;
1402         int pos = 0;
1403         for (i = 0; i < in.length() 
1404             && (in.charAt(i) == ' ' || in.charAt(i) == '\t');  i++)
1405         {
1406             if (in.charAt(i) == ' ')
1407             {
1408                 mStringBuilder.append("&nbsp;");
1409                 pos++;
1410             }
1411             else if (in.charAt(i) == '\t')
1412             {
1413                 mStringBuilder.append("&nbsp;");
1414                 pos++;
1415                 while (pos % mTabWidth != 0)
1416                 {
1417                     mStringBuilder.append("&nbsp;");
1418                     pos++;
1419                 }
1420             }
1421             
1422         }
1423         mStringBuilder.append(in.substring(i));
1424         result = mStringBuilder.toString();
1425      }
1426      else
1427      {
1428         result = in;
1429      }
1430      return result;
1431   }
1432
1433   /**
1434    * Adds the file summary to all summary lists.
1435    */
1436   private void addSummary (FileSummary summary)
1437   {
1438      mAllFiles.add(summary);
1439
1440      List<FileSummary> packageList
1441          = mAllPackages.get(summary.getPackage());
1442
1443      if (packageList == null)
1444      {
1445         packageList = new ArrayList<FileSummary>();
1446         mAllPackages.put(summary.getPackage(), packageList);
1447      }
1448      packageList.add(summary);
1449
1450      FileSummary packageSummary
1451         = mPackageSummary.get(summary.getPackage());
1452      if (packageSummary == null)
1453      {
1454         packageSummary = new FileSummary(mPackage);
1455         mPackageSummary.put(summary.getPackage(), packageSummary);
1456      }
1457      packageSummary.add(summary);
1458      mGlobalSummary.add(summary);
1459   }
1460
1461   private void createPackageSummary (List<FileSummary> pkg)
1462         throws IOException
1463   {
1464      createPackageSummary(new FileSummary.SortByPackage(), pkg);
1465      createPackageSummary(new FileSummary.SortByQuality(), pkg);
1466      createPackageSummary(new FileSummary.SortByCoverage(), pkg);
1467   }
1468
1469   private void createPackageSummary (Comparator<FileSummary> order,
1470       List<FileSummary> pkg)
1471      throws IOException
1472   {
1473      final String filename = fileNameForOrder(order);
1474      final String packageName = pkg.get(0).getPackage();
1475      final String subdir = packageName.replaceAll("\\.", "/");
1476      final java.io.File dir = new java.io.File(mOutDir, subdir);
1477      FileUtils.mkdirs(dir);
1478
1479      final BufferedWriter bw = openWriter(dir, filename);
1480      try
1481      {
1482          htmlHeader(bw, packageName, packageName);
1483          bw.write("<h1><a href='" + relativeRoot(packageName, filename)
1484             + "'>Project-Report "
1485             + mProjectName + "</a></h1>");
1486          bw.write("<h2>Packagesummary " + packageName + "</h2>");
1487          createClassListTable(bw, pkg, false, order);
1488          bw.write("</body></html>");
1489      }
1490      finally
1491      {
1492          IoUtil.close(bw);
1493      }
1494   }
1495
1496   private void createFullSummary ()
1497         throws IOException
1498   {
1499      createFullSummary(new FileSummary.SortByPackage());
1500      createFullSummary(new FileSummary.SortByQuality());
1501      createFullSummary(new FileSummary.SortByCoverage());
1502   }
1503
1504   private void createFullSummary (Comparator<FileSummary> order)
1505         throws IOException
1506   {
1507      final String filename = fileNameForOrder(order);
1508      final BufferedWriter bw = openWriter(filename);
1509      try
1510      {
1511          htmlHeader(bw, "Project Report " + mProjectName, "");
1512
1513          bw.write("<h1>Project Report " + mProjectName + "</h1>");
1514
1515          bw.write("<table border='0' cellpadding='2' cellspacing='0' "
1516                + "width='95%'>");
1517          bw.write("<thead><tr><th>");
1518          if (filename != SORT_BY_PACKAGE_INDEX)
1519          {
1520             bw.write("<a href='" + SORT_BY_PACKAGE_INDEX
1521                   + "' title='Sort by name'>");
1522          }
1523          bw.write("Package");
1524          if (filename != SORT_BY_PACKAGE_INDEX)
1525          {
1526             bw.write("</a>");
1527          }
1528          bw.write("</th><th>findings</th>");
1529          bw.write("<th>files</th><th>lines</th>");
1530          if (mCoverageData)
1531          {
1532             bw.write("<th>%</th><th>");
1533             if (filename != SORT_BY_COVERAGE_INDEX)
1534             {
1535                bw.write("<a href='" + SORT_BY_COVERAGE_INDEX
1536                      + "' title='Sort by coverage'>");
1537             }
1538             bw.write("Coverage");
1539             if (filename != SORT_BY_COVERAGE_INDEX)
1540             {
1541                bw.write("</a>");
1542             }
1543             bw.write("</th>");
1544          }
1545          bw.write("<th>%</th><th class='remainder'>");
1546          if (filename != SORT_BY_QUALITY_INDEX)
1547          {
1548             bw.write("<a href='" + SORT_BY_QUALITY_INDEX
1549                   + "' title='Sort by quality'>");
1550          }
1551          bw.write("Quality");
1552          if (filename != SORT_BY_QUALITY_INDEX)
1553          {
1554             bw.write("</a>");
1555          }
1556          bw.write("</th></tr></thead>");
1557          bw.write("<tbody>");
1558          bw.write(NEWLINE);
1559          bw.write("<tr class='odd'><td class='classname" + LAST_MARKER + "'>");
1560          bw.write("Overall summary");
1561          bw.write("</td>");
1562          hitsCell(bw, String.valueOf(mGlobalSummary.getNumberOfFindings()),
1563                  true);
1564          hitsCell(bw, String.valueOf(mGlobalSummary.getNumberOfFiles()), true);
1565          hitsCell(bw, String.valueOf(mGlobalSummary.getLinesOfCode()), true);
1566          if (mCoverageData)
1567          {
1568             hitsCell(bw, String.valueOf(mGlobalSummary.getCoverageAsString()),
1569                     true);
1570             bw.write("<td valign='middle' class='hits" + LAST_MARKER
1571                   + "' width='100'>");
1572             bw.write(mGlobalSummary.getCoverageBar());
1573             bw.write("</td>");
1574          }
1575          hitsCell(bw, String.valueOf(mGlobalSummary.getQuality()) + "%", true);
1576          bw.write("<td valign='middle' class='code" + LAST_MARKER
1577                + "' width='100'>");
1578          bw.write(mGlobalSummary.getPercentBar());
1579          bw.write("</td></tr>");
1580          bw.write(NEWLINE);
1581          bw.write("</tbody>");
1582          bw.write("</table>");
1583
1584          bw.write("<h1><a href='findings.html'>View by Finding</a></h1>");
1585          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
1591          bw.write("<h1>Packages</h1>");
1592          bw.write("<table border='0' cellpadding='2' cellspacing='0' "
1593                + "width='95%'>");
1594          bw.write("<thead><tr><th>Package</th>"
1595             + "<th>findings</th><th>files</th><th>lines</th>");
1596          if (mCoverageData)
1597          {
1598             bw.write("<th>%</th><th>Coverage</th>");
1599          }
1600          bw.write("<th>%</th><th class='remainder'>Quality</th></tr></thead>");
1601          bw.write("<tbody>");
1602          bw.write(NEWLINE);
1603
1604          final Set<FileSummary> packages = new TreeSet<FileSummary>(order);
1605          packages.addAll(mPackageSummary.values());
1606
1607          int pos = 0;
1608          final Iterator<FileSummary> i = packages.iterator();
1609          while (i.hasNext())
1610          {
1611             pos++;
1612             final FileSummary pkg = i.next();
1613             final boolean isLast = !i.hasNext();
1614             appendPackageLink(bw, pkg, filename, pos, isLast);
1615          }
1616          bw.write("</tbody></table>\n");
1617
1618          // findings with no line number...
1619          createUnassignedFindingsTable(bw);
1620
1621          bw.write("<h1>Java Files</h1>");
1622          createClassListTable(bw, mAllFiles, true, order);
1623
1624          bw.write("</body></html>");
1625      }
1626      finally
1627      {
1628          IoUtil.close(bw);
1629      }
1630   }
1631
1632    private void createUnassignedFindingsTable (final BufferedWriter bw)
1633        throws IOException
1634    {
1635        boolean tableOpened = false;
1636        int row = 0;
1637        for (final org.jcoderz.phoenix.report.jaxb.File file : mGlobalFindings)
1638        {
1639            for (final Item item : (List<Item>) file.getItem())
1640             {
1641                row++;
1642                if (!tableOpened)
1643                {
1644                    bw.write("<h1>Unassigned findings</h1>");
1645                    bw.write("<table border='0' cellpadding='0' "
1646                        + "cellspacing='0' width='95%'>");
1647                    tableOpened = true;
1648                }
1649                bw.write("<tr class='");
1650                bw.write(item.getSeverity().toString());
1651                bw.write(Java2Html.toOddEvenString(row));
1652                bw.write("'><td class='unassigned-origin'>");
1653                bw.write(ObjectUtil.toStringOrEmpty(item.getOrigin()));
1654                bw.write("</td><td class='unassigned-filename'>");
1655                bw.write(cutPath(file.getName()));
1656                bw.write("</td><td class='unassigned-data' width='100%'>");
1657                bw.write(item.getMessage());
1658                bw.write("</td></tr>");
1659
1660                FindingsSummary.addFinding(item, mGlobalSummary);
1661             }
1662         }
1663        if (tableOpened)
1664        {
1665            bw.write("</table>");
1666        }
1667    }
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    {
1674         final String name = pkg.getPackage();
1675         final String subdir = name.replaceAll("\\.", "/");
1676         bw.write("<tr class='" + Java2Html.toOddEvenString(pos)
1677               + "'><td class='classname");
1678         appendIf(bw, isLast, LAST_MARKER);
1679         bw.write("'><a href='" + subdir + "/" + filename + "'>");
1680         bw.write(pkg.getPackage());
1681         bw.write("</a></td>");
1682         hitsCell(bw, String.valueOf(pkg.getNumberOfFindings()), isLast);
1683         hitsCell(bw, String.valueOf(pkg.getNumberOfFiles()), isLast);
1684         hitsCell(bw, String.valueOf(pkg.getLinesOfCode()), isLast);
1685         if (mCoverageData)
1686         {
1687            hitsCell(bw, String.valueOf(pkg.getCoverageAsString()), isLast);
1688            bw.write("<td valign='middle' class='hits");
1689            appendIf(bw, isLast, LAST_MARKER);
1690            bw.write("' width='100'>");
1691            bw.write(pkg.getCoverageBar());
1692            bw.write("</td>");
1693         }
1694         hitsCell(bw, String.valueOf(pkg.getQuality()) + "%", isLast);
1695         bw.write("<td valign='middle' class='code");
1696         appendIf(bw, isLast, LAST_MARKER);
1697         bw.write("' width='100'>");
1698         bw.write(pkg.getPercentBar());
1699         bw.write("</td></tr>" + NEWLINE);
1700    }
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   {
1711       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>");
1720      bw.write(title);
1721      bw.write("</title>" + NEWLINE
1722         + "\t<meta name='author' content='jCoderZ java2html' />" + NEWLINE);
1723      bw.write(createStyle(packageName, mStyle));
1724      bw.write(NEWLINE + "</head>" + NEWLINE + "<body>" + NEWLINE);
1725   }
1726   
1727   
1728   
1729   
1730
1731   private Date renderAgePage (
1732       List<Item> findings, Date startDate, ReportInterval periode)
1733       throws IOException
1734   {
1735       final Writer bw
1736           = openWriter(mOutDir, "age-" + periode.toString() + ".html");
1737       htmlHeader(bw, "Finding in " + periode.toString(), "");
1738       
1739       bw.write("<h1><a href='index.html'>View by Classes</a></h1>");
1740       bw.write("<h1><a href='findings.html'>View by Finding</a></h1>");
1741       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       
1747       if (ReportInterval.OLD != periode)
1748       {
1749           bw.write("<h1>Current Findings by " + periode.toString() + "</h1>");
1750       }
1751       else
1752       {
1753           bw.write("<h1>Old findings since " + startDate + "</h1>");
1754       }
1755       
1756       
1757       int numberOfSegments = 0;
1758       Date iStart = null;
1759       try
1760       {
1761           Date iEnd = null;
1762           final List<Item> interval = new ArrayList<Item>();
1763           for (Item i : findings)
1764           {
1765               if (iEnd == null)
1766               {
1767                   if (i.getSince().before(startDate))
1768                   {
1769                       iStart = getPeriodStart(periode, i.getSince());
1770                       iEnd = getPeriodEnd(periode, i.getSince());
1771                   }
1772                   else
1773                   {
1774                       continue;
1775                   }
1776               }
1777               if (i.getSince().before(iStart))
1778               {
1779                   Collections.sort(interval, new ItemSeverityComperator());
1780                   createSegment(bw, interval, iStart, iEnd);
1781                   interval.clear();
1782                   numberOfSegments++;
1783                   if (numberOfSegments > NUMBER_OF_AGE_SEGMENTS)
1784                   {
1785                       break;
1786                   }
1787                   iStart = getPeriodStart(periode, i.getSince());
1788                   iEnd = getPeriodEnd(periode, i.getSince());
1789               }
1790               interval.add(i);
1791           }
1792           
1793           if (!interval.isEmpty())
1794           {
1795               Collections.sort(interval, new ItemSeverityComperator());
1796               createSegment(bw, interval, iStart, iEnd);
1797               interval.clear();
1798           }
1799       }
1800       finally
1801       {
1802           IoUtil.close(bw);
1803       }
1804       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   {
1813       if (!items.isEmpty())
1814       {
1815           openTable(bw, "Findings " + start
1816               + (start.equals(end) ? "" : (" - " + end))
1817               + " " + items.size());     
1818       }
1819       int pos = 0;
1820       for (final Item item : items)
1821       {
1822           createRow(bw, getFile(item), item, pos);
1823           pos++;
1824       }
1825       if (!items.isEmpty())
1826       {
1827           closeTable(bw);
1828       }
1829       
1830   }
1831
1832    private org.jcoderz.phoenix.report.jaxb.File getFile (Item item)
1833    {
1834        return mItemToFileMap.get(item);
1835    }
1836
1837    /*private*/ static Date getPeriodEnd (
1838        ReportInterval periode, Date actualStart)
1839    {
1840       final Date result;
1841       if (ReportInterval.BUILD == periode)
1842       {
1843           result = actualStart.plus(1);
1844       }
1845       else if (ReportInterval.DAY == periode)
1846       {
1847           final Calendar cal
1848               = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
1849           cal.setTimeInMillis(actualStart.getTime());
1850           cal.set(Calendar.HOUR_OF_DAY, 0);
1851           cal.set(Calendar.MINUTE, 0);
1852           cal.set(Calendar.SECOND, 0);
1853           cal.set(Calendar.MILLISECOND, 0);
1854           cal.add(Calendar.DAY_OF_MONTH, 1);
1855           result = new Date(cal.getTimeInMillis());
1856       }
1857       else if (ReportInterval.WEEK == periode)
1858       {
1859           final Calendar cal
1860               = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
1861           cal.setFirstDayOfWeek(Calendar.MONDAY);
1862           cal.setTimeInMillis(actualStart.getTime());
1863           cal.set(Calendar.HOUR_OF_DAY, 0);
1864           cal.set(Calendar.MINUTE, 0);
1865           cal.set(Calendar.SECOND, 0);
1866           cal.set(Calendar.MILLISECOND, 0);
1867           
1868           int dayOffset
1869               = Calendar.MONDAY - cal.get(Calendar.DAY_OF_WEEK);
1870           if (dayOffset <= 0)
1871           {
1872               dayOffset += cal.getMaximum(Calendar.DAY_OF_WEEK);
1873           }
1874           cal.add(Calendar.DAY_OF_MONTH, dayOffset);
1875           result = new Date(cal.getTimeInMillis());
1876       }
1877       else if (ReportInterval.MONTH == periode)
1878       {
1879           final Calendar cal
1880               = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
1881           cal.setFirstDayOfWeek(Calendar.MONDAY);
1882           cal.setTimeInMillis(actualStart.getTime());
1883           cal.set(Calendar.HOUR_OF_DAY, 0);
1884           cal.set(Calendar.MINUTE, 0);
1885           cal.set(Calendar.SECOND, 0);
1886           cal.set(Calendar.MILLISECOND, 0);
1887           cal.set(Calendar.DAY_OF_MONTH, 1);
1888           cal.add(Calendar.MONTH, 1);
1889           result = new Date(cal.getTimeInMillis());
1890       }
1891       else if (ReportInterval.OLD == periode)
1892       {
1893           result = actualStart; 
1894       }
1895       else
1896       {
1897           Assert.fail("Unsupported value for ReportInterval " + periode);
1898           result = null; // never reached
1899       }
1900       return result;
1901   }
1902
1903    /*private*/ static Date getPeriodStart (ReportInterval periode, Date pos)
1904    {
1905       final Date result;
1906       if (ReportInterval.BUILD == periode)
1907       {
1908           result = pos;
1909       }
1910       else if (ReportInterval.DAY == periode)
1911       {
1912           final Calendar cal
1913               = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
1914           cal.setTimeInMillis(pos.getTime());
1915           cal.set(Calendar.HOUR_OF_DAY, 0);
1916           cal.set(Calendar.MINUTE, 0);
1917           cal.set(Calendar.SECOND, 0);
1918           cal.set(Calendar.MILLISECOND, 0);
1919           result = new Date(cal.getTimeInMillis());
1920       }
1921       else if (ReportInterval.WEEK == periode)
1922       {
1923           final Calendar cal
1924               = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
1925           cal.setFirstDayOfWeek(Calendar.MONDAY);
1926           cal.setTimeInMillis(pos.getTime());
1927           cal.set(Calendar.HOUR_OF_DAY, 0);
1928           cal.set(Calendar.MINUTE, 0);
1929           cal.set(Calendar.SECOND, 0);
1930           cal.set(Calendar.MILLISECOND, 0);
1931           int dayOffset
1932               = Calendar.MONDAY - cal.get(Calendar.DAY_OF_WEEK);
1933           if (dayOffset > 0)
1934           {
1935               dayOffset -= cal.getMaximum(Calendar.DAY_OF_WEEK);
1936           }
1937           cal.add(Calendar.DAY_OF_MONTH, dayOffset);
1938           result = new Date(cal.getTimeInMillis());
1939       }
1940       else if (ReportInterval.MONTH == periode)
1941       {
1942           final Calendar cal
1943               = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
1944           cal.setFirstDayOfWeek(Calendar.MONDAY);
1945           cal.setTimeInMillis(pos.getTime());
1946           cal.set(Calendar.HOUR_OF_DAY, 0);
1947           cal.set(Calendar.MINUTE, 0);
1948           cal.set(Calendar.SECOND, 0);
1949           cal.set(Calendar.MILLISECOND, 0);
1950           cal.set(Calendar.DAY_OF_MONTH, 1);
1951           result = new Date(cal.getTimeInMillis());
1952       }
1953       else if (ReportInterval.OLD == periode)
1954       {
1955           result = Date.OLD_DATE; 
1956       }
1957       else
1958       {
1959           Assert.fail("Unsupported value for ReportInterval " + periode);
1960           result = null; // never reached
1961       }
1962       return result;
1963   }
1964   
1965
1966   private static String relativeRoot (String currentPackage)
1967   {
1968      return relativeRoot(currentPackage, "index.html");
1969   }
1970
1971   private static String relativeRoot (String currentPackage, String page)
1972   {
1973      final StringBuilder rootDir = new StringBuilder();
1974
1975      if (currentPackage.length() != 0)
1976      {
1977         rootDir.append("../");
1978      }
1979
1980      for (int i = 0; i < currentPackage.length(); i++)
1981      {
1982         if (currentPackage.charAt(i) == '.')
1983         {
1984            rootDir.append("../");
1985         }
1986      }
1987      rootDir.append(page);
1988
1989      return rootDir.toString().replaceAll("//", "/");
1990   }
1991
1992   private String cutPath (String fileName)
1993   {
1994      String result = ObjectUtil.toStringOrEmpty(fileName);
1995      if (fileName != null && fileName.toLowerCase(Constants.SYSTEM_LOCALE)
1996              .startsWith(mProjectHome.toLowerCase(Constants.SYSTEM_LOCALE)))
1997      {
1998         result = fileName.substring(mProjectHome.length());
1999      }
2000      return result;
2001   }
2002
2003   private String getCvsLink (String absFile)
2004   {
2005      String result;
2006      if (mWebVcBase == null || mProjectHome == null)
2007      {
2008         result = null;
2009      }
2010      else if (absFile.toLowerCase(Constants.SYSTEM_LOCALE)
2011              .startsWith(mProjectHome.toLowerCase(Constants.SYSTEM_LOCALE)))
2012      {
2013         result = absFile.substring(mProjectHome.length());
2014      }
2015      else
2016      {
2017         final String absPath
2018             = (new java.io.File(absFile)).getAbsolutePath();
2019         if (absPath.toLowerCase(Constants.SYSTEM_LOCALE).startsWith(
2020             mProjectHome.toLowerCase(Constants.SYSTEM_LOCALE)))
2021         {
2022            result = absPath.substring(mProjectHome.length());
2023         }
2024         else
2025         {
2026            result = null;
2027         }
2028
2029      }
2030      if (result != null)
2031      {
2032         result = mWebVcBase + result + mWebVcSuffix;
2033         result = result.replaceAll("\\\\", "/");
2034      }
2035      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   {
2048       String text = src.nextToken();
2049       String lastTokenType = null;
2050       Item lastFinding = null;
2051       boolean isFirst = true;
2052       while (null != src.getCurrentTokenType())
2053       {
2054           final Item currentFinding
2055               = getCurrentFinding(text, src.getCurrentLineNumber(),
2056                   src.getCurrentLinePos(), src.getCurrentTokenLength());
2057           if (currentFinding != lastFinding)
2058           {
2059               if (lastTokenType != null)
2060               {
2061                   out.write("</span>"); // close token type
2062                   lastTokenType = null;
2063               }
2064               if (lastFinding != null)
2065               {
2066                   out.write("</span>"); // close last finding
2067               }
2068               if (currentFinding != null)
2069               {
2070                   out.write("<span class='finding-");
2071                   out.write(currentFinding.getSeverity().toString());
2072                   out.write("' title='");
2073                   out.write(
2074                       XmlUtil.attributeEscape(currentFinding.getMessage()));
2075                   out.write("'>");
2076               }
2077               lastFinding = currentFinding;
2078           }
2079           final String tokenType = src.getCurrentTokenType();
2080           if (!tokenType.equals(lastTokenType))
2081           {
2082               if (lastTokenType != null)
2083               {
2084                   out.write("</span>");
2085                   isFirst = false;
2086               }
2087               out.write("<span class='code-");
2088               out.write(tokenType);
2089               out.write("'>");
2090           }
2091           String xml = XmlUtil.escape(text);
2092           if (isFirst)
2093           {
2094               xml = replaceLeadingSpaces(xml);
2095           }
2096           out.write(xml); 
2097           
2098           lastTokenType = tokenType;
2099           text = src.nextToken();
2100       }
2101       if (lastTokenType != null)
2102       {
2103           out.write("</span>");
2104       }
2105       if (lastFinding != null)
2106       {
2107           out.write("</span>");
2108       }
2109   }
2110
2111   private Item getCurrentFinding (
2112       String text, int currentLineNumber, int currentLinePos, int tokenLen)
2113   {
2114       Item theFinding = null;
2115       final Iterator<Item> i = mFindingsInCurrentLine.iterator();
2116       while (i.hasNext())
2117       {
2118           final Item finding = i.next();
2119           
2120           if (finding.getEndLine() < currentLineNumber
2121               && finding.getLine() < currentLineNumber)
2122           {
2123               i.remove();
2124               continue;
2125           }
2126           
2127           final int start = 
2128               finding.getLine() == currentLineNumber
2129                   ? finding.getColumn() : 0;
2130           int end = 
2131               finding.getEndLine() == currentLineNumber
2132                   ? finding.getEndColumn() : ABSOLUTE_END_OF_LINE;
2133           if (finding.getEndLine() == 0)
2134           {
2135               end = finding.getEndColumn();
2136           }
2137           
2138           // TODO Add code to find pos by symbol!
2139                // finding at current pos
2140           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           {
2147               if (theFinding == null
2148                   || finding.getSeverity().compareTo(
2149                       theFinding.getSeverity()) > 0)
2150               {
2151                   theFinding = finding;
2152               }
2153           }
2154       }
2155       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.
2169      if (!files.isEmpty())
2170      {
2171         final String filename = fileNameForOrder(order);
2172         final FileSummary[] summaries =
2173            files.toArray(new FileSummary[files.size()]);
2174
2175         Arrays.sort(summaries, order);
2176
2177         bw.write("<table border='0' cellpadding='2' cellspacing='0' "
2178               + "width='95%'>");
2179         bw.write("<thead><tr><th>");
2180         if (filename != SORT_BY_PACKAGE_INDEX)
2181         {
2182            bw.write("<a href='");
2183            bw.write(SORT_BY_PACKAGE_INDEX);
2184            bw.write("' title='Sort by name'>");
2185         }
2186         bw.write("Classfile");
2187         if (filename != SORT_BY_PACKAGE_INDEX)
2188         {
2189            bw.write("</a>");
2190         }
2191         bw.write("</th><th>findings</th><th>lines</th>");
2192         if (mCoverageData)
2193         {
2194            bw.write("<th>%</th><th>");
2195            if (filename != SORT_BY_COVERAGE_INDEX)
2196            {
2197               bw.write("<a href='");
2198               bw.write(SORT_BY_COVERAGE_INDEX);
2199               bw.write("' title='Sort by coverage'>");
2200            }
2201            bw.write("Coverage");
2202            if (filename != SORT_BY_COVERAGE_INDEX)
2203            {
2204               bw.write("</a>");
2205            }
2206            bw.write("</th>");
2207         }
2208         bw.write("<th>%</th><th class='remainder'>");
2209         if (filename != SORT_BY_QUALITY_INDEX)
2210         {
2211            bw.write("<a href='");
2212            bw.write(SORT_BY_QUALITY_INDEX);
2213            bw.write("' title='Sort by quality'>");
2214         }
2215         bw.write("Quality");
2216         if (filename != SORT_BY_QUALITY_INDEX)
2217         {
2218            bw.write("</a>");
2219         }
2220         bw.write("</th></tr></thead>");
2221         bw.write("<tbody>");
2222         bw.write(NEWLINE);
2223
2224         int pos = 0;
2225         final Iterator<FileSummary> i = Arrays.asList(summaries).iterator();
2226         while (i.hasNext())
2227         {
2228            pos++;
2229            final FileSummary file = i.next();
2230            final boolean isLast = !i.hasNext();
2231            appendClassLink(bw, file, fullPackageNames, pos, isLast);
2232         }
2233         bw.write("</tbody>");
2234         bw.write("</table>");
2235      }
2236      else
2237      {
2238         bw.write("EMPTY!?");
2239      }
2240   }
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;
2248        if (fullPackageNames)
2249        {
2250           name = file.getPackage() + '.' + file.getClassName();
2251           link = file.getPackage().replaceAll("\\.", "/") + "/"
2252                   + file.getClassName() + ".html";
2253        }
2254        else
2255        {
2256           name = file.getClassName();
2257           link = file.getClassName() + ".html";
2258        }
2259        bw.write("<tr class='");
2260        bw.write(Java2Html.toOddEvenString(pos));
2261        bw.write("'><td class='classname");
2262        appendIf(bw, isLast, LAST_MARKER);
2263        bw.write("'><a href='");
2264        bw.write(link);
2265        bw.write("'>");
2266        bw.write(name);
2267        bw.write("</a></td>");
2268        hitsCell(bw, String.valueOf(file.getNumberOfFindings()), isLast);
2269        hitsCell(bw, String.valueOf(file.getLinesOfCode()), isLast);
2270        if (mCoverageData)
2271        {
2272           hitsCell(bw, file.getCoverageAsString(), isLast);
2273           bw.write("<td valign='middle' class='hits");
2274           appendIf(bw, isLast, LAST_MARKER);
2275           bw.write("' width='100'>");
2276           bw.write(file.getCoverageBar());
2277           bw.write("</td>");
2278        }
2279        hitsCell(bw, String.valueOf(file.getQuality()) + "%", isLast);
2280        bw.write("<td valign='middle' class='code");
2281        appendIf(bw, isLast, LAST_MARKER);
2282        bw.write("' width='100'>");
2283        bw.write(file.getPercentBar());
2284        bw.write("</td></tr>");
2285        bw.write(NEWLINE);
2286    }
2287
2288   private static String fileNameForOrder (Comparator<FileSummary> order)
2289   {
2290      String filename;
2291      if (order instanceof FileSummary.SortByPackage)
2292      {
2293         filename = SORT_BY_PACKAGE_INDEX;
2294      }
2295      else if (order instanceof FileSummary.SortByQuality)
2296      {
2297         filename = SORT_BY_QUALITY_INDEX;
2298      }
2299      else if (order instanceof FileSummary.SortByCoverage)
2300      {
2301         filename = SORT_BY_COVERAGE_INDEX;
2302      }
2303      else
2304      {
2305         throw new RuntimeException("Order not expected " + order);
2306      }
2307      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   {
2322      bw.write("<td valign='middle' class='hits");
2323      appendIf(bw, isLast, LAST_MARKER);
2324      bw.write("'>");
2325      bw.write(content);
2326      bw.write("</td>");
2327   }
2328
2329   private static void appendIf (BufferedWriter bw, boolean condition,
2330         String str)
2331         throws IOException
2332   {
2333      if (condition)
2334      {
2335         bw.write(str);
2336      }
2337   }
2338
2339   private BufferedWriter openWriter (String filename)
2340       throws IOException
2341   {
2342       return openWriter(mOutDir, filename);
2343   }
2344
2345   private BufferedWriter openWriter (File dir, String filename)
2346       throws IOException
2347   {
2348       FileOutputStream fos = null;
2349       OutputStreamWriter osw = null;
2350       BufferedWriter result = null;
2351       try
2352       {
2353           fos = new FileOutputStream(new java.io.File(dir, filename));
2354           osw = new OutputStreamWriter(fos, Constants.ENCODING_UTF8);
2355           result = new BufferedWriter(osw);
2356       }
2357       catch (RuntimeException ex)
2358       {
2359           IoUtil.close(result);
2360           IoUtil.close(osw);
2361           IoUtil.close(fos);
2362           throw ex;
2363       }
2364       catch (IOException ex)
2365       {
2366           IoUtil.close(result);
2367           IoUtil.close(osw);
2368           IoUtil.close(fos);
2369           throw ex;
2370       }
2371       return result;
2372   }
2373   
2374   private static String createReportLink (
2375       org.jcoderz.phoenix.report.jaxb.File file)
2376   {
2377       final String pkg
2378           = StringUtil.isEmptyOrNull(file.getPackage()) 
2379               ? UNNAMED_PACKAGE_NAME : file.getPackage();
2380       String clazzName = ObjectUtil.toStringOrEmpty(file.getClassname());
2381
2382       // If no class name is reported take the filename.
2383       if (StringUtil.isEmptyOrNull(clazzName))
2384       {
2385           if (!StringUtil.isEmptyOrNull(file.getName()))
2386           {
2387               clazzName = new File(file.getName()).getName();
2388           }
2389           else
2390           {
2391               clazzName = ""; 
2392           }
2393       }
2394       
2395       final String subdir = pkg.replaceAll("\\.", "/");
2396       
2397       return subdir + "/" + clazzName + ".html";
2398   }
2399   
2400   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    {
2407        int result = 0;
2408        result = -(o1.getSeverity().compareTo(o2.getSeverity()));
2409        if (result == 0)
2410        {
2411            result = o1.getSince().compareTo(o2.getSince());
2412        }
2413        if (result == 0)
2414        {
2415            if (o1.getLine() > o2.getLine())
2416            {
2417                result = 1;
2418            }
2419            else if (o1.getLine() < o2.getLine())
2420            {
2421                result = -1;
2422            }
2423            else
2424            {
2425                result = 0;
2426            }
2427        }
2428        if (result == 0)
2429        {
2430            result = o1.getFindingType().compareTo(o2.getFindingType());
2431        }
2432        return result;
2433    }
2434   }
2435   
2436    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        {
2443            final Date since1
2444                = o1.isSetSince() ? o1.getSince() : Date.OLD_DATE;
2445            final Date since2
2446                = o2.isSetSince() ? o2.getSince() : Date.OLD_DATE;
2447            return since1.compareTo(since2);
2448        }
2449    }
2450}
Note: See TracBrowser for help on using the browser.