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

Revision 1336, 42.2 kB (checked in by amandel, 3 years ago)

#2 Quick implementation to detect new findings.
Rollback new introduced severity level NEW
Introduce flag for new and old findings.

  • 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.awt.Color;
36import java.awt.Dimension;
37import java.io.BufferedReader;
38import java.io.File;
39import java.io.FileOutputStream;
40import java.io.FileReader;
41import java.io.FilenameFilter;
42import java.io.IOException;
43import java.io.PrintWriter;
44import java.util.ArrayList;
45import java.util.Collections;
46import java.util.HashMap;
47import java.util.Iterator;
48import java.util.List;
49import java.util.Map;
50import java.util.Set;
51import java.util.StringTokenizer;
52import java.util.TreeSet;
53
54import javax.xml.parsers.DocumentBuilderFactory;
55import javax.xml.parsers.ParserConfigurationException;
56
57import net.sourceforge.chart2d.Chart2DProperties;
58import net.sourceforge.chart2d.Dataset;
59import net.sourceforge.chart2d.GraphChart2DProperties;
60import net.sourceforge.chart2d.GraphProperties;
61import net.sourceforge.chart2d.LBChart2D;
62import net.sourceforge.chart2d.LegendProperties;
63import net.sourceforge.chart2d.MultiColorsProperties;
64import net.sourceforge.chart2d.Object2DProperties;
65
66import org.apache.tools.ant.BuildException;
67import org.apache.tools.ant.Task;
68import org.jcoderz.commons.util.FileUtils;
69import org.jcoderz.commons.util.IoUtil;
70import org.w3c.dom.Document;
71import org.w3c.dom.NamedNodeMap;
72import org.w3c.dom.Node;
73import org.xml.sax.SAXException;
74
75/**
76 * This is the Ant task for the Jcoderz Summary Report.
77 * This task uses a summary database file to create progress diagrams
78 * showing the historical changes of the number of findings.
79 *
80 * TODO: Separate into Command Line tool and Ant Task so that the
81 *       java.awt.headless property can be set by the Ant Task.
82 * <ol>
83 *   <li>Read the summary db file or create a new one if none exists.</li>
84 *   <li>Check whether new folders have been created in the basedir.</li>
85 *   <li>Update the summary db file with the information from new folders.</li>
86 *   <li>Generate the diagrams based on the summary information.</li>
87 * </ol>
88 *
89 * @author Michael Rumpf
90 */
91public class JcSummaryReportAntTask
92   extends Task
93{
94   /** The number of columns in the CSV file. */
95   private static final int COLUMN_COUNT = 12;
96   private static final String[] MONTHS = new String[] {"January", "February",
97         "March", "April", "Mai", "June", "July", "August", "September",
98         "October", "November", "December"};
99   private static final int YEAR_LEN = 4;
100   private static final int MONTH_LEN = 2;
101   private static final int DAY_LEN = 2;
102
103   private static final int ROW_ERROR = 0;
104   private static final int ROW_CPD = 1;
105   private static final int ROW_WARNING = 2;
106   private static final int ROW_DESIGN = 3;
107   private static final int ROW_COVERAGE = 4;
108   private static final int ROW_CODESTYLE = 5;
109   private static final int ROW_INFO = 6;
110   private static final int ROW_FILTERED = 7;
111
112   private static final int ROW_LOC = 0;
113
114   private static final int ROW_QUALITY = 0;
115
116   private static final int LARGE_IMAGE_WIDTH = 800;
117   private static final int LARGE_IMAGE_HEIGHT = 600;
118   private static final int SMALL_IMAGE_WIDTH = 300;
119   private static final int SMALL_IMAGE_HEIGHT = 234;
120   private static final Dimension LARGE_SIZE
121         = new Dimension(LARGE_IMAGE_WIDTH, LARGE_IMAGE_HEIGHT);
122   private static final Dimension SMALL_SIZE
123         = new Dimension(SMALL_IMAGE_WIDTH, SMALL_IMAGE_HEIGHT);
124
125   private String mName = null;
126   private File mDestDir = null;
127   private File mBaseDir = null;
128   private File mSummaryDbFile = null;
129
130   /**
131    * Sets the name of the summary report.
132    *
133    * @param name the report name.
134    */
135   public void setName (String name)
136   {
137      mName = name;
138   }
139
140
141   /**
142    * Sets the destination of the report.
143    *
144    * @param dest the report destination.
145    */
146   public void setDest (String dest)
147   {
148      mDestDir = new File(dest);
149      if (!mDestDir.exists())
150      {
151         mDestDir.mkdirs();
152      }
153   }
154
155
156   /**
157    * Sets the base directory of the reports.
158    *
159    * @param base the base directory.
160    */
161   public void setBaseDir (String base)
162   {
163      mBaseDir = new File(base);
164      if (!mBaseDir.exists())
165      {
166         mBaseDir.mkdirs();
167      }
168   }
169
170
171   /**
172    * Sets the historic database file.
173    *
174    * @param summary the historic database file.
175    */
176   public void setSummary (String summary)
177   {
178      mSummaryDbFile = new File(summary);
179   }
180
181
182   /**
183    * This method is called by Ant for executing this task.
184    *
185    * @throws BuildException whenver a problem occurs.
186    */
187   public void execute ()
188      throws BuildException
189   {
190      try
191      {
192         // Always show this line
193         log("Executing JcSummaryReportAntTask...");
194
195         if (mSummaryDbFile == null)
196         {
197            throw new BuildException("Summary database must be specified!");
198         }
199
200         if (mDestDir == null)
201         {
202            throw new BuildException("Destination folder must be specified!");
203         }
204         if (mDestDir.exists())
205         {
206            if (!mDestDir.isDirectory())
207            {
208               throw new BuildException("The destination directory '" + mDestDir
209                     + "' must be folder!");
210            }
211         }
212         else
213         {
214            mDestDir.mkdirs();
215         }
216
217         if (mBaseDir == null)
218         {
219            throw new BuildException("The base directory must be specified!");
220         }
221         if (!mBaseDir.exists() || !mBaseDir.isDirectory())
222         {
223            throw new BuildException("The base directory '"
224                  + mBaseDir + "' must exists and must be a folder!");
225         }
226
227
228         // Stores a mapping between a timestamp and a Summary instance. This
229         // reads all lines in the summary db file and checks for new folders
230         final Map summaryMap = readSummaryDb(mSummaryDbFile, mBaseDir);
231         // Write the summary file
232         writeSummaryDb(mSummaryDbFile, summaryMap);
233
234         // Create a complex map hierarchy: year -> month -> day -> List
235         // The leaf list is a list of reports that might have been created
236         // on one day.
237         final Map ymd2SummaryMap = createSummaryDbMap(summaryMap);
238
239         createFindingsChart(ymd2SummaryMap, summaryMap, "Findings");
240         createQualityChart(ymd2SummaryMap, summaryMap, "Quality");
241         createLocChart(ymd2SummaryMap, summaryMap, "LOC");
242
243         renderHtmlView(ymd2SummaryMap, summaryMap);
244      }
245      catch (IOException ex)
246      {
247         throw new BuildException("An unexpected IO exception occured!", ex);
248      }
249   }
250
251
252   /**
253    * Writes the summary database file.
254    *
255    * @param summaryDbFile the summary database file.
256    */
257   private void writeSummaryDb (File summaryDbFile, Map summaryMap)
258   {
259      try
260      {
261         log("Writing summary file " + summaryDbFile);
262         summaryDbFile.createNewFile();
263         final FileOutputStream fos = new FileOutputStream(summaryDbFile);
264         final PrintWriter pw = new PrintWriter(fos);
265         pw.println("Timestamp;Error;Warning;Info;"
266               + "Coverage;Loc;CodeLoc;Filtered;Codestyle;Design;Cpd;Quality");
267
268         // Sort the keySet before writing the CSV file
269         final Set keySet = summaryMap.keySet();
270         final List keyList = new ArrayList(keySet);
271         Collections.sort(keyList);
272         final Iterator iter = keyList.iterator();
273         while (iter.hasNext())
274         {
275            final Long ts = (Long) iter.next();
276            final Summary sum = (Summary) summaryMap.get(ts);
277            pw.print(sum.getTimestamp() + ";");
278            pw.print(sum.getError() + ";");
279            pw.print(sum.getWarning() + ";");
280            pw.print(sum.getInfo() + ";");
281            pw.print(sum.getCoverage() + ";");
282            pw.print(sum.getLoc() + ";");
283            pw.print(sum.getCodeLoc() + ";");
284            pw.print(sum.getFiltered() + ";");
285            pw.print(sum.getCodestyle() + ";");
286            pw.print(sum.getDesign() + ";");
287            pw.print(sum.getCpd() + ";");
288            pw.println(FileSummary.calculateQuality(sum.getLoc(),
289                  sum.getInfo(), sum.getWarning(), sum.getError(),
290                  sum.getCoverage(), sum.getFiltered(),
291                  sum.getCodestyle(), sum.getDesign(), sum.getCpd()));
292         }
293         fos.flush();
294         IoUtil.close(pw);
295         IoUtil.close(fos);
296      }
297      catch (IOException ex)
298      {
299         throw new BuildException("Could not write summary database '"
300               + summaryDbFile + "'!");
301      }
302   }
303
304
305   /**
306    * This method creates a mapping between timestamp (e.g. 20061122103015)
307    * and the summary information.
308    *
309    * @param summaryDbFile the summary file.
310    * @param baseDir the basedir with the report folders.
311    * @return The map of timestamps and summary instances.
312    */
313   private Map readSummaryDb (File summaryDbFile, File baseDir)
314   {
315      final Map files = new HashMap();
316      if (summaryDbFile.exists())
317      {
318         log("Reading summary database...");
319         FileReader fr = null;
320         BufferedReader br = null;
321         try
322         {
323            fr = new FileReader(summaryDbFile);
324            br = new BufferedReader(fr);
325            String line = br.readLine();
326            while (line != null)
327            {
328               if (line.indexOf("Error") == -1
329                     && line.indexOf("Warning") == -1
330                     && line.indexOf("Info") == -1
331                     && line.indexOf("Coverage") == -1)
332               {
333                  final Summary sum = createSummary(line, baseDir);
334                  log("Summary information from database: " + sum);
335                  if (sum != null)
336                  {
337                    files.put(new Long(sum.getTimestamp()), sum);
338                  }
339               }
340               line = br.readLine();
341            }
342         }
343         catch (IOException ex)
344         {
345            throw new BuildException("An IO exception occured while reading '"
346                  + summaryDbFile + "'!", ex);
347         }
348         finally
349         {
350            FileUtils.safeClose(br);
351            FileUtils.safeClose(fr);
352         }
353      }
354
355      // Now check whether more folders can be found
356      final String[] folders = baseDir.list(new JcoderReportFilter());
357      for (int i = 0; i < folders.length; i++)
358      {
359         if (!files.containsKey(Long.valueOf(folders[i])))
360         {
361            log("New report sub-folder found: " + folders[i]);
362            final File folder = new File(baseDir, folders[i]);
363            Long ts = null;
364            try
365            {
366               ts = Long.valueOf(folders[i]);
367            }
368            catch (NumberFormatException ex)
369            {
370               log("The folder '" + folders[i]
371                   + "' is not a timestamp folder!");
372            }
373            final Summary sum = readSummaryXml(findSummaryXml(folder), ts);
374            files.put(new Long(sum.getTimestamp()), sum);
375         }
376         else
377         {
378            log("Report sub-folder '" + folders[i] + "' already in database!");
379         }
380      }
381      return files;
382   }
383
384   private static File findSummaryXml(File folder)
385   {
386       File result = null;
387       if (folder.isFile() && "summary.xml".equals(folder.getName()))
388       {
389           result = folder;
390       }
391       else if (folder.isDirectory())
392       {
393           for (File file : folder.listFiles())
394           {
395               result = findSummaryXml(file);
396               if (result != null)
397               {
398                   break;
399               }
400           }
401       }
402       return result;
403   }
404
405
406   private Summary readSummaryXml (File summaryXml, Long folderTimestamp)
407   {
408      Summary sum = null;
409      try
410      {
411         final DocumentBuilderFactory factory =
412               DocumentBuilderFactory.newInstance();
413         final Document doc = factory.newDocumentBuilder().parse(summaryXml);
414         // There is only one root node
415         final Node root = doc.getDocumentElement();
416         final NamedNodeMap attrs = root.getAttributes();
417         // Ignore the timestamp as the external script might choose another
418         // timestamp for the folder than the jcoderz-report has chosen
419         // for the XML file.
420         long ts;
421         if (folderTimestamp == null)
422         {
423             ts = Long.parseLong(
424                     attrs.getNamedItem("timestamp").getNodeValue().trim());
425         }
426         else
427         {
428             ts = folderTimestamp.longValue();
429         }
430
431         final int filtered = Integer.parseInt(
432            attrs.getNamedItem("filtered").getNodeValue().trim());
433         final int coverage = Integer.parseInt(
434            attrs.getNamedItem("coverage").getNodeValue().trim());
435         final int info = Integer.parseInt(
436            attrs.getNamedItem("info").getNodeValue().trim());
437         final int codestyle = Integer.parseInt(
438            attrs.getNamedItem("code-style").getNodeValue().trim());
439         final int design = Integer.parseInt(
440            attrs.getNamedItem("design").getNodeValue().trim());
441         final int warning = Integer.parseInt(
442            attrs.getNamedItem("warning").getNodeValue().trim());
443         final int cpd = Integer.parseInt(
444            attrs.getNamedItem("cpd").getNodeValue().trim());
445         final int error = Integer.parseInt(
446               attrs.getNamedItem("error").getNodeValue().trim());
447         final int loc = Integer.parseInt(
448               attrs.getNamedItem("loc").getNodeValue().trim());
449         final int codeloc = Integer.parseInt(
450               attrs.getNamedItem("codeLoc").getNodeValue().trim());
451         final double quality = Double.parseDouble(
452               attrs.getNamedItem("quality").getNodeValue());
453         sum = new Summary(ts, error, warning, info, coverage, loc, codeloc, 
454             filtered, codestyle, design, cpd, quality, summaryXml);
455      }
456      catch (ParserConfigurationException ex)
457      {
458         throw new BuildException("A parser configuration error occured!", ex);
459      }
460      catch (SAXException ex)
461      {
462         throw new BuildException("A SAX exception occured while parsing '"
463               + summaryXml + "'!", ex);
464      }
465      catch (IOException ex)
466      {
467         throw new BuildException("An IO exception occured while parsing '"
468               + summaryXml + "'!", ex);
469      }
470
471      return sum;
472   }
473
474
475   private void storeInMap (Map files, long timestamp, Summary sum)
476   {
477      log("Storing summmary: " + sum);
478      final Long ts = new Long(timestamp);
479      final String tsStr = ts.toString();
480      final Integer y = Integer.valueOf(tsStr.substring(0, 4));
481      final Integer m = Integer.valueOf(tsStr.substring(4, 6));
482      final Integer d = Integer.valueOf(tsStr.substring(6, 8));
483      if (!files.containsKey(y))
484      {
485         log("Adding hash map for year " + y);
486         files.put(y, new HashMap());
487      }
488      final Map year = (Map) files.get(y);
489      if (!year.containsKey(m))
490      {
491         log("Adding hash map for month " + m);
492         year.put(m, new HashMap());
493      }
494      final Map month = (Map) year.get(m);
495      if (!month.containsKey(d))
496      {
497         log("Adding report list for day " + d);
498         month.put(d, new ArrayList());
499      }
500      final List reports = (List) month.get(d);
501      reports.add(sum);
502   }
503
504
505   private Map createSummaryDbMap (Map ts2SumMap)
506   {
507      final Map files = new HashMap();
508      final Iterator iter = ts2SumMap.keySet().iterator();
509      while (iter.hasNext())
510      {
511         final Long ts = (Long) iter.next();
512         final Summary sum = (Summary) ts2SumMap.get(ts);
513         storeInMap(files, ts.longValue(), sum);
514      }
515      return files;
516   }
517
518
519   private Summary createSummary (String sum, File baseDir)
520   {
521      final StringTokenizer strtok = new StringTokenizer(sum, ";");
522      if (strtok.countTokens() != COLUMN_COUNT)
523      {
524         throw new BuildException(
525               "Number of columns in summary database file '"
526               + strtok.countTokens()
527               + "' does not match the expected number of '"
528               + COLUMN_COUNT + "'!");
529      }
530      Summary summary = null;
531      final long timestamp = Long.parseLong(strtok.nextToken());
532      final int error = Integer.parseInt(strtok.nextToken());
533      final int warning = Integer.parseInt(strtok.nextToken());
534      final int info = Integer.parseInt(strtok.nextToken());
535      final int coverage = Integer.parseInt(strtok.nextToken());
536      final int loc = Integer.parseInt(strtok.nextToken());
537      final int codeloc = Integer.parseInt(strtok.nextToken());
538      final int filtered = Integer.parseInt(strtok.nextToken());
539      final int codestyle = Integer.parseInt(strtok.nextToken());
540      final int design = Integer.parseInt(strtok.nextToken());
541      final int cpd = Integer.parseInt(strtok.nextToken());
542      final double quality = Double.parseDouble(strtok.nextToken());
543      final File summaryFile = new File(baseDir, String.valueOf(timestamp)
544            + File.separator + "summary.xml");
545      if (summaryFile.exists() && summaryFile.isFile())
546      {
547         summary = new Summary(timestamp, error, warning, info, coverage, loc,
548            codeloc, filtered, codestyle, design, cpd,
549            quality, summaryFile);
550      }
551      else
552      {
553         log("Summary file does not exist: " + summaryFile);
554      }
555      return summary;
556   }
557
558
559   /**
560    * Creates the quality chart.
561    * The output is a "quality.png" and a "quality_small.png" image.
562    *
563    * @throws IOException if the image can not be written.
564    */
565   private void createQualityChart (Map ymd2SummaryMap, Map summaryMap,
566       String title)
567         throws IOException
568   {
569      final Set labels = new TreeSet(summaryMap.keySet());
570      if (labels.size() == 0)
571      {
572         throw new RuntimeException("No reports found for chart!");
573      }
574
575      final String[] legendLabels = {"Quality"};
576
577      // Configure dataset
578      final Dataset dataset = new Dataset(legendLabels.length,
579         summaryMap.size(), 1);
580      final List labelsAxisLabels = new ArrayList();
581      final List sortedKeyList = new ArrayList();
582      sortedKeyList.addAll(summaryMap.keySet());
583      Collections.sort(sortedKeyList);
584      final Iterator iter = sortedKeyList.iterator();
585      int i = 0;
586      while (iter.hasNext())
587      {
588         final Long ts = (Long) iter.next();
589         labelsAxisLabels.add(ts.toString());
590         final Summary sum = (Summary) summaryMap.get(ts);
591
592         dataset.set(ROW_QUALITY, i, 0, (float) sum.getQuality());
593         i++;
594      }
595
596      final GraphChart2DProperties graphChart2DProps
597            = createGraphChart2DProperties(labelsAxisLabels, title);
598      final MultiColorsProperties multiColorsProps
599            = createMultiColorsProperties(new Color[] {Color.GREEN});
600      createChart(title, legendLabels, dataset, graphChart2DProps,
601          multiColorsProps);
602   }
603
604
605   /**
606    * Creates the lines-of-code chart.
607    * The output is a "loc.png" and a "loc_small.png" image.
608    *
609    * @throws IOException if the image can not be written.
610    */
611   private void createLocChart (Map ymd2SummaryMap, Map summaryMap,
612       String title)
613         throws IOException
614   {
615      final Set labels = new TreeSet(summaryMap.keySet());
616      if (labels.size() == 0)
617      {
618         throw new RuntimeException("No reports found for chart!");
619      }
620
621      final String[] legendLabels = {"Loc"};
622
623      // Configure dataset
624      final Dataset dataset = new Dataset(legendLabels.length,
625         summaryMap.size(), 1);
626      final List labelsAxisLabels = new ArrayList();
627      final List sortedKeyList = new ArrayList();
628      sortedKeyList.addAll(summaryMap.keySet());
629      Collections.sort(sortedKeyList);
630      final Iterator iter = sortedKeyList.iterator();
631      int i = 0;
632      while (iter.hasNext())
633      {
634         final Long ts = (Long) iter.next();
635         labelsAxisLabels.add(ts.toString());
636         final Summary sum = (Summary) summaryMap.get(ts);
637
638         dataset.set(ROW_LOC, i, 0, sum.getLoc());
639         i++;
640      }
641
642      final GraphChart2DProperties graphChart2DProps
643            = createGraphChart2DProperties(labelsAxisLabels, title);
644      final MultiColorsProperties multiColorsProps
645            = createMultiColorsProperties(
646                new Color[] {new Color(115, 117, 255)});
647      createChart(title, legendLabels, dataset, graphChart2DProps,
648          multiColorsProps);
649   }
650
651
652   /**
653    * Creates the findings chart.
654    * The output is a "findings.png" and a "findings_small.png" image.
655    *
656    * @throws IOException if the image can not be written.
657    */
658   private void createFindingsChart (Map ymd2SummaryMap, Map summaryMap,
659       String title)
660         throws IOException
661   {
662     final Set labels = new TreeSet(summaryMap.keySet());
663     if (labels.size() == 0)
664     {
665        throw new RuntimeException("No reports found for chart!");
666     }
667
668     final String[] legendLabels = {"Error", "Warning", "Info", "Coverage",
669         "Filtered", "Codestyle", "Design", "Cpd"};
670
671     // Configure dataset
672     final Dataset dataset = new Dataset(legendLabels.length,
673        summaryMap.size(), 1);
674     final List labelsAxisLabels = new ArrayList();
675     final List sortedKeyList = new ArrayList();
676     sortedKeyList.addAll(summaryMap.keySet());
677     Collections.sort(sortedKeyList);
678     final Iterator iter = sortedKeyList.iterator();
679     int i = 0;
680     while (iter.hasNext())
681     {
682        final Long ts = (Long) iter.next();
683        labelsAxisLabels.add(ts.toString());
684        final Summary sum = (Summary) summaryMap.get(ts);
685
686        dataset.set(ROW_ERROR, i, 0, sum.getError());
687        dataset.set(ROW_CPD, i, 0, sum.getCpd());
688        dataset.set(ROW_WARNING, i, 0, sum.getWarning());
689        dataset.set(ROW_DESIGN, i, 0, sum.getDesign());
690        dataset.set(ROW_COVERAGE, i, 0, sum.getCoverage());
691        dataset.set(ROW_CODESTYLE, i, 0, sum.getCodestyle());
692        dataset.set(ROW_INFO, i, 0, sum.getInfo());
693        dataset.set(ROW_FILTERED, i, 0, sum.getFiltered());
694        i++;
695     }
696
697     final GraphChart2DProperties graphChart2DProps
698           = createGraphChart2DProperties(labelsAxisLabels, title);
699     final MultiColorsProperties multiColorsProps
700           = createMultiColorsProperties(new Color[] {
701              new Color(255, 65, 66),
702              new Color(214, 243, 214),
703              new Color(255, 162, 66),
704              new Color(255, 243, 66),
705              new Color(255, 255, 231),
706              new Color(255, 243, 132),
707              new Color(214, 211, 255),
708              new Color(247, 247, 247)
709              });
710     createChart(title, legendLabels, dataset, graphChart2DProps,
711             multiColorsProps);
712   }
713
714
715   private void createChart (final String title, final String[] legendLabels,
716           final Dataset dataset,
717           final GraphChart2DProperties graphChart2DProps,
718           final MultiColorsProperties multiColorsProps) throws IOException
719   {
720      // Configure chart
721      final LBChart2D chart2D = new LBChart2D();
722      chart2D.setObject2DProperties(createObject2DProps());
723      chart2D.setChart2DProperties(createChart2DProperties());
724      chart2D.setLegendProperties(createLegendProperties(legendLabels));
725
726      chart2D.setGraphChart2DProperties(graphChart2DProps);
727      chart2D.addGraphProperties(createGraphProperties());
728      chart2D.addDataset(dataset);
729      chart2D.addMultiColorsProperties(multiColorsProps);
730
731      chart2D.setMaximumSize(LARGE_SIZE);
732      chart2D.setPreferredSize(LARGE_SIZE);
733
734      final String titleSmall = title.toLowerCase();
735      if (chart2D.validate(false))
736      {
737         java.io.File file = new java.io.File(mDestDir, titleSmall + ".png");
738         javax.imageio.ImageIO.write(chart2D.getImage(), "PNG", file);
739         chart2D.setMaximumSize(SMALL_SIZE);
740         chart2D.setPreferredSize(SMALL_SIZE);
741         chart2D.pack();
742         file = new java.io.File(mDestDir, titleSmall + "_small.png");
743         javax.imageio.ImageIO.write(chart2D.getImage(), "PNG", file);
744      }
745      else
746      {
747         chart2D.validate(true);
748      }
749   }
750
751
752   private MultiColorsProperties createMultiColorsProperties (Color[] colors)
753   {
754      // Configure graph component colors
755      final MultiColorsProperties multiColorsProps
756          = new MultiColorsProperties();
757      multiColorsProps.setColorsCustomize(true);
758      multiColorsProps.setColorsCustom(colors);
759      return multiColorsProps;
760   }
761
762
763   private Object2DProperties createObject2DProps ()
764   {
765      final Object2DProperties object2DProps = new Object2DProperties();
766      object2DProps.setObjectBackgroundLightSource(Object2DProperties.NONE);
767      object2DProps.setObjectBackgroundColor(Color.LIGHT_GRAY);
768      return object2DProps;
769   }
770
771
772   private GraphProperties createGraphProperties ()
773   {
774      // Configure graph properties
775      final GraphProperties graphProps = new GraphProperties();
776      graphProps.setGraphBarsExistence(false);
777      graphProps.setGraphLinesExistence(true);
778      graphProps.setGraphLinesThicknessModel(1);
779      return graphProps;
780   }
781
782
783   private Chart2DProperties createChart2DProperties ()
784   {
785      // Configure chart properties
786      final Chart2DProperties chart2DProps = new Chart2DProperties();
787      chart2DProps.setChartDataLabelsPrecision(0);
788      return chart2DProps;
789   }
790
791
792   private LegendProperties createLegendProperties (String[] legendLabels)
793   {
794      // Configure legend properties
795      final LegendProperties legendProps = new LegendProperties();
796      legendProps.setLegendLabelsTexts(legendLabels);
797      return legendProps;
798   }
799
800
801   private GraphChart2DProperties createGraphChart2DProperties (
802           List labelsAxisLabels, String title)
803   {
804      // Configure graph chart properties
805      final GraphChart2DProperties graphChart2DProps =
806            new GraphChart2DProperties();
807      final String[] labelsTexts = new String[labelsAxisLabels.size()];
808      graphChart2DProps.setLabelsAxisLabelsTexts((String[])
809            labelsAxisLabels.toArray(labelsTexts));
810      // The name of the Y-Axis
811      graphChart2DProps.setNumbersAxisTitleText(title);
812      graphChart2DProps.setLabelsAxisTicksAlignment(
813            GraphChart2DProperties.CENTERED);
814      return graphChart2DProps;
815   }
816
817
818   private void renderHtmlView (Map summaryDbMap, Map ts2BaseFolder)
819   {
820      try
821      {
822         final FileOutputStream fos = new FileOutputStream(
823               new File(mDestDir, "index.html"));
824         final PrintWriter pw = new PrintWriter(fos);
825         pw.println("<html><head>\n<title>");
826         pw.println(mName);
827         pw.println("</title>");
828         pw.println("<style type=\"text/css\">");
829         pw.println("<!--");
830
831         pw.println("body { font-family: verdana, tahoma; }");
832         pw.println("img { border:none; }");
833         pw.println("a { text-decoration: none; }");
834         pw.println("a:hover { text-decoration: underline; }");
835
836         pw.println(".bold { font-weight: bold; }");
837         pw.println("table { border-collapse: collapse; "
838              + "border: 1px solid black; text-align: right; }");
839         pw.println("table td { padding: 5px; border-collapse: collapse; "
840              + "border: 1px solid black; font-size: small; vertical-align: "
841              + "top; }");
842         pw.println("td.month { padding: 10px; border: 1px solid black; "
843              + "margin: 1em; background-color: #eeeeee; text-align: left; "
844              + "font-size: large; }");
845         pw.println("td.day_odd { background: #aaaaaa; }");
846         pw.println("td.day_even { background: #cccccc; }");
847         pw.println("td.quality_odd { background:#88b888; }");
848         pw.println("td.quality_even { background:#88e888; }");
849         pw.println("td.loc_odd { background:#7777cf; }");
850         pw.println("td.loc_even { background:#7777ff; }");
851         pw.println("td.filtered_odd { background:#e0e0e0; }");
852         pw.println("td.filtered_even { background:#f7f7f7; }");
853         pw.println("td.ok_odd { background:#e0e0e0; }");
854         pw.println("td.ok_even { background:#ffffff; }");
855         pw.println("td.info_odd { background:#b0b0e0; }");
856         pw.println("td.info_even { background:#d0d0ff; }");
857         pw.println("td.codestyle_odd { background:#e0e070; }");
858         pw.println("td.codestyle_even { background:#fff080; }");
859         pw.println("td.coverage_odd { background:#e0e0c0; }");
860         pw.println("td.coverage_even { background:#ffffe0; }");
861         pw.println("td.design_odd {  background:#e0d020; }");
862         pw.println("td.design_even { background:#fff040; }");
863         pw.println("td.warning_odd { background:#e08020; }");
864         pw.println("td.warning_even { background:#ffa040; }");
865         pw.println("td.cpd_odd { background: #c0e0c0; }");
866         pw.println("td.cpd_even { background:#d0f0d0; }");
867         pw.println("td.error_odd { background:#e08080; }");
868         pw.println("td.error_even { background:#ff4040; }");
869
870         pw.println("-->");
871         pw.println("</style>");
872         pw.println("<body>");
873
874         pw.println("<table>");
875
876         pw.println("<tr>");
877         pw.println("<td colspan=\"4\">");
878         pw.println("<a href=\"findings.png\"><img src=\"findings_small.png\"></a>");
879         pw.println("</td><td colspan=\"4\">");
880         pw.println("<a href=\"quality.png\"><img src=\"quality_small.png\"></a>");
881         pw.println("</td><td colspan=\"3\">");
882         pw.println("<a href=\"loc.png\"><img src=\"loc_small.png\"></a>");
883         pw.println("</td>");
884         pw.println("</tr>");
885
886
887         final Map yearMap = summaryDbMap;
888         final List sortedYearList = new ArrayList();
889         sortedYearList.addAll(yearMap.keySet());
890         Collections.sort(sortedYearList);
891         Collections.reverse(sortedYearList);
892         final Iterator yearIter = sortedYearList.iterator();
893         while (yearIter.hasNext())
894         {
895            final Integer year = (Integer) yearIter.next();
896            log("Rendering detail tables for year " + year);
897
898            final Map monthMap = (Map) summaryDbMap.get(year);
899            final List sortedMonthList = new ArrayList();
900            sortedMonthList.addAll(monthMap.keySet());
901            Collections.sort(sortedMonthList);
902            Collections.reverse(sortedMonthList);
903            final Iterator monthIter = sortedMonthList.iterator();
904            while (monthIter.hasNext())
905            {
906               final Integer month = (Integer) monthIter.next();
907               log("Rendering detail table for month " + month);
908
909               pw.println("<tr><td class=\"month bold\" colspan=\"11\">"
910                   + MONTHS[month.intValue() - 1]
911                   + " " + year + "</td></tr>");
912               pw.println("<tr>");
913               pw.println("  <td class=\"day_even bold\">Date</td>");
914               pw.println("  <td class=\"quality_even bold\">Quality</td>");
915               pw.println("  <td class=\"loc_even bold\">Loc</td>");
916               pw.println("  <td class=\"error_even bold\">Error</td>");
917               pw.println("  <td class=\"cpd_even bold\">Cpd</td>");
918               pw.println("  <td class=\"warning_even bold\">Warning</td>");
919               pw.println("  <td class=\"design_even bold\">Design</td>");
920               pw.println("  <td class=\"coverage_even bold\">Coverage</td>");
921               pw.println("  <td class=\"codestyle_even bold\">Codestyle</td>");
922               pw.println("  <td class=\"info_even bold\">Info</td>");
923               pw.println("  <td class=\"filtered_even bold\">Filtered</td>");
924               pw.println("</tr>");
925
926               int i = 0;
927               final Map dayMap = (Map) monthMap.get(month);
928               final List sortedDayList = new ArrayList();
929               sortedDayList.addAll(dayMap.keySet());
930               Collections.sort(sortedDayList);
931               Collections.reverse(sortedDayList);
932               final Iterator dayIter = sortedDayList.iterator();
933               while (dayIter.hasNext())
934               {
935                  final Integer day = (Integer) dayIter.next();
936                  final List reportsList = (List) dayMap.get(day);
937
938                  final Iterator reportsIter = reportsList.iterator();
939                  while (reportsIter.hasNext())
940                  {
941                     pw.println("<tr>");
942
943                     final Summary sum = (Summary) reportsIter.next();
944                     String suffix = "odd";
945                     if ((i & 1) == 0)
946                     {
947                        suffix = "even";
948                     }
949
950                     pw.println(wrapTimestamp(sum, suffix));
951
952                     pw.println(wrapValue(sum.getQuality(), "quality", suffix));
953                     pw.println(wrapValue(sum.getLoc(), "loc", suffix));
954
955                     pw.println(wrapValue(sum, sum.getError(), "error", suffix));
956                     pw.println(wrapValue(sum, sum.getCpd(), "cpd", suffix));
957                     pw.println(wrapValue(sum, sum.getWarning(), "warning", suffix));
958                     pw.println(wrapValue(sum, sum.getDesign(), "design", suffix));
959                     pw.println(wrapValue(sum, sum.getCoverage(), "coverage", suffix));
960                     pw.println(wrapValue(sum, sum.getCodestyle(), "codestyle", suffix));
961                     pw.println(wrapValue(sum, sum.getInfo(), "info", suffix));
962                     pw.println(wrapValue(sum, sum.getFiltered(), "filtered", suffix));
963
964                     pw.println("</tr>");
965
966                     i++;
967                  }
968               }
969            }
970         }
971
972         pw.println("</table>");
973         pw.println("</body>");
974         pw.println("</html>");
975         pw.close();
976         fos.flush();
977         fos.close();
978      }
979      catch (IOException ex)
980      {
981         throw new BuildException("An IO exception occured!", ex);
982      }
983   }
984
985
986   private String wrapTimestamp (Summary sum, String oddeven)
987   {
988      final StringBuilder sb = new StringBuilder();
989      sb.append("<td class=\"day_");
990      sb.append(oddeven);
991      sb.append("\">");
992      if (sum.getSummaryXml() != null)
993      {
994         sb.append("<a href=\"");
995         sb.append(sum.getTimestamp());
996         sb.append("/index.html\">");
997      }
998      sb.append(extractDay(sum.getTimestamp()));
999      sb.append(".");
1000      sb.append(extractMonth(sum.getTimestamp()));
1001      sb.append(".");
1002      sb.append(extractYear(sum.getTimestamp()));
1003      if (sum.getSummaryXml() != null)
1004      {
1005         sb.append("</a>");
1006      }
1007      return sb.toString();
1008   }
1009
1010
1011   private String wrapValue (Summary sum, int value, String severity,
1012       String oddeven)
1013   {
1014      final StringBuilder sb = new StringBuilder();
1015      sb.append("<td class=\"");
1016      sb.append(severity);
1017      sb.append("_");
1018      sb.append(oddeven);
1019      sb.append("\">");
1020      if (sum.getSummaryXml() != null)
1021      {
1022         sb.append("<a href=\"" + sum.getTimestamp()
1023               + "/findings.html");
1024         sb.append("#");
1025         sb.append(severity);
1026         sb.append("\">");
1027      }
1028      sb.append(value);
1029      if (sum.getSummaryXml() != null)
1030      {
1031         sb.append("</a>");
1032      }
1033      sb.append("</td>");
1034      return sb.toString();
1035   }
1036
1037
1038   private String wrapValue (int value, String severity, String oddeven)
1039   {
1040      final StringBuilder sb = new StringBuilder();
1041      sb.append("<td class=\"");
1042      sb.append(severity);
1043      sb.append("_");
1044      sb.append(oddeven);
1045      sb.append("\">");
1046      sb.append(value);
1047      sb.append("</td>");
1048      return sb.toString();
1049   }
1050
1051
1052   private String wrapValue (double value, String severity, String oddeven)
1053   {
1054      final StringBuilder sb = new StringBuilder();
1055      sb.append("<td class=\"");
1056      sb.append(severity);
1057      sb.append("_");
1058      sb.append(oddeven);
1059      sb.append("\">");
1060      sb.append(value);
1061      sb.append("%");
1062      sb.append("</td>");
1063      return sb.toString();
1064   }
1065
1066
1067   private String extractDay (long timestamp)
1068   {
1069      final String ts = String.valueOf(timestamp);
1070      return ts.substring(YEAR_LEN + MONTH_LEN, YEAR_LEN + MONTH_LEN + DAY_LEN);
1071   }
1072
1073
1074   private String extractMonth (long timestamp)
1075   {
1076      final String ts = String.valueOf(timestamp);
1077      return ts.substring(YEAR_LEN, YEAR_LEN + MONTH_LEN);
1078   }
1079
1080
1081   private String extractYear (long timestamp)
1082   {
1083      final String ts = String.valueOf(timestamp);
1084      return ts.substring(0, YEAR_LEN);
1085   }
1086
1087
1088   /**
1089    * Class holds data from the summary database file.
1090    *
1091    * @author Michael Rumpf
1092    */
1093   private static final class Summary
1094   {
1095      private final long mTimestamp;
1096      private final int mError;
1097      private final int mFiltered;
1098      private final int mDesign;
1099      private final int mCodestyle;
1100      private final int mCpd;
1101      private final int mWarning;
1102      private final int mInfo;
1103      private final int mCoverage;
1104      private final int mLoc;
1105      /** Lines of code reported by coverage test. */
1106      private final int mCodeLoc;
1107      private final double mQuality;
1108      private final File mSummaryXml;
1109
1110      public Summary (long timestamp, int error, 
1111          int warning, int info, int coverage, int loc, int codeloc, 
1112          int filtered, int codestyle, int design, int cpd, double quality)
1113      {
1114         mFiltered = filtered;
1115         mCodestyle = codestyle;
1116         mDesign = design;
1117         mCpd = cpd;
1118         mTimestamp = timestamp;
1119         mError = error;
1120         mWarning = warning;
1121         mInfo = info;
1122         mCoverage = coverage;
1123         mLoc = loc;
1124         mCodeLoc = codeloc;
1125         mQuality = quality;
1126         mSummaryXml = null;
1127      }
1128
1129      public Summary (long timestamp, int error, 
1130          int warning, int info, int coverage, int loc, int codeloc, 
1131          int filtered, int codestyle, int design, int cpd,
1132          double quality, File summaryXml)
1133      {
1134         mFiltered = filtered;
1135         mCodestyle = codestyle;
1136         mDesign = design;
1137         mCpd = cpd;
1138         mTimestamp = timestamp;
1139         mError = error;
1140         mWarning = warning;
1141         mInfo = info;
1142         mCoverage = coverage;
1143         mLoc = loc;
1144         mCodeLoc = codeloc;
1145         mQuality = quality;
1146         mSummaryXml = summaryXml;
1147      }
1148
1149      public long getTimestamp ()
1150      {
1151         return mTimestamp;
1152      }
1153
1154      public int getError ()
1155      {
1156         return mError;
1157      }
1158
1159      public int getWarning ()
1160      {
1161         return mWarning;
1162      }
1163
1164      public int getInfo ()
1165      {
1166         return mInfo;
1167      }
1168
1169      public int getCoverage ()
1170      {
1171         return mCoverage;
1172      }
1173
1174      public int getFiltered ()
1175      {
1176         return mFiltered;
1177      }
1178
1179      public int getCodestyle ()
1180      {
1181         return mCodestyle;
1182      }
1183
1184      public int getDesign ()
1185      {
1186         return mDesign;
1187      }
1188
1189      public int getCpd ()
1190      {
1191         return mCpd;
1192      }
1193
1194      public int getLoc ()
1195      {
1196         return mLoc;
1197      }
1198
1199      public int getCodeLoc ()
1200      {
1201         return mCodeLoc;
1202      }
1203
1204      public double getQuality ()
1205      {
1206         return mQuality;
1207      }
1208
1209      public File getSummaryXml ()
1210      {
1211         return mSummaryXml;
1212      }
1213
1214      public int hashCode ()
1215      {
1216         // MAXINT = 4294967296
1217         // ts 20061122001122
1218         // value to extract mmddHHMMSS from YYYY
1219         final long div = 10000000000L;
1220         final int mul = 1000000;
1221         // max 1231235959
1222         final long rest = mTimestamp % div;
1223         // year 2006
1224         final long year = mTimestamp / div;
1225         // hash 2006000000 + 1231235959 = 3237235959 (Overflow in year 3063)
1226         return (int) (year * mul + rest);
1227      }
1228
1229      public boolean equals (Object sum)
1230      {
1231         boolean result = false;
1232         if (sum != null)
1233         {
1234            if (sum == this)
1235            {
1236               result = true;
1237            }
1238            else
1239            {
1240               result = hashCode() == sum.hashCode();
1241            }
1242         }
1243         return result;
1244      }
1245
1246      public String toString ()
1247      {
1248         return "[" + mTimestamp + ", " + mError + ", " 
1249             + mWarning + ", " + mInfo + ", " + mCoverage + ", " + mLoc
1250             + ", " + mCodeLoc + ", " + mFiltered + ", " + mCodestyle + ", "
1251             + mDesign + ", " + mCpd + ", " + mQuality + ", " 
1252             + mSummaryXml + "]";
1253      }
1254   }
1255
1256
1257   /**
1258    * Filter for folders that contain a 'summary.xml' file.
1259    *
1260    * @author Michael Rumpf
1261    */
1262   public static class JcoderReportFilter
1263      implements FilenameFilter
1264   {
1265      /**
1266       * Only accept folder that contain a 'summary.xml' file.
1267       *
1268       * @param dir the directory where the file is located.
1269       * @param name the name of the file.
1270       */
1271      public boolean accept (File dir, String name)
1272      {
1273         boolean result = false;
1274         try
1275         {
1276             Long.parseLong(name);
1277             final File folder = new File(dir, name);
1278             if (folder.exists() && folder.isDirectory())
1279             {
1280                final File summaryXml = findSummaryXml(folder);
1281                result = summaryXml != null 
1282                    && summaryXml.exists() && summaryXml.isFile();
1283             }
1284         }
1285         catch (NumberFormatException ex)
1286         {
1287             // result is false;
1288         }
1289         return result;
1290      }
1291   }
1292}
Note: See TracBrowser for help on using the browser.