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 394 2009-08-10 20:08:46Z jkealey $
19 */
20 public class SvnDiffUtils implements ISvnDiffProcessor {
21 public static final int RESULT_SIZE = 3;
22
23 protected static final int PROPERTY_NAME_LINE = 4;
24
25 protected static final String PROPERTY_CHANGE = "Property changes on:";
26
27 protected static final String PROPERTY_NAME = "Name:";
28
29 protected static final String BINARY_TYPE = "Cannot display: file marked as a binary type.";
30
31 protected static final String INDEX_MARKER = "Index: ";
32
33 protected ISvnProcessor processor;
34
35 /**
36 * Invokes diffs using the svn diff command line.
37 */
38 public SvnDiffUtils(ISvnProcessor processor) {
39 this.processor = processor;
40 }
41
42 protected ISvnProcessor getProcessor() {
43 return processor;
44 }
45
46 /**
47 * Calls svn diff for the filename and revisions given. Will use URL
48 * invocation, to ensure that we get diffs even for deleted files.
49 *
50 * @param oldRevNr
51 * old revision number
52 * @param newRevNr
53 * new revision number
54 * @param filename
55 * filename.
56 * @return the InputStream related to the call. If the error steam is
57 * non-empty, will return the error stream instead of the default
58 * input stream.
59 */
60 protected synchronized ProcessUtils callSvnDiff(final String oldRevNr, final String newRevNr, String filename) throws IOException {
61 String svnDiffCommand = null;
62 filename = getProcessor().getInfoProcessor().relativePathToUrl(filename);
63 filename = StringUtils.replace(" ", "%20", filename);
64 svnDiffCommand = "svn diff --old " + filename + "@" + oldRevNr + " --new " + filename + "@" + newRevNr + "" + SvnCommandHelper.getAuthString();
65 SvnConfigurationOptions.getTaskLogger().log(Thread.currentThread().getName() + " FIRING command line:\n[" + svnDiffCommand + "]");
66 return ProcessUtils.call(svnDiffCommand);
67 }
68
69 /**
70 * Calls svn diff on all files for given revision and revision-1.
71 *
72 * @param newRevNr
73 * revision number
74 * @return the InputStream related to the call. If the error steam is
75 * non-empty, will return the error stream instead of the default
76 * input stream.
77 */
78 protected synchronized ProcessUtils callSvnDiff(final String newRevNr) throws IOException {
79 String svnDiffCommand = null;
80 svnDiffCommand = "svn diff -c " + newRevNr + " " + getProcessor().getInfoProcessor().getRootUrl() + " " + SvnCommandHelper.getAuthString();
81 SvnConfigurationOptions.getTaskLogger().log(Thread.currentThread().getName() + " FIRING command line:\n[" + svnDiffCommand + "]");
82 return ProcessUtils.call(svnDiffCommand);
83 }
84
85
86
87
88 public int[] getLineDiff(final String oldRevNr, final String newRevNr, final String filename) throws IOException, BinaryDiffException {
89 int[] lineDiff;
90 ProcessUtils pUtils = null;
91 try {
92 pUtils = callSvnDiff(oldRevNr, newRevNr, filename);
93 final InputStream diffStream = pUtils.getInputStream();
94
95 lineDiff = parseSingleDiffStream(diffStream);
96
97 verifyOutput(pUtils);
98 } finally {
99 if (pUtils != null) {
100 pUtils.close();
101 }
102 }
103
104 return lineDiff;
105 }
106
107 protected int[] parseSingleDiffStream(final InputStream diffStream) throws IOException, BinaryDiffException {
108 int[] lineDiff;
109 final LookaheadReader diffReader = new LookaheadReader(new InputStreamReader(diffStream));
110 lineDiff = parseDiff(diffReader);
111 return lineDiff;
112 }
113
114 /**
115 * Verifies the process error stream.
116 * @param pUtils the process call
117 * @throws IOException problem parsing the stream
118 * @throws BinaryDiffException if the error message is due to trying to diff binary files.
119 */
120 protected void verifyOutput(final ProcessUtils pUtils) throws IOException, BinaryDiffException {
121 if (pUtils.hasErrorOccured()) {
122
123
124 final String msg = pUtils.getErrorMessage();
125 if (isBinaryErrorMessage(msg)) {
126 throw new BinaryDiffException();
127 } else {
128 throw new IOException(msg);
129 }
130 }
131 }
132
133
134
135
136 public Vector getLineDiff(final String newRevNr) throws IOException, BinaryDiffException {
137 final Vector answer = new Vector();
138
139 ProcessUtils pUtils = null;
140 try {
141 pUtils = callSvnDiff(newRevNr);
142 final InputStream diffStream = pUtils.getInputStream();
143 parseMultipleDiffStream(answer, diffStream);
144
145 verifyOutput(pUtils);
146 } finally {
147 if (pUtils != null) {
148 pUtils.close();
149 }
150 }
151
152 return answer;
153 }
154
155 protected void parseMultipleDiffStream(final Vector answer, final InputStream diffStream) throws IOException {
156 final LookaheadReader diffReader = new LookaheadReader(new InputStreamReader(diffStream));
157 String currFile = null;
158 StringBuffer sb = new StringBuffer();
159 while (diffReader.hasNextLine()) {
160 final String currLine = diffReader.nextLine();
161
162 if (currFile == null && currLine.startsWith(INDEX_MARKER)) {
163 currFile = currLine.substring(INDEX_MARKER.length());
164 } else if (currFile != null && currLine.startsWith(INDEX_MARKER)) {
165 appendResults(answer, currFile, sb);
166 sb = new StringBuffer();
167 currFile = currLine.substring(INDEX_MARKER.length());
168 }
169
170 sb.append(currLine);
171 sb.append(System.getProperty("line.separator"));
172 }
173
174
175 if (currFile!=null) {
176 appendResults(answer, currFile, sb);
177 }
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 protected 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[RESULT_SIZE];
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 protected 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 protected 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 }