1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 package net.sf.statcvs.output;
22
23 import java.util.Calendar;
24 import java.util.Date;
25 import java.util.HashMap;
26 import java.util.Iterator;
27 import java.util.List;
28 import java.util.Map;
29 import java.util.SortedSet;
30 import java.util.Map.Entry;
31
32 import net.sf.statcvs.Messages;
33 import net.sf.statcvs.charts.ChartImage;
34 import net.sf.statcvs.charts.SymbolicNameAnnotation;
35 import net.sf.statcvs.model.Revision;
36 import net.sf.statcvs.pages.NavigationNode;
37 import net.sf.statcvs.pages.Page;
38 import net.sf.statcvs.reports.LOCSeriesBuilder;
39
40 import org.jfree.data.time.Day;
41 import org.jfree.data.time.TimeSeries;
42
43 /**
44 * A LOC and Churn Chart shows both the LOC and the number of lines touched per
45 * day, this allows you to see the evolution of lines of code and the amount of
46 * changes. A flat LOC with a lot of Churn implies a lot of refactoring, an
47 * increase in LOC in line with churn implies new functionality.
48 *
49 * @author Benoit Xhenseval (www.ObjectLab.co.uk)
50 */
51 public class ChurnPageMaker {
52 private final ReportConfig config;
53
54 /**
55 * @see net.sf.statcvs.output.HTMLPage#HTMLPage(Repository)
56 */
57 public ChurnPageMaker(final ReportConfig config) {
58 this.config = config;
59 }
60
61 public NavigationNode toFile() {
62 final Page page = this.config.createPage("churn", Messages.getString("CHURN_TITLE"), Messages.getString("CHURN_TITLE"));
63 page.addRawContent("\n\n<!-- The LOC and Churn Report was designed by Benoit Xhenseval (http://www.objectlab.co.uk/open)-->");
64 page.addRawContent("\n<!-- Initially part of StatSVN -->\n\n");
65 page.addRawContent("<p>" + Messages.getString("CHURN_DESCRIPTION") + "</p>");
66 page.add(buildChart());
67 return page;
68 }
69
70 private ChartImage buildChart() {
71 final Map changePerRevision = new HashMap();
72 final SortedSet revisions = config.getRepository().getRevisions();
73 for (final Iterator it = revisions.iterator(); it.hasNext();) {
74 final Revision rev = (Revision) it.next();
75 final Date dateToUse = blastTime(rev.getDate());
76 final Integer changes = (Integer) changePerRevision.get(dateToUse);
77 if (changes == null) {
78 changePerRevision.put(dateToUse, new Integer(Math.abs(getLineChanges(rev))));
79 } else {
80 changePerRevision.put(dateToUse, new Integer(Math.abs(changes.intValue()) + getLineChanges(rev)));
81 }
82 }
83
84 final List annotations = SymbolicNameAnnotation.createAnnotations(config.getRepository().getSymbolicNames());
85 final TimeSeries timeLine = new TimeSeries(Messages.getString("CHURN_TOUCHED_LINE"), Day.class);
86
87 for (final Iterator it = changePerRevision.entrySet().iterator(); it.hasNext();) {
88 final Map.Entry entry = (Entry) it.next();
89
90
91 timeLine.add(new Day((Date) entry.getKey()), ((Integer) entry.getValue()).intValue());
92 }
93
94 final TimeSeries locSeries = getLOCTimeSeries(revisions, Messages.getString("TIME_LOC_SUBTITLE"));
95
96 final LOCChurnChartMaker chart = new LOCChurnChartMaker("locandchurn", config, timeLine, locSeries, Messages.getString("LOC_CHURN_CHART_TITLE"),
97 "locandchurn.png", annotations);
98 return chart.toFile();
99 }
100
101 private Date blastTime(final Date date) {
102 final Calendar cal = Calendar.getInstance();
103 cal.setTime(date);
104 cal.set(Calendar.MILLISECOND, 0);
105 cal.set(Calendar.HOUR_OF_DAY, 0);
106 cal.set(Calendar.MINUTE, 0);
107 cal.set(Calendar.SECOND, 0);
108 return cal.getTime();
109 }
110
111 private int getLineChanges(final Revision rev) {
112 if (rev.isDead()) {
113 return rev.getLinesDelta();
114 }
115 return Math.abs(rev.getLinesDelta()) + 2 * rev.getReplacedLines();
116 }
117
118 private TimeSeries getLOCTimeSeries(final SortedSet revisions, final String title) {
119 final Iterator it = revisions.iterator();
120 final LOCSeriesBuilder locCounter = new LOCSeriesBuilder(title, true);
121 while (it.hasNext()) {
122 locCounter.addRevision((Revision) it.next());
123 }
124 return locCounter.getTimeSeries();
125 }
126 }