001: package net.sf.statsvn.input;
002:
003: import java.text.ParseException;
004: import java.util.ArrayList;
005: import java.util.Date;
006: import java.util.HashMap;
007:
008: import net.sf.statsvn.output.SvnConfigurationOptions;
009: import net.sf.statsvn.util.XMLUtil;
010:
011: import org.xml.sax.Attributes;
012: import org.xml.sax.SAXException;
013: import org.xml.sax.SAXParseException;
014: import org.xml.sax.helpers.DefaultHandler;
015:
016: /**
017: * This is the SAX parser for the svn log in xml format. It feeds information to
018: * the (@link net.sf.statsvn.input.SvnLogBuilder).
019: *
020: * @author Jason Kealey <jkealey@shade.ca>
021: * @author Gunter Mussbacher <gunterm@site.uottawa.ca>
022: *
023: * @version $Id: SvnXmlLogFileHandler.java 300 2007-03-11 20:56:18Z jpdaigle $
024: */
025: public class SvnXmlLogFileHandler extends DefaultHandler {
026:
027: private static final String INVALID_SVN_LOG_FILE = "Invalid SVN log file.";
028:
029: private static final String AUTHOR = "author";
030:
031: private static final String DATE = "date";
032:
033: private static final String FATAL_ERROR_MESSAGE = INVALID_SVN_LOG_FILE;
034:
035: private static final String LOG = "log";
036:
037: private static final String LOGENTRY = "logentry";
038:
039: private static final String MSG = "msg";
040:
041: private static final String PATH = "path";
042:
043: private static final String PATHS = "paths";
044:
045: private SvnLogBuilder builder;
046:
047: private ArrayList currentFilenames;
048:
049: private RevisionData currentRevisionData;
050:
051: private ArrayList currentRevisions;
052:
053: private String lastElement = "";
054:
055: private String pathAction = "";
056:
057: private String stringData = "";
058:
059: private String copyfromRev = "";
060:
061: private String copyfromPath = "";
062:
063: private RepositoryFileManager repositoryFileManager;
064:
065: private HashMap tagsMap = new HashMap();
066:
067: private HashMap tagsDateMap = new HashMap();
068:
069: /**
070: * Default constructor.
071: *
072: * @param builder
073: * where to send the information
074: * @param repositoryFileManager
075: * the repository file manager needed to obtain some information.
076: */
077: public SvnXmlLogFileHandler(final SvnLogBuilder builder,
078: final RepositoryFileManager repositoryFileManager) {
079: this .builder = builder;
080: this .repositoryFileManager = repositoryFileManager;
081: }
082:
083: /**
084: * Builds the string that was read; default implementation can invoke this
085: * function multiple times while reading the data.
086: */
087: public void characters(final char[] ch, final int start,
088: final int length) throws SAXException {
089: super .characters(ch, start, length);
090: stringData += new String(ch, start, length);
091: }
092:
093: /**
094: * Makes sure the last element received is appropriate.
095: *
096: * @param last
097: * the expected last element.
098: * @throws SAXException
099: * unexpected event.
100: */
101: private void checkLastElement(final String last)
102: throws SAXException {
103: if (!lastElement.equals(last)) {
104: fatalError(FATAL_ERROR_MESSAGE);
105: }
106: }
107:
108: /**
109: * End of author element. Saves author to the current revision.
110: *
111: * @throws SAXException
112: * unexpected event.
113: */
114: private void endAuthor() throws SAXException {
115: checkLastElement(LOGENTRY);
116: currentRevisionData.setLoginName(stringData);
117: }
118:
119: /**
120: * End of date element. See (@link XMLUtil#parseXsdDateTime(String)) for
121: * parsing of the particular datetime format.
122: *
123: * Saves date to the current revision.
124: *
125: * @throws SAXException
126: * unexpected event.
127: */
128: private void endDate() throws SAXException {
129: checkLastElement(LOGENTRY);
130: Date dt;
131: try {
132: dt = XMLUtil.parseXsdDateTime(stringData);
133: currentRevisionData.setDate(dt);
134: } catch (final ParseException e) {
135: warning("Invalid date specified.");
136: }
137: }
138:
139: /**
140: * Handles the end of an xml element and redirects to the appropriate end*
141: * method.
142: *
143: * @throws SAXException
144: * unexpected event.
145: */
146: public void endElement(final String uri, final String localName,
147: final String qName) throws SAXException {
148: super .endElement(uri, localName, qName);
149: String eName = localName; // element name
150: if ("".equals(eName)) {
151: eName = qName; // namespaceAware = false
152: }
153: if (eName.equals(LOG)) {
154: endLog();
155: } else if (eName.equals(LOGENTRY)) {
156: endLogEntry();
157: } else if (eName.equals(AUTHOR)) {
158: endAuthor();
159: } else if (eName.equals(DATE)) {
160: endDate();
161: } else if (eName.equals(MSG)) {
162: endMsg();
163: } else if (eName.equals(PATHS)) {
164: endPaths();
165: } else if (eName.equals(PATH)) {
166: endPath();
167: } else {
168: fatalError(INVALID_SVN_LOG_FILE);
169: }
170: }
171:
172: /**
173: * End of log element.
174: *
175: * @throws SAXException
176: * unexpected event.
177: */
178: private void endLog() throws SAXException {
179: checkLastElement(LOG);
180: lastElement = "";
181: }
182:
183: /**
184: * End of log entry element. For each file that was found, builds the file
185: * and revision in (@link SvnLogBuilder).
186: *
187: * @throws SAXException
188: * unexpected event.
189: */
190: private void endLogEntry() throws SAXException {
191: checkLastElement(LOGENTRY);
192: lastElement = LOG;
193:
194: for (int i = 0; i < currentFilenames.size(); i++) {
195: if (currentFilenames.get(i) == null) {
196: continue; // skip files that are not on this branch
197: }
198: final RevisionData revisionData = (RevisionData) currentRevisions
199: .get(i);
200: revisionData.setComment(currentRevisionData.getComment());
201: revisionData.setDate(currentRevisionData.getDate());
202: revisionData.setLoginName(currentRevisionData
203: .getLoginName());
204: final String currentFilename = currentFilenames.get(i)
205: .toString();
206:
207: final boolean isBinary = repositoryFileManager
208: .isBinary(currentFilename);
209: builder.buildFile(currentFilename, isBinary, revisionData
210: .isDeletion(), tagsMap, tagsDateMap);
211: builder.buildRevision(revisionData);
212: }
213: }
214:
215: /**
216: * End of msg element. Saves comment to the current revision.
217: *
218: * @throws SAXException
219: * unexpected event.
220: */
221: private void endMsg() throws SAXException {
222: checkLastElement(LOGENTRY);
223: currentRevisionData.setComment(stringData);
224: }
225:
226: /**
227: * End of path element. Builds a revision data for this element using the
228: * information that is known to date; rest is done in (@link #endLogEntry())
229: *
230: * @throws SAXException
231: * unexpected event.
232: */
233: private void endPath() throws SAXException {
234: checkLastElement(PATHS);
235:
236: // relies on the fact that absoluteToRelativePath returns null for paths
237: // that are not on the branch.
238: String filename = repositoryFileManager
239: .absoluteToRelativePath(stringData);
240: final RevisionData data = currentRevisionData.createCopy();
241: if (!pathAction.equals("D")) {
242: data.setStateExp(true);
243: if (pathAction.equals("A") || pathAction.equals("R")) {
244: data.setStateAdded(true);
245: }
246: } else {
247: data.setStateDead(true);
248: }
249:
250: final String tagsStr = "/tags/";
251: if (copyfromRev != null && filename == null
252: && stringData != null
253: && stringData.indexOf(tagsStr) >= 0) {
254: String tag = stringData.substring(stringData
255: .indexOf(tagsStr)
256: + tagsStr.length());
257: // SvnConfigurationOptions.getTaskLogger().info("= TAG " +
258: // stringData + " rev:" + copyfromRev + " ==> " + tag);
259: if (tag.indexOf("/") >= 0) {
260: tag = tag.substring(0, tag.indexOf("/"));
261: }
262:
263: if (!tagsMap.containsKey(tag)
264: && builder.matchesTagPatterns(tag)) {
265: SvnConfigurationOptions.getTaskLogger().info(
266: "= TAG " + tag + " rev:" + copyfromRev
267: + " stringData [" + stringData + "]");
268: tagsMap.put(tag, copyfromRev);
269: tagsDateMap.put(tag, currentRevisionData.getDate());
270: }
271: }
272:
273: data.setCopyfromPath(copyfromPath);
274: data.setCopyfromRevision(copyfromRev);
275:
276: currentRevisions.add(data);
277: currentFilenames.add(filename);
278: }
279:
280: /**
281: * End of paths element.
282: *
283: * @throws SAXException
284: * unexpected event.
285: */
286: private void endPaths() throws SAXException {
287: checkLastElement(PATHS);
288: lastElement = LOGENTRY;
289: }
290:
291: /**
292: * Throws a fatal error with the specified message.
293: *
294: * @param message
295: * the reason for the error
296: * @throws SAXException
297: * the error
298: */
299: private void fatalError(final String message) throws SAXException {
300: fatalError(new SAXParseException(message, null));
301: }
302:
303: /**
304: * Start of author, date or message.
305: *
306: * @throws SAXException
307: * unexpected event.
308: */
309: private void startAuthorDateMsg() throws SAXException {
310: checkLastElement(LOGENTRY);
311: }
312:
313: /**
314: * Handles the start of an xml element and redirects to the appropriate
315: * start* method.
316: *
317: * @throws SAXException
318: * unexpected event.
319: */
320: public void startElement(final String uri, final String localName,
321: final String qName, final Attributes attributes)
322: throws SAXException {
323: super .startElement(uri, localName, qName, attributes);
324: stringData = "";
325: String eName = localName; // element name
326: if ("".equals(eName)) {
327: eName = qName; // namespaceAware = false
328: }
329: if (eName.equals(LOG)) {
330: startLog();
331: } else if (eName.equals(LOGENTRY)) {
332: startLogEntry(attributes);
333: } else if (eName.equals(AUTHOR) || eName.equals(DATE)
334: || eName.equals(MSG)) {
335: startAuthorDateMsg();
336: } else if (eName.equals(PATHS)) {
337: startPaths();
338: } else if (eName.equals(PATH)) {
339: startPath(attributes);
340: } else {
341: fatalError(INVALID_SVN_LOG_FILE);
342: }
343: }
344:
345: /**
346: * Start of the log element.
347: *
348: * @throws SAXException
349: * unexpected event.
350: */
351: private void startLog() throws SAXException {
352: checkLastElement("");
353: lastElement = LOG;
354:
355: try {
356: repositoryFileManager.loadInfo();
357: builder.buildModule(repositoryFileManager.getModuleName());
358: } catch (final Exception e) {
359: throw new SAXException(e);
360: }
361:
362: }
363:
364: /**
365: * Start of the log entry element. Initializes information, to be filled
366: * during this log entry and used in (@link #endLogEntry())
367: *
368: * @throws SAXException
369: * unexpected event.
370: */
371: private void startLogEntry(final Attributes attributes)
372: throws SAXException {
373: checkLastElement(LOG);
374: lastElement = LOGENTRY;
375: currentRevisionData = new RevisionData();
376: currentRevisions = new ArrayList();
377: currentFilenames = new ArrayList();
378: if (attributes != null
379: && attributes.getValue("revision") != null) {
380: currentRevisionData.setRevisionNumber(attributes
381: .getValue("revision"));
382: } else {
383: fatalError(INVALID_SVN_LOG_FILE);
384: }
385: }
386:
387: /**
388: * Start of the path element. Saves the path action.
389: *
390: * @throws SAXException
391: * unexpected event.
392: */
393: private void startPath(final Attributes attributes)
394: throws SAXException {
395: checkLastElement(PATHS);
396: if (attributes != null && attributes.getValue("action") != null) {
397: pathAction = attributes.getValue("action");
398: } else {
399: fatalError(INVALID_SVN_LOG_FILE);
400: }
401:
402: copyfromPath = attributes.getValue("copyfrom-path");
403: copyfromRev = attributes.getValue("copyfrom-rev");
404:
405: }
406:
407: /**
408: * Start of the paths element.
409: *
410: * @throws SAXException
411: * unexpected event.
412: */
413: private void startPaths() throws SAXException {
414: checkLastElement(LOGENTRY);
415: lastElement = PATHS;
416: }
417:
418: /**
419: * Logs a warning.
420: *
421: * @param message
422: * the reason for the error
423: * @throws SAXException
424: * the error
425: */
426: private void warning(final String message) throws SAXException {
427: SvnConfigurationOptions.getTaskLogger().info(message);
428: }
429: }
|