1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 package net.sf.statsvn.output;
22
23 import java.io.BufferedWriter;
24 import java.io.File;
25 import java.io.FileWriter;
26 import java.io.IOException;
27 import java.io.InputStream;
28 import java.io.Writer;
29 import java.util.Calendar;
30 import java.util.Date;
31 import java.util.Iterator;
32 import java.util.SortedSet;
33
34 import net.sf.statcvs.Messages;
35 import net.sf.statcvs.model.Directory;
36 import net.sf.statcvs.model.Revision;
37 import net.sf.statcvs.model.VersionedFile;
38 import net.sf.statcvs.output.ConfigurationOptions;
39 import net.sf.statcvs.output.ReportConfig;
40 import net.sf.statcvs.pages.HTML;
41 import net.sf.statcvs.pages.NavigationNode;
42 import net.sf.statcvs.pages.Page;
43 import net.sf.statcvs.util.FileUtils;
44
45 /**
46 * New report that Repo Map, a jtreemap-based report (applet) that shows the
47 * entire source tree in a hierarchical manner, the size of each box is related
48 * to LOC and the colour to the changes over the last 30 days (red -loc, green
49 * +loc).
50 *
51 * @author Benoit Xhenseval (www.objectlab.co.uk)
52 * @see http://jtreemap.sourceforge.net for more about JTreeMap.
53 */
54 public class RepoMapPageMaker {
55 private static final int DAYS_FROM_LAST_DATE = 30;
56
57 private static final String WEB_FILE_PATH = "web-files/";
58
59 private static final String REPO_FILE = "repomap-data.txt";
60
61 private final Date deadline;
62
63 private final Date currentDate;
64
65 private final ReportConfig config;
66
67 private int indent = 0;
68
69 /**
70 * @see net.sf.statcvs.output.HTMLPage#HTMLPage(Repository)
71 */
72 public RepoMapPageMaker(final ReportConfig config) {
73 final Calendar cal = Calendar.getInstance();
74 if (config !=null && config.getRepository() != null && config.getRepository().getLastDate()!=null) {
75 cal.setTime(config.getRepository().getLastDate());
76 }
77 currentDate = cal.getTime();
78 cal.add(Calendar.DATE, -DAYS_FROM_LAST_DATE);
79 deadline = cal.getTime();
80 this.config = config;
81 }
82
83 public NavigationNode toFile() {
84 final Page page = this.config.createPage("repomap", Messages.getString("REPOMAP_TITLE"), Messages.getString("REPOMAP_TITLE"));
85 page.addRawAttribute(Messages.getString("REPOMAP_START_DATE"), HTML.getDate(deadline));
86 page.addRawAttribute(Messages.getString("REPOMAP_END_DATE"), HTML.getDate(currentDate));
87
88 page.addRawContent("<p>" + Messages.getString("REPOMAP_DESCRIPTION") + "</p>");
89 page.addRawContent("<p>" + getApplet() + "</p>");
90 page.addRawContent("<p><small>This page uses <a href=\"http://jtreemap.sourceforge.net\">JTreeMap</a>.</small></p>");
91 buildXmlForJTreeMap();
92
93 return page;
94 }
95
96 private String getApplet() {
97 return "<applet archive=\"./" + Messages.getString("JTREEMAP_JAR") + "\" code=\"net.sf.jtreemap.swing.example.JTreeMapAppletExample\""
98 + " width=\"940\" height=\"600\"><param name=\"dataFile\" value=\"" + REPO_FILE + "\"/>" + "<param name=\"viewTree\" value=\"true\"/>"
99 + "<param name=\"showWeight\" value=\"true\"/>" + "<param name=\"valuePrefix\" value=\"Change:\"/>"
100 + "<param name=\"weightPrefix\" value=\"LOC:\"/>" + "<param name=\"dataFileType\" value=\"xml\"/>"
101 + "<param name=\"colorProvider\" value=\"HSBLog\"/>" + "</applet>";
102 }
103
104 private void buildXmlForJTreeMap() {
105 BufferedWriter out = null;
106 try {
107 copyJar(Messages.getString("JTREEMAP_JAR"));
108 out = new BufferedWriter(new FileWriter(ConfigurationOptions.getOutputDir() + REPO_FILE));
109 out.write("<?xml version='1.0' encoding='ISO-8859-1'?>\n");
110
111 out.write("<root>\n");
112 final Iterator it = config.getRepository().getDirectories().iterator();
113 if (it.hasNext()) {
114 final Directory dir = (Directory) it.next();
115 doDirectory(out, dir);
116 }
117 out.write("</root>");
118 } catch (final IOException e) {
119 e.printStackTrace();
120 } finally {
121 if (out != null) {
122 try {
123 out.close();
124 } catch (final IOException e) {
125 SvnConfigurationOptions.getTaskLogger().error(e.toString());
126 }
127 }
128 }
129 }
130
131 private void copyJar(final String jtreemapJar) throws IOException {
132 InputStream stream = null;
133 try {
134 stream = RepoMapPageMaker.class.getResourceAsStream(WEB_FILE_PATH + jtreemapJar);
135 if (stream != null) {
136 FileUtils.copyFile(stream, new File(ConfigurationOptions.getOutputDir() + jtreemapJar));
137 } else {
138 throw new IOException("The stream to " + (WEB_FILE_PATH + jtreemapJar) + " failed, is it copied in the jar?");
139 }
140 } finally {
141 if (stream != null) {
142 stream.close();
143 }
144 }
145 }
146
147 private void addSpaces(final int count, final BufferedWriter out) throws IOException {
148 out.write(getSpaces(count));
149 }
150
151 private String getSpaces(final int count) {
152 final StringBuffer result = new StringBuffer();
153 for (int i = 0; i < count; i++) {
154 result.append(" ");
155 }
156 return result.toString();
157 }
158
159 private void doDirectory(final BufferedWriter out, final Directory dir) throws IOException {
160 indent++;
161 SvnConfigurationOptions.getTaskLogger().log("Directory:" + getSpaces(indent) + dir.getName());
162
163 if (dir.isEmpty()) {
164 indent--;
165 return;
166 }
167
168 final SortedSet set = dir.getSubdirectories();
169 final SortedSet files = dir.getFiles();
170 final String name = dir.isRoot() ? Messages.getString("NAVIGATION_ROOT") : dir.getName();
171 boolean addedBranch = false;
172 if (indent > 1 && set != null && !set.isEmpty()) {
173 out.write("\n");
174 addSpaces(indent, out);
175 out.write("<branch>\n");
176 addSpaces(indent + 2, out);
177 labelTag(out, name);
178 addedBranch = true;
179 } else if (indent == 1) {
180 addSpaces(indent, out);
181 labelTag(out, name);
182 }
183 if (set != null) {
184 for (final Iterator it2 = set.iterator(); it2.hasNext();) {
185 doDirectory(out, (Directory) it2.next());
186 }
187 }
188 addedBranch = handleEachFileInDir(out, files, name, addedBranch);
189 if (addedBranch) {
190 addSpaces(indent, out);
191 out.write("</branch>\n");
192 }
193 indent--;
194 }
195
196 private boolean handleEachFileInDir(final BufferedWriter out, final SortedSet files, final String name, boolean addedBranch) throws IOException {
197 if (files != null && !files.isEmpty()) {
198 for (final Iterator file = files.iterator(); file.hasNext();) {
199 final VersionedFile vfile = (VersionedFile) file.next();
200
201 int loc = vfile.getCurrentLinesOfCode();
202
203 SvnConfigurationOptions.getTaskLogger().log("File:" + vfile.getFilename() + " LOC:" + loc);
204
205 int delta = calculateTotalDelta(vfile);
206 if (loc == 0) {
207 loc = Math.abs(delta);
208 }
209 if (loc == 0) {
210 continue;
211 }
212 if (!addedBranch) {
213 out.write("\n");
214 addSpaces(indent, out);
215 out.write("<branch>\n");
216 addSpaces(indent + 2, out);
217 labelTag(out, name);
218 out.write("\n");
219 addedBranch = true;
220 }
221 addSpaces(indent + 2, out);
222 out.write("<leaf>");
223 labelTag(out, vfile.getFilename());
224 tag(out, "weight", String.valueOf(loc));
225 final double percentage = ((double) delta) / (double) loc * 100.0;
226 tag(out, "value", String.valueOf(percentage));
227 out.write("</leaf>\n");
228 SvnConfigurationOptions.getTaskLogger().log("===========>>> LOC=" + loc + " totalDelta=" + delta + " Delta%=" + percentage);
229 }
230 }
231 return addedBranch;
232 }
233
234 private int calculateTotalDelta(final VersionedFile vfile) {
235 int delta = 0;
236 final SortedSet revisions = vfile.getRevisions();
237
238 for (final Iterator rev = revisions.iterator(); rev.hasNext();) {
239 final Revision revision = (Revision) rev.next();
240
241 SvnConfigurationOptions.getTaskLogger().log(
242 "Revision " + revision.getDate() + " file:" + vfile.getFilename() + " Dead:" + vfile.isDead() + " LOC:" + revision.getLines() + " delta:"
243 + revision.getLinesDelta());
244
245 if (deadline.before(revision.getDate())) {
246 delta += revision.getLinesDelta();
247
248 SvnConfigurationOptions.getTaskLogger().log(
249 "Revision " + revision.getRevisionNumber() + " Delta:" + revision.getLinesDelta() + " totalDelta:" + delta + " LOC:"
250 + revision.getLines() + " Dead:" + revision.isDead());
251 }
252 }
253 return delta;
254 }
255
256 private void labelTag(final Writer result, final String name) throws IOException {
257 if (name == null || name.length() == 0) {
258 tag(result, "label", "[root]");
259 } else {
260 tag(result, "label", name);
261 }
262 }
263
264 private void tag(final Writer result, final String tagName, final String value) throws IOException {
265 result.write("<");
266 result.write(tagName);
267 result.write(">");
268 result.write(value);
269 result.write("</");
270 result.write(tagName);
271 result.write(">");
272 }
273 }