View Javadoc

1   /*
2    StatSVN - SVN Subversion statistics generation
3    Copyright (C) 2006 Benoit Xhenseval
4    http://www.statsvn.org
5   
6    This library is free software; you can redistribute it and/or
7    modify it under the terms of the GNU Lesser General Public
8    License as published by the Free Software Foundation; either
9    version 2.1 of the License, or (at your option) any later version.
10  
11   This library is distributed in the hope that it will be useful,
12   but WITHOUT ANY WARRANTY; without even the implied warranty of
13   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14   Lesser General Public License for more details.
15  
16   You should have received a copy of the GNU Lesser General Public
17   License along with this library; if not, write to the Free Software
18   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
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 			// out.append("<!DOCTYPE root SYSTEM \"TreeMap.dtd\" >\n");
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 		// take all deltas for the last 30 days.
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 }