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

Revision 1454, 21.4 kB (checked in by amandel, 3 years ago)

Support for a 'global' finding that is not related to any source.

  • 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.FileWriter;
38import java.io.IOException;
39import java.util.HashMap;
40import java.util.Iterator;
41import java.util.List;
42import java.util.Map;
43import java.util.Set;
44import java.util.TreeSet;
45import java.util.Map.Entry;
46
47import net.sourceforge.chart2d.Chart2DProperties;
48import net.sourceforge.chart2d.Dataset;
49import net.sourceforge.chart2d.GraphChart2DProperties;
50import net.sourceforge.chart2d.GraphProperties;
51import net.sourceforge.chart2d.LBChart2D;
52import net.sourceforge.chart2d.LegendProperties;
53import net.sourceforge.chart2d.MultiColorsProperties;
54import net.sourceforge.chart2d.Object2DProperties;
55
56import org.jcoderz.commons.util.IoUtil;
57import org.jcoderz.commons.util.ObjectUtil;
58import org.jcoderz.phoenix.report.jaxb.File;
59import org.jcoderz.phoenix.report.jaxb.Report;
60
61/**
62 * TODO: Extend this class to run historic tool on existing reports.
63 *
64 * @author Andreas Mandel
65 */
66public final class StatisticCollector
67{
68   private static final int MAX_SERVICE_PACKAGES = 15;
69   private static final int ROW_ERROR = 0;
70   private static final int ROW_CPD = 1;
71   private static final int ROW_WARNING = 2;
72   private static final int ROW_DESIGN = 3;
73   private static final int ROW_CODE_STYLE = 4;
74   private static final int ROW_INFO = 5;
75   private static final int ROW_COVERAGE = 6;
76   private static final int PERCENT = 100;
77   private static final int LARGE_IMAGE_WIDTH = 1000;
78   private static final int LARGE_IMAGE_HEIGHT = 600;
79   private static final int SMALL_IMAGE_WIDTH = 800;
80   private static final int SMALL_IMAGE_HEIGHT = 300;
81   private static final Dimension LARGE_SIZE
82         = new Dimension(LARGE_IMAGE_WIDTH, LARGE_IMAGE_HEIGHT);
83   private static final Dimension SMALL_SIZE
84         = new Dimension(SMALL_IMAGE_WIDTH, SMALL_IMAGE_HEIGHT);
85
86   /** The report to be used as input. */
87   private final Report mReport;
88
89   /** The package prefix to check for. */
90   private final String mPrefix;
91
92   /** The dir to be used as output. */
93   private final java.io.File mOutDir;
94
95   /** The timestamp when the report was initiated. */
96   private final String mTimestamp;
97
98
99   /**
100    * Creates a new StatisticCollector object.
101    *
102    * @param report the input report.
103    * @param outDir the output dir for the charts and summary xml.
104    * @param timestamp the timestamp for the summary db entry.
105    */
106   public StatisticCollector (Report report, java.io.File outDir,
107         String timestamp)
108   {
109      mReport = report;
110      // TODO: Try to find this automagically, 2 - 3 packagelevels
111      mPrefix = "org.jcoderz.";
112      mOutDir = outDir;
113      mTimestamp = timestamp;
114   }
115
116   /**
117    * Creates a new StatisticCollector object.
118    *
119    * @param report the input report.
120    * @param prefix the package prefix
121    * @param outDir the output dir for the charts and summary xml.
122    * @param timestamp the timestamp for the summary db entry.
123    */
124   public StatisticCollector (Report report, String prefix, java.io.File outDir,
125         String timestamp)
126   {
127      mReport = report;
128      mPrefix = prefix;
129      mOutDir = outDir;
130      mTimestamp = timestamp;
131   }
132
133   /**
134    * Creates the charts.
135    * @throws IOException if the image can not be written.
136    */
137   public void createCharts ()
138         throws IOException
139   {
140      // chart should contain x axis with packages,
141      // y axis with number of lines
142      // (2 lines 1 for production code one for test code)
143
144      final Map<String, FileSummary> productionPackages
145          = new HashMap<String, FileSummary>();
146      final Map<String, FileSummary> testPackages
147          = new HashMap<String, FileSummary>();
148      final Map<String, FileSummary> allPackages
149          = new HashMap<String, FileSummary>();
150
151      summarize(productionPackages, testPackages, allPackages);
152
153      writeSummary(allPackages);
154
155      allPackages.clear();  // make GC happy
156
157      final Map<String, FileSummary> production;
158      final Map<String, FileSummary> test;
159      final String level;
160
161      if (productionPackages.size() > MAX_SERVICE_PACKAGES)
162      {
163         production = generateServiceLevelMap(productionPackages);
164         test = generateServiceLevelMap(testPackages);
165         level = "Service";
166      }
167      else
168      {
169         production = productionPackages;
170         test = testPackages;
171         level = "Package";
172      }
173
174      createLocChart(production, test, level);
175
176      createQualityChart(production, level);
177   }
178
179   /**
180    * Writes a summary xml for the given summary map.
181    * @param packages a map with the package names / summaries to be
182    *       used.
183    * @throws IOException if the XML file can not be written
184    */
185   private void writeSummary (Map<String, FileSummary> packages)
186         throws IOException
187   {
188      final StringBuffer sb = new StringBuffer();
189
190
191      final FileSummary all = new FileSummary();
192      final Iterator<FileSummary> i = packages.values().iterator();
193
194      while (i.hasNext())
195      {
196         all.add(i.next());
197      }
198
199      sb.append("<findingsummary ");
200      fillSummaryLine(sb, all);
201      sb.append(">\n");
202
203      sb.append("   <packagelevelxml>\n");
204      for (Entry<String, FileSummary> entry : packages.entrySet())
205      {
206         final String pkg = entry.getKey();
207         final FileSummary summary = entry.getValue();
208         sb.append("      <package name='");
209         sb.append(pkg);
210         sb.append("' ");
211         fillSummaryLine(sb, summary);
212         sb.append("/>\n");
213      }
214      sb.append("   </packagelevelxml>\n");
215
216
217      final Map<String, FileSummary> serviceLevelMap
218          = generateServiceLevelMap(packages);
219
220
221      sb.append("   <servicelevelxml>\n");
222      for (Entry<String, FileSummary> service : serviceLevelMap.entrySet())
223      {
224         final String pkg = service.getKey();
225         final FileSummary summary = service.getValue();
226         sb.append("      <service name='");
227         sb.append(pkg);
228         sb.append("' ");
229         fillSummaryLine(sb, summary);
230         sb.append("/>\n");
231      }
232      sb.append("   </servicelevelxml>\n");
233
234      sb.append("</findingsummary>\n");
235
236      FileWriter w = null;
237      try
238      {
239         final java.io.File out = new java.io.File(mOutDir, "summary.xml");
240         w = new FileWriter(out);
241         w.write(sb.toString());
242      }
243      finally
244      {
245          IoUtil.close(w);
246      }
247   }
248
249   private Map<String, FileSummary> generateServiceLevelMap (
250       Map<String, FileSummary> packages)
251   {
252      final Map<String, FileSummary> serviceLevelMap
253          = new HashMap<String, FileSummary>();
254
255      for (Entry<String, FileSummary> packagz : packages.entrySet())
256      {
257         final String pkg = packagz.getKey();
258         final FileSummary summary = packagz.getValue();
259         final String service = getService(pkg);
260
261         FileSummary serviceSummary = serviceLevelMap.get(service);
262         if (serviceSummary == null)
263         {
264            serviceSummary = new FileSummary(service);
265            serviceLevelMap.put(service, serviceSummary);
266         }
267         serviceSummary.add(summary);
268      }
269      return serviceLevelMap;
270   }
271
272   private void fillSummaryLine (final StringBuffer sb, FileSummary summary)
273   {
274      sb.append("timestamp='");
275      sb.append(mTimestamp);
276      sb.append("' ");
277      for (int i = 0; i < Severity.VALUES.size(); i++)
278      {
279          final Severity currentSeverity = Severity.fromInt(i);
280          sb.append(currentSeverity.toString());
281          sb.append("='");
282          sb.append(summary.getViolations(currentSeverity));
283          sb.append("' ");
284      }
285      sb.append(" loc='");
286      sb.append(summary.getLinesOfCode());
287      sb.append("' codeLoc='");
288      sb.append(summary.getCoverage()
289          + summary.getViolations(Severity.COVERAGE));
290      sb.append("' quality='");
291      sb.append(summary.getQualityAsFloat());
292      sb.append('\'');
293   }
294
295   private String getService (String pkg)
296   {
297      String result;
298      if (pkg != null && pkg.startsWith(mPrefix))
299      {
300         result = pkg.substring(mPrefix.length());
301         if (result.indexOf('.') != -1)
302         {
303            result = result.substring(result.indexOf('.') + 1);
304         }
305         if (result.indexOf('.') != -1)
306         {
307            result = result.substring(0, result.indexOf('.'));
308         }
309      }
310      else
311      {
312         result = ObjectUtil.toStringOrEmpty(pkg);
313      }
314      return result;
315   }
316
317   /**
318    * Collects summary data and puts the results in the given maps.
319    * @param productionPackages map to collect production code data.
320    * @param testPackages map to collect test code data.
321    * @param all All packages.
322    */
323   private void summarize (final Map<String, FileSummary> productionPackages,
324         final Map<String, FileSummary> testPackages,
325         final Map<String, FileSummary> all)
326   {
327      final List<File> allFiles = mReport.getFile();
328      for (final File currentFile : allFiles)
329      {
330         // Level 3 is currently given to test classes, below 3 is production
331         if (currentFile.getLevel().equals(ReportLevel.TEST))
332         {
333            addToMap(testPackages, currentFile);
334            addToMap(all, currentFile);
335         }
336         else
337         {
338            addToMap(productionPackages, currentFile);
339            addToMap(all, currentFile);
340         }
341      }
342   }
343
344   private FileSummary addToMap (final Map<String, FileSummary> map,
345       final File file)
346   {
347      final String pkg = file.getPackage();
348      FileSummary counter = map.get(pkg);
349      if (counter == null)
350      {
351         counter = new FileSummary(pkg);
352         map.put(pkg, counter);
353      }
354      calculateSummary(file, counter);
355      return counter;
356   }
357
358   /**
359    * Collects finding summary for a concrete file.
360    * @param currentFile the file structure.
361    * @param counter the counter structure.
362    */
363   private void calculateSummary (File currentFile, FileSummary counter)
364   {
365       counter.add(currentFile);
366   }
367
368   /**
369    * Creates the lines of code chart.
370    * Locs are painted per package. Separated by test and production code.
371    * The output is a "loc.png".
372    *
373    * @throws IOException if the image can not be written.
374    */
375   private void createLocChart (Map<String, FileSummary> src,
376       Map<String, FileSummary> test, String level)
377         throws IOException
378   {
379     //<-- Begin Chart2D configuration -->
380
381     //Configure object properties
382     final Object2DProperties object2DProps = new Object2DProperties();
383     object2DProps.setObjectTitleText ("LOC by " + level);
384
385     //Configure chart properties
386     final Chart2DProperties chart2DProps = new Chart2DProperties();
387     chart2DProps.setChartDataLabelsPrecision (1);
388
389     //Configure legend properties
390     final LegendProperties legendProps = new LegendProperties();
391     final String[] legendLabels = {"Production", "Test"};
392     legendProps.setLegendLabelsTexts (legendLabels);
393
394     //Configure graph chart properties
395     final GraphChart2DProperties graphChart2DProps
396             = new GraphChart2DProperties();
397
398     final Set<String> labels = new TreeSet<String>(src.keySet());
399     labels.addAll(test.keySet());
400
401     if (labels.size() == 0)
402     {
403        throw new RuntimeException("No packages found for chart!");
404     }
405
406     final String [] labelsLongAxisLabels
407           = labels.toArray(new String[]{});
408     final String [] labelsAxisLabels
409           = cutPackages(labelsLongAxisLabels);
410
411     graphChart2DProps.setLabelsAxisLabelsTexts(labelsAxisLabels);
412     graphChart2DProps.setLabelsAxisTitleText(level + " Name");
413     graphChart2DProps.setNumbersAxisTitleText("LOC");
414     graphChart2DProps.setLabelsAxisTicksAlignment(
415           GraphChart2DProperties.CENTERED);
416
417     //Configure graph properties
418     final GraphProperties graphProps = new GraphProperties();
419     graphProps.setGraphBarsExistence(false);
420     graphProps.setGraphDotsExistence(true);
421     graphProps.setGraphAllowComponentAlignment(true);
422     graphProps.setGraphDotsWithinCategoryOverlapRatio(1);
423
424     //Configure dataset
425     final Dataset dataset = new Dataset (legendLabels.length,
426           labelsAxisLabels.length, 1);
427
428     // fill data....
429     for (int j = 0; j < dataset.getNumCats(); ++j)
430     {
431        dataset.set(0, j, 0, getCounter(src, labelsLongAxisLabels[j]));
432        dataset.set(1, j, 0, getCounter(test, labelsLongAxisLabels[j]));
433     }
434
435     //Configure graph component colors
436     final MultiColorsProperties multiColorsProps
437         = new MultiColorsProperties();
438
439     //Configure chart
440     final LBChart2D chart2D = new LBChart2D();
441     chart2D.setObject2DProperties (object2DProps);
442     chart2D.setChart2DProperties (chart2DProps);
443     chart2D.setLegendProperties (legendProps);
444     chart2D.setGraphChart2DProperties (graphChart2DProps);
445     chart2D.addGraphProperties (graphProps);
446     chart2D.addDataset (dataset);
447     chart2D.addMultiColorsProperties (multiColorsProps);
448
449     //<-- End Chart2D configuration -->
450
451
452     chart2D.setMaximumSize(LARGE_SIZE);
453     chart2D.setPreferredSize(LARGE_SIZE);
454
455     if (chart2D.validate(false))
456     {
457        java.io.File file = new java.io.File(mOutDir, "loc_large.png");
458        javax.imageio.ImageIO.write(chart2D.getImage(), "PNG", file);
459        chart2D.setMaximumSize(SMALL_SIZE);
460        chart2D.setPreferredSize(SMALL_SIZE);
461        chart2D.pack();
462        file = new java.io.File(mOutDir, "loc_small.png");
463        javax.imageio.ImageIO.write(chart2D.getImage(), "PNG", file);
464     }
465     else
466     {
467        chart2D.validate(true);
468     }
469   }
470
471   /**
472    * Creates the quality chart.
473    * The output is a "quality.png".
474    *
475    * @throws IOException if the image can not be written.
476    */
477   private void createQualityChart (Map<String, FileSummary> src, String level)
478         throws IOException
479   {
480     //<-- Begin Chart2D configuration -->
481
482     //Configure object properties
483     final Object2DProperties object2DProps = new Object2DProperties();
484     object2DProps.setObjectTitleText ("Quality by " + level);
485
486     //Configure chart properties
487     final Chart2DProperties chart2DProps = new Chart2DProperties();
488     chart2DProps.setChartDataLabelsPrecision(0);
489
490     //Configure legend properties
491     final LegendProperties legendProps = new LegendProperties();
492     final String[] legendLabels = {
493         "Error", "C&P", "Warning", "Design", "Code Style",
494         "Info", "Coverage"};
495     legendProps.setLegendLabelsTexts (legendLabels);
496
497     //Configure graph chart properties
498     final GraphChart2DProperties graphChart2DProps
499             = new GraphChart2DProperties();
500
501     final Set<String> labels = new TreeSet<String>(src.keySet());
502
503     if (labels.size() == 0)
504     {
505        throw new RuntimeException("No packages found for chart!");
506     }
507
508     final String [] labelsLongAxisLabels
509           = labels.toArray(new String[labels.size()]);
510     final String [] labelsAxisLabels = cutPackages(labelsLongAxisLabels);
511
512     graphChart2DProps.setLabelsAxisLabelsTexts(labelsAxisLabels);
513     graphChart2DProps.setLabelsAxisTitleText(level + " Name");
514     graphChart2DProps.setNumbersAxisTitleText("Finding/Loc %");
515     graphChart2DProps.setLabelsAxisTicksAlignment(
516           GraphChart2DProperties.CENTERED);
517
518     //Configure graph properties
519     final GraphProperties graphProps = new GraphProperties();
520     graphProps.setGraphBarsExistence(false);
521     graphProps.setGraphDotsExistence(true);
522     graphProps.setGraphAllowComponentAlignment(true);
523     graphProps.setGraphDotsWithinCategoryOverlapRatio(1);
524
525     //Configure dataset
526     final Dataset dataset = new Dataset (legendLabels.length,
527           labelsAxisLabels.length, 1);
528
529     // fill data....
530     for (int j = 0; j < dataset.getNumCats(); ++j)
531     {
532        final FileSummary sum = src.get(labelsLongAxisLabels[j]);
533        if (sum != null && sum.getLinesOfCode() != 0)
534        {
535           final float loc = sum.getLinesOfCode();
536           final float codeLoc = sum.getCoverage()
537               + sum.getViolations(Severity.COVERAGE);
538
539           dataset.set(ROW_ERROR, j, 0,
540               (PERCENT * sum.getViolations(Severity.ERROR)) / loc);
541           dataset.set(ROW_CPD, j, 0,
542               (PERCENT * sum.getViolations(Severity.CPD)) / loc);
543           dataset.set(ROW_WARNING, j, 0,
544               (PERCENT * sum.getViolations(Severity.WARNING)) / loc);
545           dataset.set(ROW_DESIGN, j, 0,
546               (PERCENT * sum.getViolations(Severity.DESIGN)) / loc);
547           dataset.set(ROW_CODE_STYLE, j, 0,
548               (PERCENT * sum.getViolations(Severity.CODE_STYLE)) / loc);
549           dataset.set(ROW_INFO, j, 0,
550               (PERCENT * sum.getViolations(Severity.INFO)) / loc);
551
552           if (codeLoc != 0)
553           {
554              dataset.set(ROW_COVERAGE, j, 0,
555                    (PERCENT * sum.getCoverage()) / codeLoc);
556           }
557           else
558           {
559              dataset.set(ROW_COVERAGE, j, 0, PERCENT);
560           }
561        }
562        else
563        {
564           dataset.set(ROW_ERROR, j, 0, 0);
565           dataset.set(ROW_CPD, j, 0, 0);
566           dataset.set(ROW_WARNING, j, 0, 0);
567           dataset.set(ROW_DESIGN, j, 0, 0);
568           dataset.set(ROW_CODE_STYLE, j, 0, 0);
569           dataset.set(ROW_INFO, j, 0, 0);
570           dataset.set(ROW_COVERAGE, j, 0, 0);
571        }
572     }
573
574     //Configure graph component colors
575     final MultiColorsProperties multiColorsProps
576             = new MultiColorsProperties();
577
578     multiColorsProps.setColorsCustomize(true);
579
580     multiColorsProps.setColorsCustom(new Color[]
581           {
582              Color.RED,
583              Color.BLUE,
584              Color.ORANGE,
585              Color.YELLOW,
586              Color.YELLOW,
587              Color.CYAN,
588              Color.MAGENTA
589           });
590
591     //Configure chart
592     final LBChart2D chart2D = new LBChart2D();
593     chart2D.setObject2DProperties (object2DProps);
594     chart2D.setChart2DProperties (chart2DProps);
595     chart2D.setLegendProperties (legendProps);
596     chart2D.setGraphChart2DProperties (graphChart2DProps);
597     chart2D.addGraphProperties (graphProps);
598     chart2D.addDataset (dataset);
599     chart2D.addMultiColorsProperties (multiColorsProps);
600
601     //<-- End Chart2D configuration -->
602
603     chart2D.setMaximumSize(LARGE_SIZE);
604     chart2D.setPreferredSize(LARGE_SIZE);
605
606     if (chart2D.validate(false))
607     {
608        java.io.File file = new java.io.File(mOutDir, "quality_large.png");
609        javax.imageio.ImageIO.write(chart2D.getImage(), "PNG", file);
610        chart2D.setMaximumSize(SMALL_SIZE);
611        chart2D.setPreferredSize(SMALL_SIZE);
612        chart2D.pack();
613        file = new java.io.File(mOutDir, "quality_small.png");
614        javax.imageio.ImageIO.write(chart2D.getImage(), "PNG", file);
615     }
616     else
617     {
618        chart2D.validate(true);
619     }
620   }
621
622   /**
623    * Gets the loc counter for a given package.
624    */
625   private float getCounter (Map<String, FileSummary> src,
626       String string)
627   {
628      final FileSummary counter = src.get(string);
629      final int result;
630
631      if (counter != null)
632      {
633         result = counter.getLinesOfCode();
634      }
635      else
636      {
637         result = 0;
638      }
639      return result;
640   }
641
642   /**
643    * Returns an array with the short for of the given package name.
644    * @param labelsAxisLabels an array with package names to be cut.
645    * @return a string array containing short form of the input package names.
646    */
647   private String[] cutPackages (String[] labelsAxisLabels)
648   {
649      final String [] result = new String[labelsAxisLabels.length];
650
651      for (int i = 0; i < labelsAxisLabels.length; i++)
652      {
653         if (labelsAxisLabels[i].startsWith(mPrefix))
654         {
655            result[i]
656                  = labelsAxisLabels[i].substring(mPrefix.length());
657            if (result[i].indexOf('.') != -1)
658            {
659               result[i] = result[i].substring(result[i].indexOf('.') + 1);
660            }
661         }
662         else
663         {
664            result[i] = labelsAxisLabels[i];
665         }
666      }
667
668      return result;
669   }
670
671}
Note: See TracBrowser for help on using the browser.