1 package net.sf.statsvn.util;
2
3 import java.io.IOException;
4 import java.io.InputStream;
5 import java.io.InputStreamReader;
6 import java.io.StringReader;
7 import java.util.Vector;
8
9 import net.sf.statcvs.util.LookaheadReader;
10 import net.sf.statsvn.output.SvnConfigurationOptions;
11
12 /**
13 * Utilities class that manages calls to svn diff.
14 *
15 * @author Jason Kealey <jkealey@shade.ca>
16 * @author Gunter Mussbacher <gunterm@site.uottawa.ca>
17 *
18 * @version $Id: SvnDiffUtils.java 352 2008-03-31 16:14:51Z benoitx $
19 */
20 public final class SvnDiffUtils {
21 private static final int PROPERTY_NAME_LINE = 4;
22
23 private static final String PROPERTY_CHANGE = "Property changes on:";
24
25 private static final String PROPERTY_NAME = "Name:";
26
27 private static final String BINARY_TYPE = "Cannot display: file marked as a binary type.";
28
29 private static final String INDEX_MARKER = "Index: ";
30
31 /**
32 * A utility class (only static methods) should be final and have a private
33 * constructor.
34 */
35 private SvnDiffUtils() {
36 }
37
38 /**
39 * Calls svn diff for the filename and revisions given. Will use URL
40 * invocation, to ensure that we get diffs even for deleted files.
41 *
42 * @param oldRevNr
43 * old revision number
44 * @param newRevNr
45 * new revision number
46 * @param filename
47 * filename.
48 * @return the InputStream related to the call. If the error steam is
49 * non-empty, will return the error stream instead of the default
50 * input stream.
51 */
52 private static synchronized ProcessUtils callSvnDiff(final String oldRevNr, final String newRevNr, String filename) throws IOException {
53 String svnDiffCommand = null;
54 filename = SvnInfoUtils.relativePathToUrl(filename);
55 filename = SvnInfoUtils.replace(" ", "%20", filename);
56 svnDiffCommand = "svn diff --old " + filename + "@" + oldRevNr + " --new " + filename + "@" + newRevNr + "" + SvnCommandHelper.getAuthString();
57 SvnConfigurationOptions.getTaskLogger().log(Thread.currentThread().getName() + " FIRING command line:\n[" + svnDiffCommand + "]");
58 return ProcessUtils.call(svnDiffCommand);
59 }
60
61 /**
62 * Calls svn diff on all files for given revision and revision-1.
63 *
64 * @param newRevNr
65 * revision number
66 * @return the InputStream related to the call. If the error steam is
67 * non-empty, will return the error stream instead of the default
68 * input stream.
69 */
70 private static synchronized ProcessUtils callSvnDiff(final String newRevNr) throws IOException {
71 String svnDiffCommand = null;
72 svnDiffCommand = "svn diff -c " + newRevNr + " " + SvnInfoUtils.getRootUrl() + " " + SvnCommandHelper.getAuthString();
73 SvnConfigurationOptions.getTaskLogger().log(Thread.currentThread().getName() + " FIRING command line:\n[" + svnDiffCommand + "]");
74 return ProcessUtils.call(svnDiffCommand);
75 }
76
77 /**
78 * Returns line count differences between two revisions of a file.
79 *
80 * @param oldRevNr
81 * old revision number
82 * @param newRevNr
83 * new revision number
84 * @param filename
85 * the filename
86 * @return A int[2] array of [lines added, lines removed] is returned.
87 * @throws IOException
88 * problem parsing the stream
89 * @throws BinaryDiffException
90 * if the error message is due to trying to diff binary files.
91 */
92 public static int[] getLineDiff(final String oldRevNr, final String newRevNr, final String filename) throws IOException, BinaryDiffException {
93 int[] lineDiff;
94 ProcessUtils pUtils = null;
95 try {
96 pUtils = callSvnDiff(oldRevNr, newRevNr, filename);
97 final InputStream diffStream = pUtils.getInputStream();
98
99 final LookaheadReader diffReader = new LookaheadReader(new InputStreamReader(diffStream));
100 lineDiff = parseDiff(diffReader);
101
102 verifyOutput(pUtils);
103 } finally {
104 if (pUtils != null) {
105 pUtils.close();
106 }
107 }
108
109 return lineDiff;
110 }
111
112 /**
113 * Verifies the process error stream.
114 * @param pUtils the process call
115 * @throws IOException problem parsing the stream
116 * @throws BinaryDiffException if the error message is due to trying to diff binary files.
117 */
118 private static void verifyOutput(final ProcessUtils pUtils) throws IOException, BinaryDiffException {
119 if (pUtils.hasErrorOccured()) {
120
121
122 final String msg = pUtils.getErrorMessage();
123 if (isBinaryErrorMessage(msg)) {
124 throw new BinaryDiffException();
125 } else {
126 throw new IOException(msg);
127 }
128 }
129 }
130
131 /**
132 * Returns line count differences for all files in a particular revision.
133 *
134 * @param newRevNr
135 * new revision number
136 * @return A vector of object[3] array of [filename, int[2](lines added, lines removed), isBinary] is returned.
137 * @throws IOException
138 * problem parsing the stream
139 * @throws BinaryDiffException
140 * if the error message is due to trying to diff binary files.
141 */
142 public static Vector getLineDiff(final String newRevNr) throws IOException, BinaryDiffException {
143 final Vector answer = new Vector();
144
145 ProcessUtils pUtils = null;
146 try {
147 pUtils = callSvnDiff(newRevNr);
148 final InputStream diffStream = pUtils.getInputStream();
149 final LookaheadReader diffReader = new LookaheadReader(new InputStreamReader(diffStream));
150 String currFile = null;
151 StringBuffer sb = new StringBuffer();
152 while (diffReader.hasNextLine()) {
153 final String currLine = diffReader.nextLine();
154
155 if (currFile == null && currLine.startsWith(INDEX_MARKER)) {
156 currFile = currLine.substring(INDEX_MARKER.length());
157 } else if (currFile != null && currLine.startsWith(INDEX_MARKER)) {
158 appendResults(answer, currFile, sb);
159 sb = new StringBuffer();
160 currFile = currLine.substring(INDEX_MARKER.length());
161 }
162
163 sb.append(currLine);
164 sb.append(System.getProperty("line.separator"));
165 }
166
167
168 appendResults(answer, currFile, sb);
169
170 verifyOutput(pUtils);
171 } finally {
172 if (pUtils != null) {
173 pUtils.close();
174 }
175 }
176
177 return answer;
178 }
179
180 /**
181 * Append results to answer vector.
182 * @param answer the current answers
183 * @param currFile the current file being added.
184 * @param sb the diff for this individual file.
185 * @throws IOException
186 * problem parsing the stream
187 * @throws BinaryDiffException
188 * if the error message is due to trying to diff binary files.
189 */
190 private static void appendResults(final Vector answer, final String currFile, final StringBuffer sb) throws IOException {
191 int[] lineDiff;
192 Boolean isBinary = Boolean.FALSE;
193
194 final LookaheadReader individualDiffReader = new LookaheadReader(new StringReader(sb.toString()));
195 try {
196 lineDiff = parseDiff(individualDiffReader);
197 } catch (final BinaryDiffException e) {
198 lineDiff = new int[2];
199 lineDiff[0] = 0;
200 lineDiff[1] = 0;
201 isBinary = Boolean.TRUE;
202
203 }
204 final Object[] results = new Object[3];
205 results[0] = currFile;
206 results[1] = lineDiff;
207 results[2] = isBinary;
208 answer.add(results);
209 }
210
211 /**
212 * Returns true if msg is an error message display that the file is binary.
213 *
214 * @param msg
215 * the error message given by ProcessUtils.getErrorMessage();
216 * @return true if the file is binary
217 */
218 private static boolean isBinaryErrorMessage(final String msg) {
219
220
221
222
223
224
225
226 return (msg.indexOf(BINARY_TYPE) >= 0);
227 }
228
229 /**
230 * Returns line count differences between two revisions of a file.
231 *
232 * @param diffReader
233 * the stream reader for svn diff
234 *
235 * @return A int[2] array of [lines added, lines removed] is returned.
236 * @throws IOException
237 * problem parsing the stream
238 */
239 private static int[] parseDiff(final LookaheadReader diffReader) throws IOException, BinaryDiffException {
240 final int[] lineDiff = { -1, -1 };
241 boolean propertyChange = false;
242 if (!diffReader.hasNextLine()) {
243
244
245
246 lineDiff[0] = 0;
247 lineDiff[1] = 0;
248 }
249 while (diffReader.hasNextLine()) {
250 diffReader.nextLine();
251 final String currentLine = diffReader.getCurrentLine();
252
253 SvnConfigurationOptions.getTaskLogger().log(Thread.currentThread().getName() + " Diff Line: [" + currentLine + "]");
254
255 if (currentLine.length() == 0) {
256 continue;
257 }
258 final char firstChar = currentLine.charAt(0);
259
260 if (firstChar == '+') {
261 lineDiff[0]++;
262 } else if (firstChar == '-') {
263 lineDiff[1]++;
264 } else if (currentLine.indexOf(PROPERTY_CHANGE) == 0
265 || (currentLine.indexOf(PROPERTY_NAME) == 0 && diffReader.getLineNumber() == PROPERTY_NAME_LINE)) {
266 propertyChange = true;
267 } else if (currentLine.indexOf(BINARY_TYPE) == 0) {
268 throw new BinaryDiffException();
269 }
270 }
271 if (propertyChange && (lineDiff[0] == -1 || lineDiff[1] == -1)) {
272 lineDiff[0] = 0;
273 lineDiff[1] = 0;
274 }
275
276 return lineDiff;
277 }
278
279 }