Coverage Report - net.sf.statsvn.input.SvnLogfileParser
 
Classes in this File Line Coverage Branch Coverage Complexity
SvnLogfileParser
77%
281/366
64%
167/260
3.364
SvnLogfileParser$DiffTask
0%
0/90
0%
0/12
3.364
SvnLogfileParser$PerRevDiffTask
0%
0/49
0%
0/16
3.364
 
 1  
 /*
 2  
  StatCvs - CVS statistics generation 
 3  
  Copyright (C) 2002  Lukasz Pekacki <lukasz@pekacki.de>
 4  
  http://statcvs.sf.net/
 5  
  
 6  
  This library is free software; you can redistribute it and/or
 7  
  modify it under the terms of the GNU Lesser General Public
 8  
  License as published by the Free Software Foundation; either
 9  
  version 2.1 of the License, or (at your option) any later version.
 10  
 
 11  
  This library is distributed in the hope that it will be useful,
 12  
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 13  
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 14  
  Lesser General Public License for more details.
 15  
 
 16  
  You should have received a copy of the GNU Lesser General Public
 17  
  License along with this library; if not, write to the Free Software
 18  
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 19  
  
 20  
  $RCSfile: SvnLogfileParser.java,v $ 
 21  
  Created on $Date: 2004/10/10 11:29:07 $ 
 22  
  */
 23  
 
 24  
 package net.sf.statsvn.input;
 25  
 
 26  
 import java.io.FileInputStream;
 27  
 import java.io.FileNotFoundException;
 28  
 import java.io.IOException;
 29  
 import java.io.InputStream;
 30  
 import java.util.ArrayList;
 31  
 import java.util.Collection;
 32  
 import java.util.Collections;
 33  
 import java.util.Date;
 34  
 import java.util.HashMap;
 35  
 import java.util.HashSet;
 36  
 import java.util.Iterator;
 37  
 import java.util.List;
 38  
 import java.util.Map;
 39  
 import java.util.Vector;
 40  
 
 41  
 import javax.xml.parsers.ParserConfigurationException;
 42  
 import javax.xml.parsers.SAXParser;
 43  
 import javax.xml.parsers.SAXParserFactory;
 44  
 
 45  
 import net.sf.statcvs.input.LogSyntaxException;
 46  
 import net.sf.statsvn.output.SvnConfigurationOptions;
 47  
 import net.sf.statsvn.util.BinaryDiffException;
 48  
 import net.sf.statsvn.util.FilenameComparator;
 49  
 import net.sf.statsvn.util.SvnDiffUtils;
 50  
 import net.sf.statsvn.util.XMLUtil;
 51  
 
 52  
 import org.xml.sax.SAXException;
 53  
 
 54  
 import edu.emory.mathcs.backport.java.util.concurrent.ExecutorService;
 55  
 import edu.emory.mathcs.backport.java.util.concurrent.Executors;
 56  
 import edu.emory.mathcs.backport.java.util.concurrent.TimeUnit;
 57  
 
 58  
 /**
 59  
  * Parses a Subversion logfile and does post-parse processing. A {@link Builder}
 60  
  * must be specified which does the construction work.
 61  
  * 
 62  
  * @author Jason Kealey <jkealey@shade.ca>
 63  
  * @author Gunter Mussbacher <gunterm@site.uottawa.ca>
 64  
  * 
 65  
  * @version $Id: SvnLogfileParser.java 368 2008-06-25 21:23:46Z benoitx $
 66  0
  */
 67  0
 public class SvnLogfileParser {
 68  
     private static final int INTERMEDIARY_SAVE_INTERVAL_MS = 120000;
 69  
 
 70  
     private static final String REPOSITORIES_XML = "repositories.xml";
 71  
 
 72  
     private final SvnLogBuilder builder;
 73  
 
 74  
     private final InputStream logFile;
 75  
 
 76  
     private final RepositoryFileManager repositoryFileManager;
 77  
 
 78  
     private CacheBuilder cacheBuilder;
 79  3
 
 80  27
     private HashSet revsForNewDiff = null;
 81  
 
 82  
     /**
 83  
      * Default Constructor
 84  
      * 
 85  
      * @param repositoryFileManager
 86  
      *            the repository file manager
 87  
      * @param logFile
 88  
      *            a <tt>Reader</tt> containing the SVN logfile
 89  
      * @param builder
 90  
      *            the builder that will process the log information
 91  3
      */
 92  30
     public SvnLogfileParser(final RepositoryFileManager repositoryFileManager, final InputStream logFile, final SvnLogBuilder builder) {
 93  30
         this.logFile = logFile;
 94  30
         this.builder = builder;
 95  30
         this.repositoryFileManager = repositoryFileManager;
 96  27
     }
 97  
 
 98  
     /**
 99  
      * Because the log file does not contain the lines added or removed in a
 100  
      * commit, and because the logfile contains implicit actions (@link
 101  
      * #verifyImplicitActions()), we must query the repository for line
 102  
      * differences. This method uses the (@link LineCountsBuilder) to load the
 103  
      * persisted information and (@link SvnDiffUtils) to find new information.
 104  
      * 
 105  
      * @param factory
 106  
      *            the factory used to create SAX parsers.
 107  
      * @throws IOException
 108  
      */
 109  3
     protected void handleLineCounts(final SAXParserFactory factory) throws IOException {
 110  30
         long startTime = System.currentTimeMillis();
 111  27
         final String xmlFile = SvnConfigurationOptions.getCacheDir() + REPOSITORIES_XML;
 112  3
 
 113  30
         final RepositoriesBuilder repositoriesBuilder = readAndParseXmlFile(factory, xmlFile);
 114  30
         cacheFileName = SvnConfigurationOptions.getCacheDir() + repositoriesBuilder.getFileName(repositoryFileManager.getRepositoryUuid());
 115  30
         XMLUtil.writeXmlFile(repositoriesBuilder.getDocument(), xmlFile);
 116  30
         SvnConfigurationOptions.getTaskLogger().log("parsing repositories finished in " + (System.currentTimeMillis() - startTime) + " ms.");
 117  27
         startTime = System.currentTimeMillis();
 118  3
 
 119  30
         readCache(factory);
 120  30
         SvnConfigurationOptions.getTaskLogger().log("parsing line counts finished in " + (System.currentTimeMillis() - startTime) + " ms.");
 121  27
         startTime = System.currentTimeMillis();
 122  
 
 123  
         // update the cache xml file with the latest binary status information
 124  3
         // from the working copy
 125  27
         cacheBuilder.updateBinaryStatus(builder.getFileBuilders().values(), repositoryFileManager.getRootRevisionNumber());
 126  3
 
 127  27
         final Collection fileBuilders = builder.getFileBuilders().values();
 128  3
 
 129  27
         calculateNumberRequiredCalls(fileBuilders);
 130  
 
 131  3
         // concurrency
 132  30
         ExecutorService poolService = null;
 133  30
         if (SvnConfigurationOptions.getNumberSvnDiffThreads() > 1) {
 134  27
             poolService = Executors.newFixedThreadPool(SvnConfigurationOptions.getNumberSvnDiffThreads());
 135  
         }
 136  3
 
 137  30
         boolean isFirstDiff = true;
 138  30
         calls = 0;
 139  30
         groupStart = System.currentTimeMillis();
 140  27
         boolean poolUseRequired = false;
 141  3
 
 142  27
         if (SvnConfigurationOptions.isLegacyDiff()) {
 143  0
             for (final Iterator iter = fileBuilders.iterator(); iter.hasNext();) {
 144  0
                 final FileBuilder fileBuilder = (FileBuilder) iter.next();
 145  0
                 final String fileName = fileBuilder.getName();
 146  0
                 if (fileBuilder.isBinary() || !builder.matchesPatterns(fileName)) {
 147  0
                     continue;
 148  0
                 }
 149  0
                 final List revisions = fileBuilder.getRevisions();
 150  0
                 for (int i = 0; i < revisions.size(); i++) {
 151  
                     // line diffs are expensive operations. therefore, the
 152  
                     // result is
 153  
                     // stored in the
 154  
                     // cacheBuilder and eventually persisted in the cache xml
 155  
                     // file.
 156  
                     // the next time
 157  
                     // the file is read the line diffs (or 0/0 in case of binary
 158  
                     // files) are intialized
 159  
                     // in the RevisionData. this cause hasNoLines to be false
 160  
                     // which
 161  
                     // in turn causes the
 162  0
                     // if clause below to be skipped.
 163  0
                     if (i + 1 < revisions.size() && ((RevisionData) revisions.get(i)).hasNoLines() && !((RevisionData) revisions.get(i)).isDeletion()) {
 164  0
                         if (((RevisionData) revisions.get(i + 1)).isDeletion()) {
 165  0
                             continue;
 166  0
                         }
 167  0
                         final String revNrNew = ((RevisionData) revisions.get(i)).getRevisionNumber();
 168  0
                         if (cacheBuilder.isBinary(fileName, revNrNew)) {
 169  0
                             continue;
 170  0
                         }
 171  0
                         final String revNrOld = ((RevisionData) revisions.get(i + 1)).getRevisionNumber();
 172  0
 
 173  0
                         if (isFirstDiff) {
 174  0
                             SvnConfigurationOptions.getTaskLogger().info("Contacting server to obtain line count information.");
 175  0
                             SvnConfigurationOptions.getTaskLogger().info(
 176  
                                     "This information will be cached so that the next time you run StatSVN, results will be returned more quickly.");
 177  0
 
 178  0
                             if (SvnConfigurationOptions.isLegacyDiff()) {
 179  0
                                 SvnConfigurationOptions.getTaskLogger().info("Using the legacy Subversion 1.3 diff mechanism: one diff per file per revision.");
 180  0
                             } else {
 181  0
                                 SvnConfigurationOptions.getTaskLogger().info("Using the Subversion 1.4 diff mechanism: one diff per revision.");
 182  
                             }
 183  0
 
 184  0
                             isFirstDiff = false;
 185  
                         }
 186  0
 
 187  0
                         final DiffTask diff = new DiffTask(fileName, revNrNew, revNrOld, fileBuilder);
 188  
 
 189  
                         // SvnConfigurationOptions.getTaskLogger().log(Thread.currentThread().getName()
 190  
                         // + " Schedule task for " + fileName + " rev:" +
 191  
                         // revNrNew);
 192  0
 
 193  0
                         poolUseRequired = executeTask(poolService, poolUseRequired, diff);
 194  
                     }
 195  0
                 }
 196  0
             }
 197  3
         } else {
 198  27
             for (final Iterator iter = revsForNewDiff.iterator(); iter.hasNext();) {
 199  0
                 final String revNrNew = (String) iter.next();
 200  0
                 final PerRevDiffTask diff = new PerRevDiffTask(revNrNew, builder.getFileBuilders());
 201  0
 
 202  0
                 poolUseRequired = executeTask(poolService, poolUseRequired, diff);
 203  0
             }
 204  
 
 205  3
         }
 206  30
         waitForPoolIfRequired(poolService);
 207  30
         SvnConfigurationOptions.getTaskLogger().log("parsing svn diff");
 208  30
         XMLUtil.writeXmlFile(cacheBuilder.getDocument(), cacheFileName);
 209  30
         SvnConfigurationOptions.getTaskLogger().log("parsing svn diff finished in " + (System.currentTimeMillis() - startTime) + " ms.");
 210  27
     }
 211  
 
 212  0
     private boolean executeTask(final ExecutorService poolService, boolean poolUseRequired, final DiffTask diff) {
 213  0
         if (poolUseRequired && SvnConfigurationOptions.getNumberSvnDiffThreads() > 1) {
 214  0
             poolService.execute(diff);
 215  0
         } else {
 216  0
             final long start = System.currentTimeMillis();
 217  0
             diff.run();
 218  0
             final long end = System.currentTimeMillis();
 219  0
             poolUseRequired = (end - start) > SvnConfigurationOptions.getThresholdInMsToUseConcurrency();
 220  0
         }
 221  0
         return poolUseRequired;
 222  
     }
 223  
 
 224  3
     private void waitForPoolIfRequired(final ExecutorService poolService) {
 225  30
         if (SvnConfigurationOptions.getNumberSvnDiffThreads() > 1 && poolService != null) {
 226  27
             SvnConfigurationOptions.getTaskLogger().info(
 227  
                     "Scheduled " + requiredDiffCalls + " svn diff calls on " + Math.min(requiredDiffCalls, SvnConfigurationOptions.getNumberSvnDiffThreads())
 228  3
                             + " threads.");
 229  27
             poolService.shutdown();
 230  3
             try {
 231  30
                 SvnConfigurationOptions.getTaskLogger().log("================ Wait for completion =========================");
 232  27
                 if (!poolService.awaitTermination(2, TimeUnit.DAYS)) {
 233  0
                     SvnConfigurationOptions.getTaskLogger().log("================ TIME OUT!!! =========================");
 234  0
                 }
 235  0
             } catch (final InterruptedException e) {
 236  3
                 SvnConfigurationOptions.getTaskLogger().error(e.toString());
 237  27
             }
 238  3
         }
 239  27
     }
 240  
 
 241  
     private void calculateNumberRequiredCalls(final Collection fileBuilders) {
 242  3
         // Calculate the number of required calls...
 243  27
         requiredDiffCalls = 0;
 244  3
 
 245  30
         if (!SvnConfigurationOptions.isLegacyDiff()) {
 246  27
             revsForNewDiff = new HashSet();
 247  
         }
 248  3
 
 249  3180
         for (final Iterator iter = fileBuilders.iterator(); iter.hasNext();) {
 250  31530
             final FileBuilder fileBuilder = (FileBuilder) iter.next();
 251  31530
             final String fileName = fileBuilder.getName();
 252  31092
             if (!fileBuilder.isBinary() && builder.matchesPatterns(fileName)) {
 253  42972
                 final List revisions = fileBuilder.getRevisions();
 254  182655
                 for (int i = 0; i < revisions.size(); i++) {
 255  142941
                     if (i + 1 < revisions.size() && ((RevisionData) revisions.get(i)).hasNoLines() && !((RevisionData) revisions.get(i)).isDeletion()) {
 256  5430
                         if (((RevisionData) revisions.get(i + 1)).isDeletion()) {
 257  4887
                             continue;
 258  0
                         }
 259  0
                         final String revNrNew = ((RevisionData) revisions.get(i)).getRevisionNumber();
 260  0
                         if (cacheBuilder.isBinary(fileName, revNrNew)) {
 261  0
                             continue;
 262  
                         }
 263  
                         // count if legacy diff or this rev wasn't already
 264  0
                         // counted.
 265  0
                         if (revsForNewDiff == null || !revsForNewDiff.contains(revNrNew)) {
 266  0
                             requiredDiffCalls++;
 267  0
 
 268  0
                             if (revsForNewDiff != null) {
 269  0
                                 revsForNewDiff.add(revNrNew);
 270  
                             }
 271  
                         }
 272  
                     }
 273  
                 }
 274  3153
             }
 275  25224
         }
 276  3
         // END Calculate the number of required calls...
 277  27
     }
 278  
 
 279  3
     private void readCache(final SAXParserFactory factory) throws IOException {
 280  30
         cacheBuilder = new CacheBuilder(builder, repositoryFileManager);
 281  27
         FileInputStream cacheFile = null;
 282  3
         try {
 283  30
             cacheFile = new FileInputStream(cacheFileName);
 284  30
             final SAXParser parser = factory.newSAXParser();
 285  30
             parser.parse(cacheFile, new SvnXmlCacheFileHandler(cacheBuilder));
 286  27
             cacheFile.close();
 287  0
         } catch (final ParserConfigurationException e) {
 288  0
             SvnConfigurationOptions.getTaskLogger().error("Cache: " + e.toString());
 289  0
         } catch (final SAXException e) {
 290  0
             SvnConfigurationOptions.getTaskLogger().error("Cache: " + e.toString());
 291  0
         } catch (final FileNotFoundException e) {
 292  0
             SvnConfigurationOptions.getTaskLogger().log("Cache: " + e.toString());
 293  0
         } catch (final IOException e) {
 294  0
             SvnConfigurationOptions.getTaskLogger().error("Cache: " + e.toString());
 295  3
         } finally {
 296  30
             if (cacheFile != null) {
 297  27
                 cacheFile.close();
 298  
             }
 299  3
         }
 300  27
     }
 301  
 
 302  3
     private RepositoriesBuilder readAndParseXmlFile(final SAXParserFactory factory, final String xmlFile) throws IOException {
 303  30
         final RepositoriesBuilder repositoriesBuilder = new RepositoriesBuilder();
 304  27
         FileInputStream repositoriesFile = null;
 305  3
         try {
 306  30
             repositoriesFile = new FileInputStream(xmlFile);
 307  30
             final SAXParser parser = factory.newSAXParser();
 308  30
             parser.parse(repositoriesFile, new SvnXmlRepositoriesFileHandler(repositoriesBuilder));
 309  27
             repositoriesFile.close();
 310  0
         } catch (final ParserConfigurationException e) {
 311  0
             SvnConfigurationOptions.getTaskLogger().error("Repositories: " + e.toString());
 312  0
         } catch (final SAXException e) {
 313  0
             SvnConfigurationOptions.getTaskLogger().error("Repositories: " + e.toString());
 314  0
         } catch (final FileNotFoundException e) {
 315  0
             SvnConfigurationOptions.getTaskLogger().log("Repositories: " + e.toString());
 316  0
         } catch (final IOException e) {
 317  0
             SvnConfigurationOptions.getTaskLogger().error("Repositories: " + e.toString());
 318  3
         } finally {
 319  30
             if (repositoriesFile != null) {
 320  27
                 repositoriesFile.close();
 321  
             }
 322  3
         }
 323  27
         return repositoriesBuilder;
 324  
     }
 325  
 
 326  
     /**
 327  
      * Parses the logfile. After <tt>parse()</tt> has finished, the result of
 328  
      * the parsing process can be obtained from the builder.
 329  
      * 
 330  
      * @throws LogSyntaxException
 331  
      *             if syntax errors in log
 332  
      * @throws IOException
 333  
      *             if errors while reading from the log Reader
 334  
      */
 335  
     public void parse() throws LogSyntaxException, IOException {
 336  3
 
 337  27
         final SAXParserFactory factory = parseSvnLog();
 338  3
 
 339  27
         verifyImplicitActions();
 340  
 
 341  3
         // must be after verifyImplicitActions();
 342  27
         removeDirectories();
 343  3
 
 344  27
         handleLineCounts(factory);
 345  3
 
 346  27
     }
 347  
 
 348  
     /**
 349  
      * The svn log can contain deletions of directories which imply that all of
 350  
      * its contents have been deleted.
 351  
      * 
 352  
      * Furthermore, the svn log can contain entries which are copies from other
 353  
      * directories (additions or replacements; I haven't seen modifications with
 354  
      * this property, but am not 100% sure) meaning that all files from the
 355  
      * other directory are copied here. We currently do not go back through
 356  
      * copies, so we must infer what files <i>could</i> have been added during
 357  
      * those copies.
 358  
      * 
 359  
      */
 360  
     protected void verifyImplicitActions() {
 361  
         // this method most certainly has issues with implicit actions on root
 362  
         // folder.
 363  3
 
 364  30
         final long startTime = System.currentTimeMillis();
 365  27
         SvnConfigurationOptions.getTaskLogger().log("verifying implicit actions ...");
 366  3
 
 367  27
         final HashSet implicitActions = new HashSet();
 368  
 
 369  3
         // get all filenames
 370  30
         final ArrayList files = new ArrayList();
 371  27
         final Collection fileBuilders = fetchAllFileNames(files);
 372  
 
 373  
         // sort them so that folders are immediately followed by the folder
 374  
         // entries and then by other files which are prefixed by the folder
 375  3
         // name.
 376  27
         Collections.sort(files, new FilenameComparator());
 377  
 
 378  3444
         // for each file
 379  34437
         for (int i = 0; i < files.size(); i++) {
 380  34410
             final String parent = files.get(i).toString();
 381  30969
             final FileBuilder parentBuilder = (FileBuilder) builder.getFileBuilders().get(parent);
 382  
             // check to see if there are files that indicate that parent is a
 383  17682
             // folder.
 384  159138
             for (int j = i + 1; j < files.size() && files.get(j).toString().indexOf(parent + "/") == 0; j++) {
 385  14241
                 // we might not know that it was a folder.
 386  128169
                 repositoryFileManager.addDirectory(parent);
 387  14241
 
 388  142410
                 final String child = files.get(j).toString();
 389  128169
                 final FileBuilder childBuilder = (FileBuilder) builder.getFileBuilders().get(child);
 390  14241
                 // for all revisions in the the parent folder
 391  143463
                 for (final Iterator iter = parentBuilder.getRevisions().iterator(); iter.hasNext();) {
 392  137646
                     final RevisionData parentData = (RevisionData) iter.next();
 393  
                     int parentRevision;
 394  15294
                     try {
 395  137646
                         parentRevision = Integer.parseInt(parentData.getRevisionNumber());
 396  0
                     } catch (final Exception e) {
 397  15294
                         continue;
 398  137646
                     }
 399  
 
 400  15294
                     // ignore modifications to folders
 401  137646
                     if (parentData.isCreationOrRestore() || parentData.isDeletion()) {
 402  
                         int k;
 403  
 
 404  
                         // check to see if the parent revision is an implicit
 405  15282
                         // action acting on the child.
 406  137538
                         k = detectActionOnChildGivenActionOnParent(childBuilder, parentRevision);
 407  
 
 408  15282
                         // we found something to insert
 409  138255
                         if (k < childBuilder.getRevisions().size()) {
 410  6453
                             createImplicitAction(implicitActions, child, childBuilder, parentData, k);
 411  
                         }
 412  15294
                     }
 413  122352
                 }
 414  
             }
 415  
         }
 416  
 
 417  
         // Some implicit revisions may have resulted in double deletion
 418  
         // (e.g. deleting a directory and THEN deleting the parent directory).
 419  3
         // this will get rid of any consecutive deletion.
 420  27
         cleanPotentialDuplicateImplicitActions(fileBuilders);
 421  
 
 422  
         // in the preceeding block, we add implicit additions to too may files.
 423  
         // possibly a folder was deleted and restored later on, without the
 424  
         // specific file being re-added. we get rid of those here. however,
 425  
         // without knowledge of what was copied during the implicit additions /
 426  
         // replacements, we will remove as many implicit actions as possible
 427  
         // 
 428  
         // this solution is imperfect.
 429  
 
 430  
         // Examples:
 431  
         // IA ID IA ID M A -> ID M A
 432  3
         // IA ID A D M A -> ID A D M A
 433  30
         removePotentialInconsistencies(implicitActions, fileBuilders);
 434  30
         SvnConfigurationOptions.getTaskLogger().log("verifying implicit actions finished in " + (System.currentTimeMillis() - startTime) + " ms.");
 435  27
     }
 436  
 
 437  
     private void createImplicitAction(final HashSet implicitActions, final String child, final FileBuilder childBuilder, final RevisionData parentData,
 438  
             final int k) {
 439  717
         // we want to memorize this implicit action.
 440  7170
         final RevisionData implicit = parentData.createCopy();
 441  6453
         implicitActions.add(implicit);
 442  
 
 443  717
         // avoid concurrent modification errors.
 444  7170
         final List toMove = new ArrayList();
 445  9729
         for (final Iterator it = childBuilder.getRevisions().subList(k, childBuilder.getRevisions().size()).iterator(); it.hasNext();) {
 446  29484
             final RevisionData revToMove = (RevisionData) it.next();
 447  
             // if
 448  
             // (!revToMove.getRevisionNumber().equals(implicit.getRevisionNumber()))
 449  3276
             // {
 450  29484
             toMove.add(revToMove);
 451  3276
             // }
 452  26208
         }
 453  
 
 454  717
         // remove the revisions to be moved.
 455  6453
         childBuilder.getRevisions().removeAll(toMove);
 456  
 
 457  
         // don't call addRevision directly. buildRevision
 458  717
         // does more.
 459  6453
         builder.buildFile(child, false, false, new HashMap(), new HashMap());
 460  
 
 461  
         // only add the implicit if the last one for the
 462  
         // file is NOT a deletion!
 463  
         // if (!toMove.isEmpty() && !((RevisionData)
 464  717
         // toMove.get(0)).isDeletion()) {
 465  6453
         builder.buildRevision(implicit);
 466  
         // }
 467  
 
 468  717
         // copy back the revisions we removed.
 469  9729
         for (final Iterator it = toMove.iterator(); it.hasNext();) {
 470  29484
             builder.buildRevision((RevisionData) it.next());
 471  717
         }
 472  6453
     }
 473  
 
 474  
     private int detectActionOnChildGivenActionOnParent(final FileBuilder childBuilder, final int parentRevision) {
 475  83664
         int k;
 476  824901
         for (k = 0; k < childBuilder.getRevisions().size(); k++) {
 477  719250
             final RevisionData childData = (RevisionData) childBuilder.getRevisions().get(k);
 478  647325
             final int childRevision = Integer.parseInt(childData.getRevisionNumber());
 479  
 
 480  
             // we don't want to add duplicate entries for the
 481  71925
             // same revision
 482  650151
             if (parentRevision == childRevision) {
 483  28260
                 k = childBuilder.getRevisions().size();
 484  25434
                 break;
 485  
             }
 486  69099
 
 487  622608
             if (parentRevision > childRevision) {
 488  6453
                 break; // we must insert it here!
 489  
             }
 490  15282
         }
 491  137538
         return k;
 492  
     }
 493  
 
 494  3
     private void removePotentialInconsistencies(final HashSet implicitActions, final Collection fileBuilders) {
 495  3468
         for (final Iterator iter = fileBuilders.iterator(); iter.hasNext();) {
 496  30969
             final FileBuilder filebuilder = (FileBuilder) iter.next();
 497  
 
 498  
             // make sure our attic is well set, with our new deletions that we
 499  3441
             // might have added.
 500  31791
             if (!repositoryFileManager.existsInWorkingCopy(filebuilder.getName())) {
 501  7398
                 builder.addToAttic(filebuilder.getName());
 502  
             }
 503  
 
 504  3441
             // do we detect an inconsistency?
 505  30999
             if (!repositoryFileManager.existsInWorkingCopy(filebuilder.getName()) && !filebuilder.finalRevisionIsDead()) {
 506  366
                 int earliestDelete = -1;
 507  960
                 for (int i = 0; i < filebuilder.getRevisions().size(); i++) {
 508  864
                     final RevisionData data = (RevisionData) filebuilder.getRevisions().get(i);
 509  96
 
 510  897
                     if (data.isDeletion()) {
 511  297
                         earliestDelete = i;
 512  
                     }
 513  96
 
 514  873
                     if ((!data.isCreationOrRestore() && data.isChange()) || !implicitActions.contains(data)) {
 515  81
                         break;
 516  
                     }
 517  
                 }
 518  30
 
 519  270
                 if (earliestDelete > 0) {
 520  30
                     // avoid concurrent modification errors.
 521  300
                     final List toRemove = new ArrayList();
 522  306
                     for (final Iterator it = filebuilder.getRevisions().subList(0, earliestDelete).iterator(); it.hasNext();) {
 523  324
                         toRemove.add(it.next());
 524  30
                     }
 525  270
                     filebuilder.getRevisions().removeAll(toRemove);
 526  
                 }
 527  3441
             }
 528  27531
         }
 529  27
     }
 530  
 
 531  3
     private void cleanPotentialDuplicateImplicitActions(final Collection fileBuilders) {
 532  3468
         for (final Iterator iter = fileBuilders.iterator(); iter.hasNext();) {
 533  30969
             final FileBuilder filebuilder = (FileBuilder) iter.next();
 534  3441
 
 535  34410
             boolean previousIsDelete = false;
 536  30969
             final List toRemove = new ArrayList();
 537  
             // for this file, iterate through all revisions and store any
 538  
             // deletion revision that follows
 539  3441
             // a deletion.
 540  47940
             for (final Iterator it = filebuilder.getRevisions().iterator(); it.hasNext();) {
 541  169710
                 final RevisionData data = (RevisionData) it.next();
 542  152754
                 if (data.isDeletion() && previousIsDelete) {
 543  135
                     toRemove.add(data);
 544  16971
                 }
 545  169710
                 previousIsDelete = data.isDeletion();
 546  135768
             }
 547  
 
 548  3441
             // get rid of the duplicate deletion for this file.
 549  30984
             if (!toRemove.isEmpty()) {
 550  135
                 filebuilder.getRevisions().removeAll(toRemove);
 551  3441
             }
 552  27531
         }
 553  27
     }
 554  
 
 555  3
     private Collection fetchAllFileNames(final ArrayList files) {
 556  30
         final Collection fileBuilders = builder.getFileBuilders().values();
 557  3468
         for (final Iterator iter = fileBuilders.iterator(); iter.hasNext();) {
 558  34410
             final FileBuilder fileBuilder = (FileBuilder) iter.next();
 559  34410
             files.add(fileBuilder.getName());
 560  27531
         }
 561  27
         return fileBuilders;
 562  
     }
 563  
 
 564  
     /**
 565  
      * We have created FileBuilders for directories because we needed the
 566  
      * information to be able to find implicit actions. However, we don't want
 567  
      * to query directories for their line counts later on. Therefore, we must
 568  
      * remove them here.
 569  
      * 
 570  
      * (@link SvnInfoUtils#isDirectory(String)) is used to know what files are
 571  
      * directories. Deleted directories are assumed to have been added in (@link
 572  
      * #verifyImplicitActions())
 573  
      */
 574  3
     protected void removeDirectories() {
 575  30
         final Collection fileBuilders = builder.getFileBuilders().values();
 576  30
         final ArrayList toRemove = new ArrayList();
 577  3468
         for (final Iterator iter = fileBuilders.iterator(); iter.hasNext();) {
 578  34410
             final FileBuilder fileBuilder = (FileBuilder) iter.next();
 579  31257
             if (repositoryFileManager.isDirectory(fileBuilder.getName())) {
 580  2592
                 toRemove.add(fileBuilder.getName());
 581  3441
             }
 582  27528
         }
 583  3
 
 584  315
         for (final Iterator iter = toRemove.iterator(); iter.hasNext();) {
 585  2592
             builder.getFileBuilders().remove(iter.next());
 586  
         }
 587  3
 
 588  27
     }
 589  
 
 590  
     /**
 591  
      * Parses the svn log file.
 592  
      * 
 593  
      * @return the SaxParserFactory, so that it can be reused.
 594  
      * @throws IOException
 595  
      *             errors while reading file.
 596  
      * @throws LogSyntaxException
 597  
      *             invalid log syntax.
 598  
      */
 599  3
     protected SAXParserFactory parseSvnLog() throws IOException, LogSyntaxException {
 600  30
         final long startTime = System.currentTimeMillis();
 601  27
         SvnConfigurationOptions.getTaskLogger().log("starting to parse...");
 602  3
 
 603  27
         final SAXParserFactory factory = SAXParserFactory.newInstance();
 604  3
         try {
 605  30
             final SAXParser parser = factory.newSAXParser();
 606  27
             parser.parse(logFile, new SvnXmlLogFileHandler(builder, repositoryFileManager));
 607  0
         } catch (final ParserConfigurationException e) {
 608  0
             throw new LogSyntaxException("svn log: " + e.getMessage());
 609  0
         } catch (final SAXException e) {
 610  3
             throw new LogSyntaxException("svn log: " + e.getMessage());
 611  27
         }
 612  3
 
 613  30
         SvnConfigurationOptions.getTaskLogger().log("parsing svn log finished in " + (System.currentTimeMillis() - startTime) + " ms.");
 614  27
         return factory;
 615  
     }
 616  3
 
 617  27
     private long totalTime = 0;
 618  3
 
 619  27
     private long groupStart = 0;
 620  3
 
 621  27
     private int calls = 0;
 622  3
 
 623  27
     private int requiredDiffCalls = 0;
 624  
 
 625  
     private String cacheFileName;
 626  
 
 627  
     protected class DiffTask implements Runnable {
 628  
         private String fileName;
 629  
         private String newRevision;
 630  
         private String oldRevision;
 631  
         private FileBuilder fileBuilder;
 632  
 
 633  0
         protected DiffTask() {
 634  0
         }
 635  0
 
 636  0
         protected DiffTask(final String newRevision) {
 637  0
             super();
 638  0
             this.newRevision = newRevision;
 639  0
         }
 640  0
 
 641  0
         public DiffTask(final String fileName, final String newRevision, final String oldRevision, final FileBuilder fileBuilder) {
 642  0
             super();
 643  0
             this.fileName = fileName;
 644  0
             this.newRevision = newRevision;
 645  0
             this.oldRevision = oldRevision;
 646  0
             this.fileBuilder = fileBuilder;
 647  0
         }
 648  
 
 649  
         /**
 650  0
          * @return the fileName
 651  
          */
 652  
         public String getFileName() {
 653  0
             return fileName;
 654  
         }
 655  
 
 656  
         /**
 657  
          * @param fileName
 658  0
          *            the fileName to set
 659  0
          */
 660  
         public void setFileName(final String fileName) {
 661  0
             this.fileName = fileName;
 662  0
         }
 663  
 
 664  
         /**
 665  0
          * @return the newRevision
 666  
          */
 667  
         public String getNewRevision() {
 668  0
             return newRevision;
 669  
         }
 670  
 
 671  
         /**
 672  
          * @param newRevision
 673  0
          *            the newRevision to set
 674  0
          */
 675  
         public void setNewRevision(final String newRevision) {
 676  0
             this.newRevision = newRevision;
 677  0
         }
 678  
 
 679  
         /**
 680  0
          * @return the oldRevision
 681  
          */
 682  
         public String getOldRevision() {
 683  0
             return oldRevision;
 684  
         }
 685  
 
 686  
         /**
 687  
          * @param oldRevision
 688  0
          *            the oldRevision to set
 689  0
          */
 690  
         public void setOldRevision(final String oldRevision) {
 691  0
             this.oldRevision = oldRevision;
 692  0
         }
 693  0
 
 694  
         public void run() {
 695  
             int[] lineDiff;
 696  0
             long end = 0L;
 697  0
             try {
 698  0
                 // SvnConfigurationOptions.getTaskLogger().log(Thread.currentThread().getName()
 699  0
                 // + " Starts... now");
 700  0
                 final long start = System.currentTimeMillis();
 701  0
                 lineDiff = repositoryFileManager.getLineDiff(oldRevision, newRevision, fileName);
 702  0
                 end = System.currentTimeMillis();
 703  0
                 synchronized (cacheBuilder) {
 704  0
                     totalTime += (end - start);
 705  0
                 }
 706  
 
 707  0
                 SvnConfigurationOptions.getTaskLogger().info(
 708  0
                         "svn diff " + (++calls) + "/" + requiredDiffCalls + ": " + fileName + ", r" + oldRevision + " to r" + newRevision + ", +" + lineDiff[0]
 709  0
                                 + " -" + lineDiff[1] + " (" + (end - start) + " ms.) " + Thread.currentThread().getName());
 710  0
             } catch (final BinaryDiffException e) {
 711  0
                 calls++;
 712  0
                 trackBinaryFile();
 713  0
                 return;
 714  0
             } catch (final IOException e) {
 715  0
                 SvnConfigurationOptions.getTaskLogger()
 716  
                         .error("" + (++calls) + "/" + requiredDiffCalls + " IOException: Unable to obtain diff: " + e.toString());
 717  0
                 return;
 718  0
             }
 719  0
 
 720  0
             trackFileDiff(lineDiff);
 721  
 
 722  0
             performIntermediarySave(end);
 723  0
         }
 724  0
 
 725  0
         protected void trackBinaryFile() {
 726  0
             // file is binary and has been deleted
 727  0
             cacheBuilder.newRevision(fileName, newRevision, "0", "0", true);
 728  0
             fileBuilder.setBinary(true);
 729  0
         }
 730  0
 
 731  0
         protected void trackFileDiff(final int[] lineDiff) {
 732  0
             if (lineDiff[0] != -1 && lineDiff[1] != -1) {
 733  0
                 builder.updateRevision(fileName, newRevision, lineDiff[0], lineDiff[1]);
 734  0
                 cacheBuilder.newRevision(fileName, newRevision, lineDiff[0] + "", lineDiff[1] + "", false);
 735  0
             } else {
 736  0
                 SvnConfigurationOptions.getTaskLogger().info("unknown behaviour; to be investigated:" + fileName + " r:" + oldRevision + "/r:" + newRevision);
 737  
             }
 738  0
         }
 739  0
 
 740  0
         protected void performIntermediarySave(long end) {
 741  0
             synchronized (cacheBuilder) {
 742  0
                 if (end - groupStart > INTERMEDIARY_SAVE_INTERVAL_MS) {
 743  0
                     final long start = System.currentTimeMillis();
 744  0
                     XMLUtil.writeXmlFile(cacheBuilder.getDocument(), cacheFileName);
 745  0
                     groupStart = System.currentTimeMillis();
 746  0
                     final double estimateLeftInMs = ((double) totalTime / (double) calls * (requiredDiffCalls - calls) / SvnConfigurationOptions
 747  
                             .getNumberSvnDiffThreads());
 748  0
                     end = System.currentTimeMillis();
 749  0
                     SvnConfigurationOptions.getTaskLogger().info(
 750  0
                             System.getProperty("line.separator") + new Date() + " Intermediary save took " + (end - start) + " ms. Estimated completion="
 751  0
                                     + new Date(end + (long) estimateLeftInMs) + System.getProperty("line.separator"));
 752  
                 }
 753  0
             }
 754  0
         }
 755  
 
 756  
         protected FileBuilder getFileBuilder() {
 757  0
             return fileBuilder;
 758  0
         }
 759  0
 
 760  0
         protected void setFileBuilder(final FileBuilder fileBuilder) {
 761  0
             this.fileBuilder = fileBuilder;
 762  0
         }
 763  
 
 764  
     }
 765  
 
 766  0
     protected class PerRevDiffTask extends DiffTask {
 767  
         private Map fileBuilders;
 768  
 
 769  0
         public PerRevDiffTask(final String newRevision, final Map fileBuilders) {
 770  0
             super(newRevision);
 771  0
             this.fileBuilders = fileBuilders;
 772  0
         }
 773  0
 
 774  0
         public void run() {
 775  0
             int[] lineDiff;
 776  
             Vector results;
 777  0
             long end = 0L;
 778  
             try {
 779  
                 // SvnConfigurationOptions.getTaskLogger().log(Thread.currentThread().getName()
 780  
                 // + " Starts... now");
 781  0
                 final long start = System.currentTimeMillis();
 782  0
                 results = repositoryFileManager.getRevisionDiff(getNewRevision());
 783  0
                 end = System.currentTimeMillis();
 784  0
                 synchronized (cacheBuilder) {
 785  0
                     totalTime += (end - start);
 786  0
                 }
 787  0
 
 788  0
                 SvnConfigurationOptions.getTaskLogger().info(
 789  
                         "svn diff " + (++calls) + "/" + requiredDiffCalls + " on r" + getNewRevision() + " (" + (end - start) + " ms.) "
 790  0
                                 + Thread.currentThread().getName());
 791  0
 
 792  0
                 for (int i = 0; i < results.size(); i++) {
 793  0
                     final Object[] element = (Object[]) results.get(i);
 794  
 
 795  0
                     if (element.length == SvnDiffUtils.RESULT_SIZE && fileBuilders.containsKey(element[0].toString())) {
 796  0
                         setFileName(element[0].toString());
 797  0
                         setFileBuilder((FileBuilder) fileBuilders.get(getFileName()));
 798  0
                         lineDiff = (int[]) element[1];
 799  0
                         setOldRevision("?");
 800  
 
 801  0
                         final Boolean isBinary = (Boolean) element[2];
 802  0
                         if (isBinary.booleanValue()) {
 803  0
                             trackBinaryFile();
 804  
                         }
 805  0
 
 806  0
                         SvnConfigurationOptions.getTaskLogger().info(
 807  0
                                 "\t " + getFileName() + ", on r" + getNewRevision() + ", +" + lineDiff[0] + " -" + lineDiff[1]);
 808  
 
 809  0
                         trackFileDiff(lineDiff);
 810  0
                     } else {
 811  0
                         SvnConfigurationOptions.getTaskLogger().error("Problem with diff " + i + " for revision " + getNewRevision() + ".");
 812  0
                     }
 813  0
                 }
 814  
 
 815  0
             } catch (final BinaryDiffException e) {
 816  
                 // not supposed to happen. tracked individually.
 817  0
                 return;
 818  0
             } catch (final IOException e) {
 819  0
                 SvnConfigurationOptions.getTaskLogger()
 820  
                         .error("" + (++calls) + "/" + requiredDiffCalls + " IOException: Unable to obtain diff: " + e.toString());
 821  0
                 return;
 822  0
             }
 823  
 
 824  0
             performIntermediarySave(end);
 825  0
         }
 826  
     }
 827 &n