View Javadoc

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  	/* (non-Javadoc)
86       * @see net.sf.statsvn.util.ISvnDiffProcessor#getLineDiff(java.lang.String, java.lang.String, java.lang.String)
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 			// The binary checking code here might be useless... as it may
123 			// be output on the standard out.
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 	/* (non-Javadoc)
134      * @see net.sf.statsvn.util.ISvnDiffProcessor#getLineDiff(java.lang.String)
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         // last file
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 		 * Index: junit.jar
221 		 * ===================================================================
222 		 * Cannot display: file marked as a binary type.
223 		 * 
224 		 * svn:mime-type = application/octet-stream
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 			// diff has no output because we modified properties or the changes
244 			// are auto-generated ($id$ $author$ kind of thing)
245 			// http://svnbook.red-bean.com/nightly/en/svn.advanced.props.html#svn.advanced.props.special.keywords
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 			// very simple algorithm
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 }