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;
146 if ("".equals(eName)) {
147 eName = qName;
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;
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
228
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;
305 if ("".equals(eName)) {
306 eName = qName;
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 }