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 */ SvnXmlLogFileHandler(final SvnLogBuilder builder, final RepositoryFileManager repositoryFileManager)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 */ characters(final char[] ch, final int start, final int length)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 */ checkLastElement(final String last)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 */ endAuthor()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 */ endDate()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 */ endElement(final String uri, final String localName, final String qName)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 */ endLog()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 */ endLogEntry()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 */ endMsg()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 */ endPath()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 */ endPaths()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 */ fatalError(final String message)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 */ startAuthorDateMsg()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 */ startElement(final String uri, final String localName, final String qName, final Attributes attributes)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 */ startLog()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 */ startLogEntry(final Attributes attributes)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 */ startPath(final Attributes attributes)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 */ startPaths()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 */ warning(final String message)400 private void warning(final String message) throws SAXException { 401 SvnConfigurationOptions.getTaskLogger().info(message); 402 } 403 } 404