1 package net.sf.statsvn.input; 2 3 import java.util.Collection; 4 import java.util.HashMap; 5 import java.util.Iterator; 6 import java.util.Map; 7 8 import javax.xml.parsers.DocumentBuilder; 9 import javax.xml.parsers.DocumentBuilderFactory; 10 import javax.xml.parsers.ParserConfigurationException; 11 12 import net.sf.statcvs.output.ConfigurationOptions; 13 import net.sf.statsvn.output.SvnConfigurationOptions; 14 15 import org.w3c.dom.Document; 16 import org.w3c.dom.Element; 17 import org.w3c.dom.NodeList; 18 19 /** 20 * <p> 21 * CVS log files include lines modified for each commit and binary status of a 22 * file while SVN log files do not offer this additional information. 23 * </p> 24 * 25 * <p> 26 * StatSVN must query the Subversion repository for line counts using svn diff. 27 * However, this is very costly, performance-wise. Therefore, the decision was 28 * taken to persist this information in an XML file. This class receives 29 * information from (@link net.sf.statsvn.input.SvnXmlLineCountsFileHandler) to 30 * build a DOM-based xml structure. It also forwards line counts to the 31 * appropriate (@link net.sf.statsvn.input.FileBuilder). 32 * </p> 33 * 34 * @author Gunter Mussbacher <gunterm@site.uottawa.ca> 35 * @version $Id: CacheBuilder.java 351 2008-03-28 18:46:26Z benoitx $ 36 */ 37 public class CacheBuilder { 38 private final SvnLogBuilder builder; 39 40 private final RepositoryFileManager repositoryFileManager; 41 42 private Element currentPath = null; 43 44 private Document document = null; 45 46 private String currentFilename; 47 48 private Element cache = null; 49 50 /** 51 * Constructs the LineCountsBuilder by giving it a reference to the builder 52 * currently in use. 53 * 54 * @param builder 55 * the SvnLogBuilder which contains all the FileBuilders. 56 */ CacheBuilder(final SvnLogBuilder builder, final RepositoryFileManager repositoryFileManager)57 public CacheBuilder(final SvnLogBuilder builder, final RepositoryFileManager repositoryFileManager) { 58 this.builder = builder; 59 this.repositoryFileManager = repositoryFileManager; 60 } 61 62 /** 63 * Adds a path in the DOM. To be followed by invocations to (@link 64 * #addRevision(String, String, String)) 65 * 66 * @param name 67 * the filename 68 * @param latestRevision 69 * the latest revision of the file for which the binary status is 70 * known 71 * @param binaryStatus 72 * binary status of latest revision 73 */ addDOMPath(final String name, final String latestRevision, final String binaryStatus)74 private void addDOMPath(final String name, final String latestRevision, final String binaryStatus) { 75 currentPath = document.createElement(CacheConfiguration.PATH); 76 currentPath.setAttribute(CacheConfiguration.NAME, name); 77 currentPath.setAttribute(CacheConfiguration.LATEST_REVISION, latestRevision); 78 currentPath.setAttribute(CacheConfiguration.BINARY_STATUS, binaryStatus); 79 cache.appendChild(currentPath); 80 } 81 82 /** 83 * Updates the BINARY_STATUS and LATEST_REVISION attributes of a path in the 84 * DOM. Updates only if the revisionNumber is higher than current 85 * LATEST_REVISION of the path. 86 * 87 * @param path 88 * the path to be updated 89 * @param isBinary 90 * indicates if the revision is binary or not 91 * @param revisionNumber 92 * the revision number for which the binary status is valid 93 */ updateDOMPath(final Element path, final boolean isBinary, final String revisionNumber)94 private void updateDOMPath(final Element path, final boolean isBinary, final String revisionNumber) { 95 int oldRevision = 0; 96 int newRevision = -1; 97 try { 98 oldRevision = Integer.parseInt(path.getAttribute(CacheConfiguration.LATEST_REVISION)); 99 newRevision = Integer.parseInt(revisionNumber); 100 } catch (final NumberFormatException e) { 101 SvnConfigurationOptions.getTaskLogger().log( 102 "Ignoring invalid revision number " + revisionNumber + " for " + path.getAttribute(CacheConfiguration.NAME)); 103 newRevision = -1; 104 } 105 String binaryStatus = CacheConfiguration.NOT_BINARY; 106 if (isBinary) { 107 binaryStatus = CacheConfiguration.BINARY; 108 } 109 if (newRevision >= oldRevision) { 110 path.setAttribute(CacheConfiguration.LATEST_REVISION, revisionNumber); 111 path.setAttribute(CacheConfiguration.BINARY_STATUS, binaryStatus); 112 } 113 } 114 115 /** 116 * Finds a path in the DOM. 117 * 118 * @param name 119 * the filename 120 * @return the path or null if the path does not exist 121 */ findDOMPath(final String name)122 private Element findDOMPath(final String name) { 123 if (currentPath != null && name.equals(currentPath.getAttribute(CacheConfiguration.NAME))) { 124 return currentPath; 125 } 126 final NodeList paths = cache.getChildNodes(); 127 for (int i = 0; i < paths.getLength(); i++) { 128 final Element path = (Element) paths.item(i); 129 if (name.equals(path.getAttribute(CacheConfiguration.NAME))) { 130 return path; 131 } 132 } 133 return null; 134 } 135 136 /** 137 * Adds a revision to the current path in the DOM. To be preceeded by (@link 138 * #addPath(String)) 139 * 140 * @param number 141 * the revision number 142 * @param added 143 * the number of lines that were added 144 * @param removed 145 * the number of lines that were removed 146 */ addDOMRevision(final String number, final String added, final String removed, final String binaryStatus)147 private void addDOMRevision(final String number, final String added, final String removed, final String binaryStatus) { 148 final Element revision = document.createElement(CacheConfiguration.REVISION); 149 revision.setAttribute(CacheConfiguration.NUMBER, number); 150 revision.setAttribute(CacheConfiguration.ADDED, added); 151 revision.setAttribute(CacheConfiguration.REMOVED, removed); 152 revision.setAttribute(CacheConfiguration.BINARY_STATUS, binaryStatus); 153 currentPath.appendChild(revision); 154 } 155 156 /** 157 * Initializes the builder for subsequent invocations of (@link 158 * #buildRevision(String, String, String)). 159 * 160 * @param name 161 * the filename 162 */ buildPath(final String name, final String revision, final String binaryStatus)163 public void buildPath(final String name, final String revision, final String binaryStatus) { 164 currentFilename = repositoryFileManager.absoluteToRelativePath(name); 165 addDOMPath(name, revision, binaryStatus); 166 167 } 168 169 /** 170 * Given the file specified by the preceeding invocation to (@link 171 * #buildPath(String)), set the line counts for the given revision. 172 * 173 * If the path given in the preceeding invocation to (@link 174 * #buildPath(String)) is not used by the (@link SvnLogBuilder), this call 175 * does nothing. 176 * 177 * @param number 178 * the revision number 179 * @param added 180 * the number of lines added 181 * @param removed 182 * the number of lines removed. 183 */ buildRevision(final String number, final String added, final String removed, final String binaryStatus)184 public void buildRevision(final String number, final String added, final String removed, final String binaryStatus) { 185 if (!added.equals("-1") && !removed.equals("-1")) { 186 addDOMRevision(number, added, removed, binaryStatus); 187 builder.updateRevision(currentFilename, number, Integer.parseInt(added), Integer.parseInt(removed)); 188 } 189 } 190 191 /** 192 * Builds the DOM root. 193 * 194 * @throws ParserConfigurationException 195 */ buildRoot()196 public void buildRoot() throws ParserConfigurationException { 197 final DocumentBuilderFactory factoryDOM = DocumentBuilderFactory.newInstance(); 198 DocumentBuilder builderDOM; 199 builderDOM = factoryDOM.newDocumentBuilder(); 200 document = builderDOM.newDocument(); 201 cache = document.createElement(CacheConfiguration.CACHE); 202 cache.setAttribute(CacheConfiguration.PROJECT, ConfigurationOptions.getProjectName()); 203 cache.setAttribute(CacheConfiguration.XML_VERSION, "1.0"); 204 document.appendChild(cache); 205 } 206 207 /** 208 * Returns the DOM object when building is complete. 209 * 210 * @return the DOM document. 211 */ getDocument()212 public Document getDocument() { 213 return document; 214 } 215 216 /** 217 * Adds a revision to the DOM. 218 * 219 * Encapsulates calls to (@link #buildRoot()), (@link #buildPath(String)), 220 * and (@link #buildRevision(String, String, String)) into one easy to use 221 * interface. 222 * 223 * 224 * @param name 225 * the filename 226 * @param number 227 * the revision number 228 * @param added 229 * the number of lines added 230 * @param removed 231 * the number of lines removed 232 */ newRevision(String name, final String number, final String added, final String removed, final boolean binaryStatus)233 public synchronized void newRevision(String name, final String number, final String added, final String removed, final boolean binaryStatus) { 234 name = repositoryFileManager.relativeToAbsolutePath(name); 235 checkDocument(); 236 if (document != null) { 237 currentPath = findDOMPath(name); 238 if (currentPath == null) { 239 // changes currentPath to new one 240 addDOMPath(name, "0", CacheConfiguration.UNKNOWN); 241 } 242 String sBinaryStatus = CacheConfiguration.NOT_BINARY; 243 if (binaryStatus) { 244 sBinaryStatus = CacheConfiguration.BINARY; 245 } 246 addDOMRevision(number, added, removed, sBinaryStatus); 247 } 248 } 249 checkDocument()250 private void checkDocument() { 251 if (document == null) { 252 try { 253 buildRoot(); 254 } catch (final ParserConfigurationException e) { 255 document = null; 256 } 257 } 258 } 259 260 /** 261 * Updates all paths in the DOM structure with the latest binary status 262 * information from the working folder. 263 * 264 * @param name 265 * the filename 266 * @param number 267 * the revision number 268 * @param added 269 * the number of lines added 270 * @param removed 271 * the number of lines removed 272 */ updateBinaryStatus(final Collection fileBuilders, final String revisionNumber)273 public void updateBinaryStatus(final Collection fileBuilders, final String revisionNumber) { 274 // change data structure to a more appropriate one for lookup 275 final Map mFileBuilders = new HashMap(); 276 for (final Iterator iter = fileBuilders.iterator(); iter.hasNext();) { 277 final FileBuilder fileBuilder = (FileBuilder) iter.next(); 278 mFileBuilders.put(fileBuilder.getName(), fileBuilder); 279 } 280 if (!mFileBuilders.isEmpty()) { 281 // go through all the paths in the DOM and update their binary 282 // status 283 // remove the fileBuilder once its corresponding path in the DOM was 284 // dealt with 285 checkDocument(); 286 final NodeList paths = cache.getChildNodes(); 287 for (int i = 0; i < paths.getLength(); i++) { 288 final Element path = (Element) paths.item(i); 289 if (mFileBuilders.containsKey(repositoryFileManager.absoluteToRelativePath(path.getAttribute(CacheConfiguration.NAME)))) { 290 final FileBuilder fileBuilder = (FileBuilder) mFileBuilders.get(repositoryFileManager.absoluteToRelativePath(path 291 .getAttribute(CacheConfiguration.NAME))); 292 updateDOMPath(path, fileBuilder.isBinary(), revisionNumber); 293 mFileBuilders.remove(repositoryFileManager.absoluteToRelativePath(path.getAttribute(CacheConfiguration.NAME))); 294 } 295 } 296 // go through remaining fileBuilders and add them to the DOM 297 final Collection cFileBuilders = mFileBuilders.values(); 298 for (final Iterator iter = cFileBuilders.iterator(); iter.hasNext();) { 299 final FileBuilder fileBuilder = (FileBuilder) iter.next(); 300 String binaryStatus = CacheConfiguration.NOT_BINARY; 301 if (fileBuilder.isBinary()) { 302 binaryStatus = CacheConfiguration.BINARY; 303 } 304 addDOMPath(repositoryFileManager.relativeToAbsolutePath(fileBuilder.getName()), revisionNumber, binaryStatus); 305 } 306 } 307 308 } 309 310 /** 311 * Checks the path's cached binary status. 312 * 313 * @param fileName 314 * the path to be checked 315 * @param revisionNumber 316 * the revision of the path to be checked 317 * @return true if the path's BINARY_STATUS is true and the revisionNumber 318 * is lower or equal to the path's LATEST_REVISION 319 */ isBinary(final String fileName, final String revisionNumber)320 public synchronized boolean isBinary(final String fileName, final String revisionNumber) { 321 int latestRevision = 0; 322 int revisionToCheck = -1; 323 checkDocument(); 324 final Element path = findDOMPath(repositoryFileManager.relativeToAbsolutePath(fileName)); 325 if (path == null) { 326 return false; 327 } 328 try { 329 latestRevision = Integer.parseInt(path.getAttribute(CacheConfiguration.LATEST_REVISION)); 330 revisionToCheck = Integer.parseInt(revisionNumber); 331 } catch (final NumberFormatException e) { 332 SvnConfigurationOptions.getTaskLogger().log( 333 "Ignoring invalid revision number " + revisionNumber + " for " + path.getAttribute(CacheConfiguration.NAME)); 334 revisionToCheck = -1; 335 } 336 if (latestRevision >= revisionToCheck) { 337 final String binaryStatus = path.getAttribute(CacheConfiguration.BINARY_STATUS); 338 if (binaryStatus.equals(CacheConfiguration.BINARY)) { 339 return true; 340 } 341 } 342 return false; 343 } 344 } 345