View Javadoc

1   package net.sf.statcvs.charts;
2   
3   import java.awt.Color;
4   import java.awt.Dimension;
5   import java.awt.Paint;
6   import java.util.ArrayList;
7   import java.util.Collection;
8   import java.util.Collections;
9   import java.util.Comparator;
10  import java.util.Date;
11  import java.util.HashMap;
12  import java.util.Iterator;
13  import java.util.List;
14  import java.util.Map;
15  import java.util.SortedSet;
16  
17  import net.sf.statcvs.Messages;
18  import net.sf.statcvs.model.Author;
19  import net.sf.statcvs.model.Directory;
20  import net.sf.statcvs.model.Module;
21  import net.sf.statcvs.model.Repository;
22  import net.sf.statcvs.model.Revision;
23  import net.sf.statcvs.output.ReportConfig;
24  import net.sf.statcvs.pages.HTML;
25  import net.sf.statcvs.reports.LOCSeriesBuilder;
26  import net.sf.statcvs.util.IntegerMap;
27  
28  import org.jfree.chart.ChartFactory;
29  import org.jfree.chart.JFreeChart;
30  import org.jfree.chart.annotations.XYAnnotation;
31  import org.jfree.chart.axis.DateAxis;
32  import org.jfree.chart.axis.ValueAxis;
33  import org.jfree.chart.plot.XYPlot;
34  import org.jfree.chart.renderer.xy.XYItemRenderer;
35  import org.jfree.chart.renderer.xy.XYStepRenderer;
36  import org.jfree.data.time.TimeSeries;
37  import org.jfree.data.time.TimeSeriesCollection;
38  
39  /**
40   * Produces Lines Of Code charts
41   * 
42   * TODO: At least the single-series charts should be done by TimeLineChartMakers
43   * 
44   * @author jentzsch
45   * @author Richard Cyganiak (richard@cyganiak.de)
46   * @version $Id: LOCChartMaker.java,v 1.19 2009/08/31 19:16:35 benoitx Exp $
47   */
48  public class LOCChartMaker {
49      private final ReportConfig config;
50      private ChartImage chartFile = null;
51      private final String chartName;
52  
53      /**
54       * Creates a Lines Of Code chart from a <tt>BasicTimeSeries</tt> and
55       * saves it as PNG
56       * @param locSeries the LOC history
57       * @param title the chart title
58       * @param fileName the filename where the chart will be saved
59       * @param size width and height of PNG in pixels
60       * @param annotations
61       */
62      public LOCChartMaker(final String chartName, final ReportConfig config, final TimeSeries locSeries, final String title, final String fileName,
63              final Dimension size, final List annotations) {
64          this.chartName = chartName;
65          this.config = config;
66          if (locSeries == null) {
67              return;
68          }
69          final Paint[] colors = new Paint[1];
70          colors[0] = Color.RED;
71  
72          final TimeSeriesCollection collection = new TimeSeriesCollection();
73          collection.addSeries(locSeries);
74          final JFreeChart chart = createLOCChart(collection, colors, title, annotations);
75          final Dimension dim = ChartConfigUtil.getDimension(chartName, size);
76          this.chartFile = this.config.createChartImage(fileName, title, chart, dim);
77      }
78  
79      /**
80       * Creates a Lines Of Code chart from a list of <tt>BasicTimesSeries</tt> and
81       * saves it as PNG
82       * @param locSeriesList a list of <tt>BasicTimesSeries</tt>
83       * @param title the chart title
84       * @param fileName the filename where the chart will be saved
85       * @param size width and height of PNG in pixels
86       */
87      public LOCChartMaker(final String chartName, final ReportConfig config, final List locSeriesList, final String title, final String fileName,
88              final Dimension size, final List annotations) {
89          this.chartName = chartName;
90          this.config = config;
91          if (locSeriesList.isEmpty()) {
92              return;
93          }
94          int i = 0;
95          final TimeSeriesCollection collection = new TimeSeriesCollection();
96          final Iterator it = locSeriesList.iterator();
97          while (it.hasNext()) {
98              final TimeSeries series = (TimeSeries) it.next();
99              collection.addSeries(series);
100             i++;
101         }
102         final JFreeChart chart = createLOCChart(collection, null, title, annotations);
103         final Dimension dim = ChartConfigUtil.getDimension(chartName, size);
104 
105         this.chartFile = this.config.createChartImage(fileName, title, chart, dim);
106     }
107 
108     private JFreeChart createLOCChart(final TimeSeriesCollection data, final Paint[] colors, final String title, final List annotations) {
109         final String domain = Messages.getString("TIME_LOC_DOMAIN");
110         final String range = Messages.getString("TIME_LOC_RANGE");
111 
112         final boolean legend = (data.getSeriesCount() > 1);
113         final JFreeChart chart = ChartFactory.createTimeSeriesChart(this.config.getProjectName() + ": " + title, domain, range, data, legend, false, false);
114 
115         final XYPlot plot = chart.getXYPlot();
116         plot.setRenderer(new XYStepRenderer());
117         if (colors == null) {
118             // We don't like the bright yellow color early on in the series, use a darker one
119             for (int i = 0; i < plot.getSeriesCount(); i++) {
120                 final Paint seriesPaint = plot.getRenderer().getSeriesPaint(i);
121                 if (seriesPaint != null && seriesPaint.equals(new Color(0xFF, 0xFF, 0x55))) {
122                     plot.getRenderer().setSeriesPaint(i, new Color(240, 220, 0x55));
123                 }
124             }
125         } else {
126             for (int i = 0; i < colors.length; i++) {
127                 plot.getRenderer().setSeriesPaint(i, colors[i]);
128             }
129         }
130         final DateAxis domainAxis = (DateAxis) plot.getDomainAxis();
131         domainAxis.setVerticalTickLabels(true);
132         final ValueAxis valueAxis = plot.getRangeAxis();
133         valueAxis.setLowerBound(0);
134 
135         if (annotations != null) {
136             for (final Iterator it = annotations.iterator(); it.hasNext();) {
137                 plot.addAnnotation((XYAnnotation) it.next());
138             }
139         }
140 
141         plot.setBackgroundPaint(ChartConfigUtil.getPlotColor(chartName));
142         chart.setBackgroundPaint(ChartConfigUtil.getBackgroundColor(chartName));
143         final XYItemRenderer renderer = plot.getRenderer();
144         ChartConfigUtil.configureStroke(chartName, renderer, data);
145         ChartConfigUtil.configureShapes(chartName, renderer);
146         ChartConfigUtil.configureCopyrightNotice(chartName, chart);
147         ChartConfigUtil.configureChartBackgroungImage(chartName, chart);
148         ChartConfigUtil.configurePlotImage(chartName, chart);
149 
150         return chart;
151     }
152 
153     public ChartImage toFile() {
154         return this.chartFile;
155     }
156 
157     private static TimeSeries getLOCTimeSeries(final SortedSet revisions, final String title) {
158         final LOCSeriesBuilder locCounter = new LOCSeriesBuilder(title, true);
159         final Iterator it = revisions.iterator();
160         while (it.hasNext()) {
161             locCounter.addRevision((Revision) it.next());
162         }
163         if (locCounter.getMaximum() == 0) {
164             return null;
165         }
166         return locCounter.getTimeSeries();
167     }
168 
169     public static class MainLOCChartMaker extends LOCChartMaker {
170         public MainLOCChartMaker(final String chartName, final ReportConfig config, final String fileName, final Dimension size) {
171             super(chartName, config, getLOCTimeSeries(config.getRepository().getRevisions(), Messages.getString("TIME_LOC_SUBTITLE")), Messages
172                     .getString("TIME_LOC_SUBTITLE"), fileName, size, SymbolicNameAnnotation.createAnnotations(config.getRepository().getSymbolicNames()));
173         }
174     }
175 
176     public static class DirectoryLOCChartMaker extends LOCChartMaker {
177         private static String getTitle(final Directory directory) {
178             return directory.getPath() + (directory.getPath() != null && directory.getPath().length() > 1 ? " " : "") + Messages.getString("TIME_LOC_SUBTITLE");
179         }
180 
181         private static String getFilename(final Directory directory) {
182             return "loc_module" + HTML.escapeDirectoryName(directory.getPath()) + ".png";
183         }
184 
185         public DirectoryLOCChartMaker(final ReportConfig config, final Directory directory) {
186             super("loc_module", config, getLOCTimeSeries(directory.getRevisions(), getTitle(directory)), getTitle(directory), getFilename(directory), config
187                     .getLargeChartSize(), SymbolicNameAnnotation.createAnnotations(config.getRepository().getSymbolicNames()));
188         }
189     }
190 
191     public static class AllDevelopersLOCChartMaker extends LOCChartMaker {
192         private static List createAllDevelopersLOCSeries(final ReportConfig config) {
193             Iterator it = config.getRepository().getAuthors().iterator();
194             final Map authorSeriesMap = new HashMap();
195             while (it.hasNext()) {
196                 final Author author = (Author) it.next();
197                 if (!config.isDeveloper(author)) {
198                     continue;
199                 }
200                 authorSeriesMap.put(author, new LOCSeriesBuilder(author.getRealName(), false));
201             }
202             it = config.getRepository().getRevisions().iterator();
203             while (it.hasNext()) {
204                 final Revision rev = (Revision) it.next();
205                 if (rev.isBeginOfLog()) {
206                     continue;
207                 }
208                 final LOCSeriesBuilder builder = (LOCSeriesBuilder) authorSeriesMap.get(rev.getAuthor());
209                 if (builder != null) {
210                     builder.addRevision(rev);
211                 } // otherwise the revision was by a non-developer login
212             }
213             final List authors = new ArrayList(authorSeriesMap.keySet());
214             Collections.sort(authors);
215             final List result = new ArrayList();
216             it = authors.iterator();
217             while (it.hasNext()) {
218                 final Author author = (Author) it.next();
219                 final LOCSeriesBuilder builder = (LOCSeriesBuilder) authorSeriesMap.get(author);
220                 final TimeSeries series = builder.getTimeSeries();
221                 if (series != null) {
222                     result.add(series);
223                 }
224             }
225             return result;
226         }
227 
228         public AllDevelopersLOCChartMaker(final ReportConfig config, final Dimension size) {
229             super("loc_per_author", config, createAllDevelopersLOCSeries(config), Messages.getString("CONTRIBUTED_LOC_TITLE"), "loc_per_author.png", size,
230                     SymbolicNameAnnotation.createAnnotations(config.getRepository().getSymbolicNames()));
231         }
232     }
233 
234     public static class AllDirectoriesLOCChartMaker extends LOCChartMaker {
235         private static Collection getMajorDirectories(final Repository repository, final int max) {
236             if (repository.getFirstDate() == null || repository.getLastDate() == null || repository.getFirstDate().equals(repository.getLastDate())) {
237                 return Collections.EMPTY_LIST;
238             }
239             final IntegerMap importances = new IntegerMap();
240             final Iterator it = repository.getDirectories().iterator();
241             while (it.hasNext()) {
242                 final Directory directory = (Directory) it.next();
243                 importances.put(directory, getImportance(directory, repository.getFirstDate(), repository.getLastDate()));
244             }
245             final List result = new ArrayList(repository.getDirectories());
246             Collections.sort(result, new Comparator() {
247                 public int compare(final Object o1, final Object o2) {
248                     final int importance1 = importances.get(o1);
249                     final int importance2 = importances.get(o2);
250                     if (importance1 > importance2) {
251                         return -1;
252                     }
253                     if (importance1 == importance2) {
254                         return 0;
255                     }
256                     return 1;
257                 }
258             });
259             return firstN(result, max);
260         }
261 
262         private static int getImportance(final Directory dir, final Date start, final Date end) {
263             final long timeRange = end.getTime() - start.getTime();
264             double maxImportance = 0;
265             int currentLines = 0;
266             final Iterator it = dir.getRevisions().iterator();
267             while (it.hasNext()) {
268                 final Revision revision = (Revision) it.next();
269                 currentLines += revision.getLinesDelta();
270                 final long timeInRange = revision.getDate().getTime() - start.getTime();
271                 final double timeFraction = (timeInRange / (double) timeRange) * 0.9 + 0.1;
272                 maxImportance = Math.max(maxImportance, (currentLines) * (timeFraction));
273             }
274             return (int) (maxImportance * 10);
275         }
276 
277         private static List firstN(final List list, final int n) {
278             return list.subList(0, Math.min(list.size(), n));
279         }
280 
281         private static List createAllDirectoriesLOCSeries(final Repository repository, final int max) {
282             Iterator it = getMajorDirectories(repository, max).iterator();
283             final Map directorySeriesMap = new HashMap();
284             while (it.hasNext()) {
285                 final Directory directory = (Directory) it.next();
286                 directorySeriesMap.put(directory, new LOCSeriesBuilder(directory.getPath(), true));
287             }
288             it = repository.getRevisions().iterator();
289             while (it.hasNext()) {
290                 final Revision rev = (Revision) it.next();
291                 if (rev.isBeginOfLog()) {
292                     continue;
293                 }
294                 final LOCSeriesBuilder builder = (LOCSeriesBuilder) directorySeriesMap.get(rev.getFile().getDirectory());
295                 if (builder == null) {
296                     continue; // minor directory
297                 }
298                 builder.addRevision(rev);
299             }
300             final List directories = new ArrayList(directorySeriesMap.keySet());
301             Collections.sort(directories);
302             final List result = new ArrayList();
303             it = directories.iterator();
304             while (it.hasNext()) {
305                 final Directory directory = (Directory) it.next();
306                 final LOCSeriesBuilder builder = (LOCSeriesBuilder) directorySeriesMap.get(directory);
307                 final TimeSeries series = builder.getTimeSeries();
308                 if (series != null) {
309                     result.add(series);
310                 }
311             }
312             return result;
313         }
314 
315         public AllDirectoriesLOCChartMaker(final ReportConfig config, final int showMaxDirectories) {
316             super("directories_loc_timeline", config, createAllDirectoriesLOCSeries(config.getRepository(), showMaxDirectories), Messages
317                     .getString("DIRECTORY_LOC_TITLE"), "directories_loc_timeline.png", config.getLargeChartSize(), SymbolicNameAnnotation
318                     .createAnnotations(config.getRepository().getSymbolicNames()));
319         }
320     }
321 
322 
323     public static class AllModulesLOCChartMaker extends LOCChartMaker {
324         private static Collection getMajorModules(final Repository repository, final int max) {
325             if (repository.getFirstDate() == null || repository.getLastDate() == null || repository.getFirstDate().equals(repository.getLastDate())) {
326                 return Collections.EMPTY_LIST;
327             }
328             final IntegerMap importances = new IntegerMap();
329             final Iterator it = repository.getModules().values().iterator();
330             while (it.hasNext()) {
331                 final Module directory = (Module) it.next();
332                 importances.put(directory, getImportance(directory, repository.getFirstDate(), repository.getLastDate()));
333             }
334             final List result = new ArrayList(repository.getModules().values());
335             Collections.sort(result, new Comparator() {
336                 public int compare(final Object o1, final Object o2) {
337                     final int importance1 = importances.get(o1);
338                     final int importance2 = importances.get(o2);
339                     if (importance1 > importance2) {
340                         return -1;
341                     }
342                     if (importance1 == importance2) {
343                         return 0;
344                     }
345                     return 1;
346                 }
347             });
348             return firstN(result, max);
349         }
350 
351         private static int getImportance(final Module dir, final Date start, final Date end) {
352             final long timeRange = end.getTime() - start.getTime();
353             double maxImportance = 0;
354             int currentLines = 0;
355             final Iterator it = dir.getRevisions().iterator();
356             while (it.hasNext()) {
357                 final Revision revision = (Revision) it.next();
358                 currentLines += revision.getLinesDelta();
359                 final long timeInRange = revision.getDate().getTime() - start.getTime();
360                 final double timeFraction = (timeInRange / (double) timeRange) * 0.9 + 0.1;
361                 maxImportance = Math.max(maxImportance, (currentLines) * (timeFraction));
362             }
363             return (int) (maxImportance * 10);
364         }
365 
366         private static List firstN(final List list, final int n) {
367             return list.subList(0, Math.min(list.size(), n));
368         }
369 
370         private static List createAllModulesLOCSeries(final Repository repository, final int max) {
371             Iterator it = getMajorModules(repository, max).iterator();
372             final Map directorySeriesMap = new HashMap();
373             while (it.hasNext()) {
374                 final Module module = (Module) it.next();
375                 directorySeriesMap.put(module, new LOCSeriesBuilder(module.getName(), true));
376             }
377             it = repository.getRevisions().iterator();
378             while (it.hasNext()) {
379                 final Revision rev = (Revision) it.next();
380                 if (rev.isBeginOfLog()) {
381                     continue;
382                 }
383                 final LOCSeriesBuilder builder = (LOCSeriesBuilder) directorySeriesMap.get(rev.getFile().getModule());
384                 if (builder == null) {
385                     continue; // minor directory
386                 }
387                 builder.addRevision(rev);
388             }
389             final List modules = new ArrayList(directorySeriesMap.keySet());
390             Collections.sort(modules);
391             final List result = new ArrayList();
392             it = modules.iterator();
393             while (it.hasNext()) {
394                 final Module module = (Module) it.next();
395                 final LOCSeriesBuilder builder = (LOCSeriesBuilder) directorySeriesMap.get(module);
396                 final TimeSeries series = builder.getTimeSeries();
397                 if (series != null) {
398                     result.add(series);
399                 }
400             }
401             return result;
402         }
403 
404         public AllModulesLOCChartMaker(final ReportConfig config, final int showMaxModules) {
405             super("modules_loc_timeline", config, createAllModulesLOCSeries(config.getRepository(), showMaxModules), Messages
406                     .getString("MODULES_LOC_TITLE"), "modules_loc_timeline.png", config.getLargeChartSize(), SymbolicNameAnnotation
407                     .createAnnotations(config.getRepository().getSymbolicNames()));
408         }
409     }
410 }