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