View Javadoc

1   package net.sf.statsvn.input;
2   
3   import java.text.ParseException;
4   import java.util.ArrayList;
5   import java.util.Date;
6   import java.util.HashMap;
7   
8   import net.sf.statsvn.output.SvnConfigurationOptions;
9   import net.sf.statsvn.util.XMLUtil;
10  
11  import org.xml.sax.Attributes;
12  import org.xml.sax.SAXException;
13  import org.xml.sax.SAXParseException;
14  import org.xml.sax.helpers.DefaultHandler;
15  
16  /**
17   * This is the SAX parser for the svn log in xml format. It feeds information to
18   * the (@link net.sf.statsvn.input.SvnLogBuilder).
19   * 
20   * @author Jason Kealey <jkealey@shade.ca>
21   * @author Gunter Mussbacher <gunterm@site.uottawa.ca>
22   * 
23   * @version $Id: SvnXmlLogFileHandler.java 351 2008-03-28 18:46:26Z benoitx $
24   */
25  public class SvnXmlLogFileHandler extends DefaultHandler {
26  
27  	private static final String INVALID_SVN_LOG_FILE = "Invalid SVN log file.";
28  
29  	private static final String AUTHOR = "author";
30  
31  	private static final String DATE = "date";
32  
33  	private static final String FATAL_ERROR_MESSAGE = INVALID_SVN_LOG_FILE;
34  
35  	private static final String LOG = "log";
36  
37  	private static final String LOGENTRY = "logentry";
38  
39  	private static final String MSG = "msg";
40  
41  	private static final String PATH = "path";
42  
43  	private static final String PATHS = "paths";
44  
45  	private final SvnLogBuilder builder;
46  
47  	private ArrayList currentFilenames;
48  
49  	private RevisionData currentRevisionData;
50  
51  	private ArrayList currentRevisions;
52  
53  	private String lastElement = "";
54  
55  	private String pathAction = "";
56  
57  	private String stringData = "";
58  
59  	private String copyfromRev = "";
60  
61  	private String copyfromPath = "";
62  
63  	private final RepositoryFileManager repositoryFileManager;
64  
65  	private final HashMap tagsMap = new HashMap();
66  
67  	private final HashMap tagsDateMap = new HashMap();
68  
69  	/**
70  	 * Default constructor.
71  	 * 
72  	 * @param builder
73  	 *            where to send the information
74  	 * @param repositoryFileManager
75  	 *            the repository file manager needed to obtain some information.
76  	 */
77  	public SvnXmlLogFileHandler(final SvnLogBuilder builder, final RepositoryFileManager repositoryFileManager) {
78  		this.builder = builder;
79  		this.repositoryFileManager = repositoryFileManager;
80  	}
81  
82  	/**
83  	 * Builds the string that was read; default implementation can invoke this
84  	 * function multiple times while reading the data.
85  	 */
86  	public void characters(final char[] ch, final int start, final int length) throws SAXException {
87  		super.characters(ch, start, length);
88  		stringData += new String(ch, start, length);
89  	}
90  
91  	/**
92  	 * Makes sure the last element received is appropriate.
93  	 * 
94  	 * @param last
95  	 *            the expected last element.
96  	 * @throws SAXException
97  	 *             unexpected event.
98  	 */
99  	private void checkLastElement(final String last) throws SAXException {
100 		if (!lastElement.equals(last)) {
101 			fatalError(FATAL_ERROR_MESSAGE);
102 		}
103 	}
104 
105 	/**
106 	 * End of author element. Saves author to the current revision.
107 	 * 
108 	 * @throws SAXException
109 	 *             unexpected event.
110 	 */
111 	private void endAuthor() throws SAXException {
112 		checkLastElement(LOGENTRY);
113 		currentRevisionData.setLoginName(stringData);
114 	}
115 
116 	/**
117 	 * End of date element. See (@link XMLUtil#parseXsdDateTime(String)) for
118 	 * parsing of the particular datetime format.
119 	 * 
120 	 * Saves date to the current revision.
121 	 * 
122 	 * @throws SAXException
123 	 *             unexpected event.
124 	 */
125 	private void endDate() throws SAXException {
126 		checkLastElement(LOGENTRY);
127 		Date dt;
128 		try {
129 			dt = XMLUtil.parseXsdDateTime(stringData);
130 			currentRevisionData.setDate(dt);
131 		} catch (final ParseException e) {
132 			warning("Invalid date specified.");
133 		}
134 	}
135 
136 	/**
137 	 * Handles the end of an xml element and redirects to the appropriate end*
138 	 * method.
139 	 * 
140 	 * @throws SAXException
141 	 *             unexpected event.
142 	 */
143 	public void endElement(final String uri, final String localName, final String qName) throws SAXException {
144 		super.endElement(uri, localName, qName);
145 		String eName = localName; // element name
146 		if ("".equals(eName)) {
147 			eName = qName; // namespaceAware = false
148 		}
149 		if (eName.equals(LOG)) {
150 			endLog();
151 		} else if (eName.equals(LOGENTRY)) {
152 			endLogEntry();
153 		} else if (eName.equals(AUTHOR)) {
154 			endAuthor();
155 		} else if (eName.equals(DATE)) {
156 			endDate();
157 		} else if (eName.equals(MSG)) {
158 			endMsg();
159 		} else if (eName.equals(PATHS)) {
160 			endPaths();
161 		} else if (eName.equals(PATH)) {
162 			endPath();
163 		} else {
164 			fatalError(INVALID_SVN_LOG_FILE);
165 		}
166 	}
167 
168 	/**
169 	 * End of log element.
170 	 * 
171 	 * @throws SAXException
172 	 *             unexpected event.
173 	 */
174 	private void endLog() throws SAXException {
175 		checkLastElement(LOG);
176 		lastElement = "";
177 	}
178 
179 	/**
180 	 * End of log entry element. For each file that was found, builds the file
181 	 * and revision in (@link SvnLogBuilder).
182 	 * 
183 	 * @throws SAXException
184 	 *             unexpected event.
185 	 */
186 	private void endLogEntry() throws SAXException {
187 		checkLastElement(LOGENTRY);
188 		lastElement = LOG;
189 
190 		for (int i = 0; i < currentFilenames.size(); i++) {
191 			if (currentFilenames.get(i) == null) {
192 				continue; // skip files that are not on this branch
193 			}
194 			final RevisionData revisionData = (RevisionData) currentRevisions.get(i);
195 			revisionData.setComment(currentRevisionData.getComment());
196 			revisionData.setDate(currentRevisionData.getDate());
197 			revisionData.setLoginName(currentRevisionData.getLoginName());
198 			final String currentFilename = currentFilenames.get(i).toString();
199 
200 			final boolean isBinary = repositoryFileManager.isBinary(currentFilename);
201 			builder.buildFile(currentFilename, isBinary, revisionData.isDeletion(), tagsMap, tagsDateMap);
202 			builder.buildRevision(revisionData);
203 		}
204 	}
205 
206 	/**
207 	 * End of msg element. Saves comment to the current revision.
208 	 * 
209 	 * @throws SAXException
210 	 *             unexpected event.
211 	 */
212 	private void endMsg() throws SAXException {
213 		checkLastElement(LOGENTRY);
214 		currentRevisionData.setComment(stringData);
215 	}
216 
217 	/**
218 	 * End of path element. Builds a revision data for this element using the
219 	 * information that is known to date; rest is done in (@link #endLogEntry())
220 	 * 
221 	 * @throws SAXException
222 	 *             unexpected event.
223 	 */
224 	private void endPath() throws SAXException {
225 		checkLastElement(PATHS);
226 
227 		// relies on the fact that absoluteToRelativePath returns null for paths
228 		// that are not on the branch.
229 		final String filename = repositoryFileManager.absoluteToRelativePath(stringData);
230 		final RevisionData data = currentRevisionData.createCopy();
231 		if (!pathAction.equals("D")) {
232 			data.setStateExp(true);
233 			if (pathAction.equals("A") || pathAction.equals("R")) {
234 				data.setStateAdded(true);
235 			}
236 		} else {
237 			data.setStateDead(true);
238 		}
239 
240 		final String tagsStr = SvnConfigurationOptions.getTagsDirectory();
241 		if (copyfromRev != null && filename == null && stringData != null && stringData.indexOf(tagsStr) >= 0) {
242 			String tag = stringData.substring(stringData.indexOf(tagsStr) + tagsStr.length());
243 			if (tag.indexOf("/") >= 0) {
244 				tag = tag.substring(0, tag.indexOf("/"));
245 			}
246 
247 			if (!tagsMap.containsKey(tag) && builder.matchesTagPatterns(tag)) {
248 				SvnConfigurationOptions.getTaskLogger().info("= TAG " + tag + " rev:" + copyfromRev + " stringData [" + stringData + "]");
249 				tagsMap.put(tag, copyfromRev);
250 				tagsDateMap.put(tag, currentRevisionData.getDate());
251 			}
252 		}
253 
254 		data.setCopyfromPath(copyfromPath);
255 		data.setCopyfromRevision(copyfromRev);
256 
257 		currentRevisions.add(data);
258 		currentFilenames.add(filename);
259 	}
260 
261 	/**
262 	 * End of paths element.
263 	 * 
264 	 * @throws SAXException
265 	 *             unexpected event.
266 	 */
267 	private void endPaths() throws SAXException {
268 		checkLastElement(PATHS);
269 		lastElement = LOGENTRY;
270 	}
271 
272 	/**
273 	 * Throws a fatal error with the specified message.
274 	 * 
275 	 * @param message
276 	 *            the reason for the error
277 	 * @throws SAXException
278 	 *             the error
279 	 */
280 	private void fatalError(final String message) throws SAXException {
281 		fatalError(new SAXParseException(message, null));
282 	}
283 
284 	/**
285 	 * Start of author, date or message.
286 	 * 
287 	 * @throws SAXException
288 	 *             unexpected event.
289 	 */
290 	private void startAuthorDateMsg() throws SAXException {
291 		checkLastElement(LOGENTRY);
292 	}
293 
294 	/**
295 	 * Handles the start of an xml element and redirects to the appropriate
296 	 * start* method.
297 	 * 
298 	 * @throws SAXException
299 	 *             unexpected event.
300 	 */
301 	public void startElement(final String uri, final String localName, final String qName, final Attributes attributes) throws SAXException {
302 		super.startElement(uri, localName, qName, attributes);
303 		stringData = "";
304 		String eName = localName; // element name
305 		if ("".equals(eName)) {
306 			eName = qName; // namespaceAware = false
307 		}
308 		if (eName.equals(LOG)) {
309 			startLog();
310 		} else if (eName.equals(LOGENTRY)) {
311 			startLogEntry(attributes);
312 		} else if (eName.equals(AUTHOR) || eName.equals(DATE) || eName.equals(MSG)) {
313 			startAuthorDateMsg();
314 		} else if (eName.equals(PATHS)) {
315 			startPaths();
316 		} else if (eName.equals(PATH)) {
317 			startPath(attributes);
318 		} else {
319 			fatalError(INVALID_SVN_LOG_FILE);
320 		}
321 	}
322 
323 	/**
324 	 * Start of the log element.
325 	 * 
326 	 * @throws SAXException
327 	 *             unexpected event.
328 	 */
329 	private void startLog() throws SAXException {
330 		checkLastElement("");
331 		lastElement = LOG;
332 
333 		try {
334 			repositoryFileManager.loadInfo();
335 			builder.buildModule(repositoryFileManager.getModuleName());
336 		} catch (final Exception e) {
337 			throw new SAXException(e);
338 		}
339 
340 	}
341 
342 	/**
343 	 * Start of the log entry element. Initializes information, to be filled
344 	 * during this log entry and used in (@link #endLogEntry())
345 	 * 
346 	 * @throws SAXException
347 	 *             unexpected event.
348 	 */
349 	private void startLogEntry(final Attributes attributes) throws SAXException {
350 		checkLastElement(LOG);
351 		lastElement = LOGENTRY;
352 		currentRevisionData = new RevisionData();
353 		currentRevisions = new ArrayList();
354 		currentFilenames = new ArrayList();
355 		if (attributes != null && attributes.getValue("revision") != null) {
356 			currentRevisionData.setRevisionNumber(attributes.getValue("revision"));
357 		} else {
358 			fatalError(INVALID_SVN_LOG_FILE);
359 		}
360 	}
361 
362 	/**
363 	 * Start of the path element. Saves the path action.
364 	 * 
365 	 * @throws SAXException
366 	 *             unexpected event.
367 	 */
368 	private void startPath(final Attributes attributes) throws SAXException {
369 		checkLastElement(PATHS);
370 		if (attributes != null && attributes.getValue("action") != null) {
371 			pathAction = attributes.getValue("action");
372 		} else {
373 			fatalError(INVALID_SVN_LOG_FILE);
374 		}
375 
376 		copyfromPath = attributes.getValue("copyfrom-path");
377 		copyfromRev = attributes.getValue("copyfrom-rev");
378 
379 	}
380 
381 	/**
382 	 * Start of the paths element.
383 	 * 
384 	 * @throws SAXException
385 	 *             unexpected event.
386 	 */
387 	private void startPaths() throws SAXException {
388 		checkLastElement(LOGENTRY);
389 		lastElement = PATHS;
390 	}
391 
392 	/**
393 	 * Logs a warning.
394 	 * 
395 	 * @param message
396 	 *            the reason for the error
397 	 * @throws SAXException
398 	 *             the error
399 	 */
400 	private void warning(final String message) throws SAXException {
401 		SvnConfigurationOptions.getTaskLogger().info(message);
402 	}
403 }