Coverage Report - net.sf.statsvn.input.CacheBuilder
 
Classes in this File Line Coverage Branch Coverage Complexity
CacheBuilder
58%
67/116
31%
13/42
3
 
 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  30
         private Element currentPath = null;
 43  
 
 44  30
         private Document document = null;
 45  
 
 46  
         private String currentFilename;
 47  
 
 48  30
         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  
          */
 57  30
         public CacheBuilder(final SvnLogBuilder builder, final RepositoryFileManager repositoryFileManager) {
 58  30
                 this.builder = builder;
 59  30
                 this.repositoryFileManager = repositoryFileManager;
 60  30
         }
 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  
          */
 74  
         private void addDOMPath(final String name, final String latestRevision, final String binaryStatus) {
 75  31530
                 currentPath = document.createElement(CacheConfiguration.PATH);
 76  31530
                 currentPath.setAttribute(CacheConfiguration.NAME, name);
 77  31530
                 currentPath.setAttribute(CacheConfiguration.LATEST_REVISION, latestRevision);
 78  31530
                 currentPath.setAttribute(CacheConfiguration.BINARY_STATUS, binaryStatus);
 79  31530
                 cache.appendChild(currentPath);
 80  31530
         }
 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  
          */
 94  
         private void updateDOMPath(final Element path, final boolean isBinary, final String revisionNumber) {
 95  31530
                 int oldRevision = 0;
 96  31530
                 int newRevision = -1;
 97  
                 try {
 98  31530
                         oldRevision = Integer.parseInt(path.getAttribute(CacheConfiguration.LATEST_REVISION));
 99  31530
                         newRevision = Integer.parseInt(revisionNumber);
 100  0
                 } catch (final NumberFormatException e) {
 101  0
                         SvnConfigurationOptions.getTaskLogger().log(
 102  
                                 "Ignoring invalid revision number " + revisionNumber + " for " + path.getAttribute(CacheConfiguration.NAME));
 103  0
                         newRevision = -1;
 104  31530
                 }
 105  31530
                 String binaryStatus = CacheConfiguration.NOT_BINARY;
 106  31530
                 if (isBinary) {
 107  4380
                         binaryStatus = CacheConfiguration.BINARY;
 108  
                 }
 109  31530
                 if (newRevision >= oldRevision) {
 110  31530
                         path.setAttribute(CacheConfiguration.LATEST_REVISION, revisionNumber);
 111  31530
                         path.setAttribute(CacheConfiguration.BINARY_STATUS, binaryStatus);
 112  
                 }
 113  31530
         }
 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  
          */
 122  
         private Element findDOMPath(final String name) {
 123  0
                 if (currentPath != null && name.equals(currentPath.getAttribute(CacheConfiguration.NAME))) {
 124  0
                         return currentPath;
 125  
                 }
 126  0
                 final NodeList paths = cache.getChildNodes();
 127  0
                 for (int i = 0; i < paths.getLength(); i++) {
 128  0
                         final Element path = (Element) paths.item(i);
 129  0
                         if (name.equals(path.getAttribute(CacheConfiguration.NAME))) {
 130  0
                                 return path;
 131  
                         }
 132  
                 }
 133  0
                 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  
          */
 147  
         private void addDOMRevision(final String number, final String added, final String removed, final String binaryStatus) {
 148  112320
                 final Element revision = document.createElement(CacheConfiguration.REVISION);
 149  112320
                 revision.setAttribute(CacheConfiguration.NUMBER, number);
 150  112320
                 revision.setAttribute(CacheConfiguration.ADDED, added);
 151  112320
                 revision.setAttribute(CacheConfiguration.REMOVED, removed);
 152  112320
                 revision.setAttribute(CacheConfiguration.BINARY_STATUS, binaryStatus);
 153  112320
                 currentPath.appendChild(revision);
 154  112320
         }
 155  
 
 156  
         /**
 157  
          * Initializes the builder for subsequent invocations of (@link
 158  
          * #buildRevision(String, String, String)).
 159  
          * 
 160  
          * @param name
 161  
          *            the filename
 162  
          */
 163  
         public void buildPath(final String name, final String revision, final String binaryStatus) {
 164  31530
                 currentFilename = repositoryFileManager.absoluteToRelativePath(name);
 165  31530
                 addDOMPath(name, revision, binaryStatus);
 166  
 
 167  31530
         }
 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  
          */
 184  
         public void buildRevision(final String number, final String added, final String removed, final String binaryStatus) {
 185  112320
                 if (!added.equals("-1") && !removed.equals("-1")) {
 186  112320
                         addDOMRevision(number, added, removed, binaryStatus);
 187  112320
                         builder.updateRevision(currentFilename, number, Integer.parseInt(added), Integer.parseInt(removed));
 188  
                 }
 189  112320
         }
 190  
 
 191  
         /**
 192  
          * Builds the DOM root.
 193  
          * 
 194  
          * @throws ParserConfigurationException
 195  
          */
 196  
         public void buildRoot() throws ParserConfigurationException {
 197  30
                 final DocumentBuilderFactory factoryDOM = DocumentBuilderFactory.newInstance();
 198  
                 DocumentBuilder builderDOM;
 199  30
                 builderDOM = factoryDOM.newDocumentBuilder();
 200  30
                 document = builderDOM.newDocument();
 201  30
                 cache = document.createElement(CacheConfiguration.CACHE);
 202  30
                 cache.setAttribute(CacheConfiguration.PROJECT, ConfigurationOptions.getProjectName());
 203  30
                 cache.setAttribute(CacheConfiguration.XML_VERSION, "1.0");
 204  30
                 document.appendChild(cache);
 205  30
         }
 206  
 
 207  
         /**
 208  
          * Returns the DOM object when building is complete.
 209  
          * 
 210  
          * @return the DOM document.
 211  
          */
 212  
         public Document getDocument() {
 213  30
                 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  
          */
 233  
         public synchronized void newRevision(String name, final String number, final String added, final String removed, final boolean binaryStatus) {
 234  0
                 name = repositoryFileManager.relativeToAbsolutePath(name);
 235  0
                 checkDocument();
 236  0
                 if (document != null) {
 237  0
                         currentPath = findDOMPath(name);
 238  0
                         if (currentPath == null) {
 239  
                                 // changes currentPath to new one
 240  0
                                 addDOMPath(name, "0", CacheConfiguration.UNKNOWN);
 241  
                         }
 242  0
                         String sBinaryStatus = CacheConfiguration.NOT_BINARY;
 243  0
                         if (binaryStatus) {
 244  0
                                 sBinaryStatus = CacheConfiguration.BINARY;
 245  
                         }
 246  0
                         addDOMRevision(number, added, removed, sBinaryStatus);
 247  
                 }
 248  0
         }
 249  
 
 250  
         private void checkDocument() {
 251  30
                 if (document == null) {
 252  
                         try {
 253  0
                                 buildRoot();
 254  0
                         } catch (final ParserConfigurationException e) {
 255  0
                                 document = null;
 256  0
                         }
 257  
                 }
 258  30
         }
 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  
          */
 273  
         public void updateBinaryStatus(final Collection fileBuilders, final String revisionNumber) {
 274  
                 // change data structure to a more appropriate one for lookup
 275  30
                 final Map mFileBuilders = new HashMap();
 276  30
                 for (final Iterator iter = fileBuilders.iterator(); iter.hasNext();) {
 277  31530
                         final FileBuilder fileBuilder = (FileBuilder) iter.next();
 278  31530
                         mFileBuilders.put(fileBuilder.getName(), fileBuilder);
 279  28377
                 }
 280  30
                 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  30
                         checkDocument();
 286  30
                         final NodeList paths = cache.getChildNodes();
 287  31560
                         for (int i = 0; i < paths.getLength(); i++) {
 288  31530
                                 final Element path = (Element) paths.item(i);
 289  31530
                                 if (mFileBuilders.containsKey(repositoryFileManager.absoluteToRelativePath(path.getAttribute(CacheConfiguration.NAME)))) {
 290  31530
                                         final FileBuilder fileBuilder = (FileBuilder) mFileBuilders.get(repositoryFileManager.absoluteToRelativePath(path
 291  
                                                 .getAttribute(CacheConfiguration.NAME)));
 292  31530
                                         updateDOMPath(path, fileBuilder.isBinary(), revisionNumber);
 293  31530
                                         mFileBuilders.remove(repositoryFileManager.absoluteToRelativePath(path.getAttribute(CacheConfiguration.NAME)));
 294  
                                 }
 295  
                         }
 296  
                         // go through remaining fileBuilders and add them to the DOM
 297  30
                         final Collection cFileBuilders = mFileBuilders.values();
 298  30
                         for (final Iterator iter = cFileBuilders.iterator(); iter.hasNext();) {
 299  0
                                 final FileBuilder fileBuilder = (FileBuilder) iter.next();
 300  0
                                 String binaryStatus = CacheConfiguration.NOT_BINARY;
 301  0
                                 if (fileBuilder.isBinary()) {
 302  0
                                         binaryStatus = CacheConfiguration.BINARY;
 303  
                                 }
 304  0
                                 addDOMPath(repositoryFileManager.relativeToAbsolutePath(fileBuilder.getName()), revisionNumber, binaryStatus);
 305  0
                         }
 306  
                 }
 307  
 
 308  30
         }
 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  
          */
 320  
         public synchronized boolean isBinary(final String fileName, final String revisionNumber) {
 321  0
                 int latestRevision = 0;
 322  0
                 int revisionToCheck = -1;
 323  0
                 checkDocument();
 324  0
                 final Element path = findDOMPath(repositoryFileManager.relativeToAbsolutePath(fileName));
 325  0
                 if (path == null) {
 326  0
                         return false;
 327  
                 }
 328  
                 try {
 329  0
                         latestRevision = Integer.parseInt(path.getAttribute(CacheConfiguration.LATEST_REVISION));
 330  0
                         revisionToCheck = Integer.parseInt(revisionNumber);
 331  0
                 } catch (final NumberFormatException e) {
 332  0
                         SvnConfigurationOptions.getTaskLogger().log(
 333  
                                 "Ignoring invalid revision number " + revisionNumber + " for " + path.getAttribute(CacheConfiguration.NAME));
 334  0
                         revisionToCheck = -1;
 335  0
                 }
 336  0
                 if (latestRevision >= revisionToCheck) {
 337  0
                         final String binaryStatus = path.getAttribute(CacheConfiguration.BINARY_STATUS);
 338  0
                         if (binaryStatus.equals(CacheConfiguration.BINARY)) {
 339  0
                                 return true;
 340  
                         }
 341  
                 }
 342  0
                 return false;
 343  
         }
 344  
 }