Coverage Report - net.sf.statsvn.input.FileBuilder
 
Classes in this File Line Coverage Branch Coverage Complexity
FileBuilder
74%
117/159
59%
54/92
3.045
 
 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: FileBuilder.java,v $
 21  
  $Date: 2005/03/29 22:45:06 $
 22  
  */
 23  
 package net.sf.statsvn.input;
 24  
 
 25  
 import java.io.IOException;
 26  
 import java.util.ArrayList;
 27  
 import java.util.Date;
 28  
 import java.util.Iterator;
 29  
 import java.util.List;
 30  
 import java.util.ListIterator;
 31  
 import java.util.Map;
 32  
 import java.util.SortedSet;
 33  
 import java.util.TreeSet;
 34  
 
 35  
 import net.sf.statcvs.input.NoLineCountException;
 36  
 import net.sf.statcvs.model.Revision;
 37  
 import net.sf.statcvs.model.SymbolicName;
 38  
 import net.sf.statcvs.model.VersionedFile;
 39  
 import net.sf.statsvn.output.SvnConfigurationOptions;
 40  
 
 41  
 /**
 42  
  * <p>
 43  
  * Builds a {@link VersionedFile} with {@link Revision}s from logging data.
 44  
  * This class is responsible for deciding if a file or revisions will be
 45  
  * included in the report, for translating from CVS logfile data structures to
 46  
  * the data structures in the <tt>net.sf.statsvn.model</tt> package, and for
 47  
  * calculating the LOC history for the file.
 48  
  * </p>
 49  
  * 
 50  
  * <p>
 51  
  * A main goal of this class is to delay the creation of the
 52  
  * <tt>VersionedFile</tt> object until all revisions of the file have been
 53  
  * collected from the log. We could simply create <tt>VersionedFile</tt> and
 54  
  * <tt>Revision</tt>s on the fly as we parse through the log, but this
 55  
  * creates a problem if we decide not to include the file after reading several
 56  
  * revisions. The creation of a <tt>VersionedFile</tt> or <tt>Revision</tt>
 57  
  * can cause many more objects to be created (<tt>Author</tt>,
 58  
  * <tt>Directory</tt>, <tt>Commit</tt>), and it would be very hard to get
 59  
  * rid of them if we don't want the file. This problem is solved by first
 60  
  * collecting all information about one file in this class, and then, with all
 61  
  * information present, deciding if we want to create the model instances or
 62  
  * not.
 63  
  * </p>
 64  
  * 
 65  
  * @author Richard Cyganiak <richard@cyganiak.de>
 66  
  * @author Tammo van Lessen
 67  
  * @author Jason Kealey <jkealey@shade.ca>
 68  
  * @author Gunter Mussbacher <gunterm@site.uottawa.ca>
 69  
  * @version $Id: FileBuilder.java 351 2008-03-28 18:46:26Z benoitx $
 70  
  */
 71  
 public class FileBuilder {
 72  
         private static final int ONE_SECOND = 1000;
 73  
 
 74  
         private static final int ONE_MIN_IN_MS = 60000;
 75  
 
 76  
         private final Builder builder;
 77  
 
 78  
         private final String name;
 79  
 
 80  
         private boolean binary;
 81  
 
 82  35420
         private final List revisions = new ArrayList();
 83  
 
 84  
         private final Map revBySymnames;
 85  
 
 86  
         private final Map dateBySymnames;
 87  
 
 88  
         private int locDelta;
 89  
 
 90  
         /**
 91  
          * Creates a new <tt>FileBuilder</tt>.
 92  
          * 
 93  
          * @param builder
 94  
          *            a <tt>Builder</tt> that provides factory services for author
 95  
          *            and directory instances and line counts.
 96  
          * @param name
 97  
          *            the filename
 98  
          * @param binary
 99  
          *            Is this a binary file or not?
 100  
          */
 101  35420
         public FileBuilder(final Builder builder, final String name, final boolean isBinary, final Map revBySymnames, final Map dateBySymnames) {
 102  35420
                 this.builder = builder;
 103  35420
                 this.name = name;
 104  35420
                 this.binary = isBinary;
 105  35420
                 this.revBySymnames = revBySymnames;
 106  35420
                 this.dateBySymnames = dateBySymnames;
 107  
 
 108  35420
                 SvnConfigurationOptions.getTaskLogger().log("logging " + name);
 109  35420
         }
 110  
 
 111  
         /**
 112  
          * Adds a revision to the file. The revisions must be added in the same
 113  
          * order as they appear in the CVS logfile, that is, most recent first.
 114  
          * 
 115  
          * @param data
 116  
          *            the revision
 117  
          */
 118  
         public void addRevisionData(final RevisionData data) {
 119  203506
                 if (binary && !data.isCreationOrRestore()) {
 120  2199
                         data.setLines(0, 0);
 121  
                 }
 122  203506
                 this.revisions.add(data);
 123  
 
 124  203506
                 locDelta += getLOCChange(data);
 125  203506
         }
 126  
 
 127  
         /**
 128  
          * Creates and returns a {@link VersionedFile} representation of the file.
 129  
          * <tt>null</tt> is returned if the file does not meet certain criteria,
 130  
          * for example if its filename meets an exclude filter or if it was dead
 131  
          * during the entire logging timespan.
 132  
          * 
 133  
          * @param beginOfLogDate
 134  
          *            the date of the begin of the log
 135  
          * @return a <tt>VersionedFile</tt> representation of the file.
 136  
          */
 137  
         public VersionedFile createFile(final Date beginOfLogDate) {
 138  32373
                 if (isFilteredFile() || !fileExistsInLogPeriod()) {
 139  129
                         return null;
 140  
                 }
 141  
 
 142  32244
                 final VersionedFile file = new VersionedFile(name, builder.getDirectory(name));
 143  
 
 144  32244
                 if (revisions.isEmpty()) {
 145  98
                         buildBeginOfLogRevision(file, beginOfLogDate, getFinalLOC(), null);
 146  98
                         return file;
 147  
                 }
 148  
 
 149  32146
                 final Iterator it = revisions.iterator();
 150  32146
                 RevisionData currentData = (RevisionData) it.next();
 151  32146
                 int currentLOC = getFinalLOC();
 152  
                 RevisionData previousData;
 153  
                 int previousLOC;
 154  
                 SortedSet symbolicNames;
 155  
 
 156  165768
                 while (it.hasNext()) {
 157  133622
                         previousData = currentData;
 158  133622
                         previousLOC = currentLOC;
 159  133622
                         currentData = (RevisionData) it.next();
 160  133622
                         currentLOC = previousLOC - getLOCChange(previousData);
 161  
 
 162  
                         // symbolic names for previousData
 163  133622
                         symbolicNames = createSymbolicNamesCollection(previousData);
 164  
 
 165  133622
                         if (previousData.isCreationOrRestore() || previousData.isChange() || isBinary()) {
 166  120205
                                 if (currentData.isDeletion()) {
 167  5462
                                         buildCreationRevision(file, previousData, previousLOC, symbolicNames);
 168  
                                 } else {
 169  114743
                                         buildChangeRevision(file, previousData, previousLOC, symbolicNames);
 170  
                                 }
 171  13417
                         } else if (previousData.isDeletion()) {
 172  13417
                                 buildDeletionRevision(file, previousData, previousLOC, symbolicNames);
 173  
                         } else {
 174  0
                                 SvnConfigurationOptions.getTaskLogger().info("illegal state in " + file.getFilenameWithPath() + ":" + previousData.getRevisionNumber());
 175  
                         }
 176  
                 }
 177  
 
 178  
                 // symbolic names for currentData
 179  32146
                 symbolicNames = createSymbolicNamesCollection(currentData);
 180  
 
 181  32146
                 final int nextLinesOfCode = currentLOC - getLOCChange(currentData);
 182  32146
                 if (currentData.isCreationOrRestore()) {
 183  31328
                         buildCreationRevision(file, currentData, currentLOC, symbolicNames);
 184  818
                 } else if (currentData.isDeletion()) {
 185  60
                         buildDeletionRevision(file, currentData, currentLOC, symbolicNames);
 186  60
                         buildBeginOfLogRevision(file, beginOfLogDate, nextLinesOfCode, symbolicNames);
 187  758
                 } else if (currentData.isChange()) {
 188  758
                         buildChangeRevision(file, currentData, currentLOC, symbolicNames);
 189  758
                         currentData.setDate(new Date(currentData.getDate().getTime() - ONE_SECOND));
 190  758
                         buildCreationRevision(file, currentData, 0, symbolicNames);
 191  758
                         buildBeginOfLogRevision(file, beginOfLogDate, nextLinesOfCode, symbolicNames);
 192  
                 } else {
 193  0
                         SvnConfigurationOptions.getTaskLogger().info("illegal state in " + file.getFilenameWithPath() + ":" + currentData.getRevisionNumber());
 194  
                 }
 195  32146
                 return file;
 196  
         }
 197  
 
 198  
         /**
 199  
          * Gets a LOC count for the file's most recent revision. If the file exists
 200  
          * in the local checkout, we ask the {@link RepositoryFileManager} to count
 201  
          * its lines of code. If not (that is, it is dead), return an approximated
 202  
          * LOC value for its last non-dead revision.
 203  
          * 
 204  
          * @return the LOC count for the file's most recent revision.
 205  
          */
 206  
         private int getFinalLOC() {
 207  32244
                 if (binary) {
 208  4510
                         return 0;
 209  
                 }
 210  
 
 211  27734
                 String revision = null;
 212  
                 try {
 213  27734
                         revision = builder.getRevision(name);
 214  8214
                 } catch (final IOException e) {
 215  8214
                         if (!finalRevisionIsDead()) {
 216  231
                                 SvnConfigurationOptions.getTaskLogger().info(e.getMessage());
 217  
                         }
 218  19520
                 }
 219  
 
 220  
                 try {
 221  
                         // if ("1.1".equals(revision)) {
 222  
                         // return builder.getLOC(name) + locDelta;
 223  
                         // } else {
 224  27734
                         if (!revisions.isEmpty()) {
 225  27636
                                 final RevisionData firstAdded = (RevisionData) revisions.get(0);
 226  27636
                                 if (!finalRevisionIsDead() && !firstAdded.getRevisionNumber().equals(revision)) {
 227  601
                                         SvnConfigurationOptions.getTaskLogger().info("Revision of " + name + " does not match expected revision");
 228  
                                 }
 229  
                         }
 230  27734
                         return builder.getLOC(name);
 231  
                         // }
 232  8274
                 } catch (final NoLineCountException e) {
 233  8274
                         if (!finalRevisionIsDead()) {
 234  259
                                 SvnConfigurationOptions.getTaskLogger().info(e.getMessage());
 235  
                         }
 236  8274
                         return approximateFinalLOC();
 237  
                 }
 238  
         }
 239  
 
 240  
         /**
 241  
          * Returns <tt>true</tt> if the file's most recent revision is dead.
 242  
          * 
 243  
          * @return <tt>true</tt> if the file is dead.
 244  
          */
 245  
         protected boolean finalRevisionIsDead() {
 246  52344
                 if (revisions.isEmpty()) {
 247  66
                         return false;
 248  
                 }
 249  52278
                 return ((RevisionData) revisions.get(0)).isDeletion();
 250  
         }
 251  
 
 252  
         /**
 253  
          * Returns <tt>true</tt> if the file has revisions.
 254  
          * 
 255  
          * @return Returns <tt>true</tt> if the file has revisions.
 256  
          */
 257  
         public boolean existRevision() {
 258  0
                 return !revisions.isEmpty();
 259  
         }
 260  
 
 261  
         /**
 262  
          * Approximates the LOC count for files that are not present in the local
 263  
          * checkout. If a file was deleted at some point in history, then we can't
 264  
          * count its final lines of code. This algorithm calculates a lower bound
 265  
          * for the file's LOC prior to deletion by following the ups and downs of
 266  
          * the revisions.
 267  
          * 
 268  
          * @return a lower bound for the file's LOC before it was deleted
 269  
          */
 270  
         private int approximateFinalLOC() {
 271  8274
                 int max = 0;
 272  8274
                 int current = 0;
 273  8274
                 final Iterator it = revisions.iterator();
 274  46000
                 while (it.hasNext()) {
 275  37726
                         final RevisionData data = (RevisionData) it.next();
 276  37726
                         current += data.getLinesAdded();
 277  37726
                         max = Math.max(current, max);
 278  37726
                         current -= data.getLinesRemoved();
 279  33958
                 }
 280  8274
                 return max;
 281  
         }
 282  
 
 283  
         /**
 284  
          * Returns the change in LOC count caused by a revision. If there were 10
 285  
          * lines added and 3 lines removed, 7 would be returned. This does not take
 286  
          * into account file deletion and creation.
 287  
          * 
 288  
          * @param data
 289  
          *            a revision
 290  
          * @return the change in LOC count
 291  
          */
 292  
         private int getLOCChange(final RevisionData data) {
 293  369274
                 return data.getLinesAdded() - data.getLinesRemoved();
 294  
         }
 295  
 
 296  
         private void buildCreationRevision(final VersionedFile file, final RevisionData data, final int loc, final SortedSet symbolicNames) {
 297  37548
                 file.addInitialRevision(data.getRevisionNumber(), builder.getAuthor(data.getLoginName()), data.getDate(), data.getComment(), loc, symbolicNames);
 298  37548
         }
 299  
 
 300  
         private void buildChangeRevision(final VersionedFile file, final RevisionData data, final int loc, final SortedSet symbolicNames) {
 301  115501
                 file.addChangeRevision(data.getRevisionNumber(), builder.getAuthor(data.getLoginName()), data.getDate(), data.getComment(), loc, data.getLinesAdded()
 302  
                         - data.getLinesRemoved(), Math.min(data.getLinesAdded(), data.getLinesRemoved()), symbolicNames);
 303  115501
         }
 304  
 
 305  
         private void buildDeletionRevision(final VersionedFile file, final RevisionData data, final int loc, final SortedSet symbolicNames) {
 306  13477
                 file.addDeletionRevision(data.getRevisionNumber(), builder.getAuthor(data.getLoginName()), data.getDate(), data.getComment(), loc, symbolicNames);
 307  13477
         }
 308  
 
 309  
         private void buildBeginOfLogRevision(final VersionedFile file, final Date beginOfLogDate, final int loc, final SortedSet symbolicNames) {
 310  916
                 final Date date = new Date(beginOfLogDate.getTime() - ONE_MIN_IN_MS);
 311  916
                 final Revision dummyForMove = file.addBeginOfLogRevision(date, loc, symbolicNames);
 312  
 
 313  
                 // + BX: DO NOT add a 0.0 revision to this SymbolicNames set as this
 314  
                 // would duplicate the impact of the
 315  
                 // move on the TAG set.
 316  916
                 if (symbolicNames != null) {
 317  0
                         final Iterator it = symbolicNames.iterator();
 318  0
                         while (it.hasNext()) {
 319  0
                                 ((SymbolicName) it.next()).getRevisions().remove(dummyForMove);
 320  
                         }
 321  
                 }
 322  916
         }
 323  
 
 324  
         /**
 325  
          * Takes a filename and checks if it should be processed or not. Can be used
 326  
          * to filter out unwanted files.
 327  
          * 
 328  
          * @return <tt>true</tt> if this file should not be processed
 329  
          */
 330  
         private boolean isFilteredFile() {
 331  32373
                 return !this.builder.matchesPatterns(this.name);
 332  
         }
 333  
 
 334  
         /**
 335  
          * Returns <tt>false</tt> if the file did never exist in the timespan
 336  
          * covered by the log. For our purposes, a file is non-existant if it has no
 337  
          * revisions and does not exists in the module checkout. Note: A file with
 338  
          * no revisions must be included in the report if it does exist in the
 339  
          * module checkout. This happens if it was created before the log started,
 340  
          * and not changed before the log ended.
 341  
          * 
 342  
          * @return <tt>true</tt> if the file did exist at some point in the log
 343  
          *         period.
 344  
          */
 345  
         private boolean fileExistsInLogPeriod() {
 346  32373
                 if (revisions.size() > 0 || binary) {
 347  32146
                         return true;
 348  
                 }
 349  
                 try {
 350  227
                         builder.getLOC(name);
 351  98
                         return true;
 352  129
                 } catch (final NoLineCountException fileDoesNotExistInTimespan) {
 353  129
                         return false;
 354  
                 }
 355  
         }
 356  
 
 357  
         /**
 358  
          * Creates a sorted set containing all symbolic name objects affected by
 359  
          * this revision. If this revision has no symbolic names, this method
 360  
          * returns null.
 361  
          * 
 362  
          * @param revisionData
 363  
          *            this revision
 364  
          * @return the sorted set or null
 365  
          */
 366  
         private SortedSet createSymbolicNamesCollection(final RevisionData revisionData) {
 367  165768
                 SortedSet symbolicNames = null;
 368  
 
 369  165768
                 final int currentRevision = getRevisionAsInt(revisionData.getRevisionNumber());
 370  165768
                 SvnConfigurationOptions.getTaskLogger().log("\n" + name + " CURRENT REVISION = " + currentRevision + " Deleted " + revisionData.isDeletion());
 371  
 
 372  165768
                 if (revisions.isEmpty()) {
 373  0
                         SvnConfigurationOptions.getTaskLogger().log("NO Revisions....");
 374  0
                         return symbolicNames;
 375  
                 }
 376  
 
 377  
                 // go through each possible tag
 378  165768
                 for (final Iterator tags = revBySymnames.entrySet().iterator(); tags.hasNext();) {
 379  0
                         final Map.Entry tag = (Map.Entry) tags.next();
 380  
 
 381  0
                         final int tagRevision = getRevisionAsInt((String) tag.getValue());
 382  
 
 383  0
                         SvnConfigurationOptions.getTaskLogger().log("Considering tag REV " + tagRevision + " name=" + tag.getKey());
 384  
 
 385  
                         // go through the revisions for this file
 386  
                         // in order to find either the rev ON the tag or JUST BEFORE!
 387  0
                         int previousRevisionForThisFile = getRevisionAsInt(((RevisionData) revisions.get(revisions.size() - 1)).getRevisionNumber());
 388  0
                         int revisionToTag = -1;
 389  0
                         for (final ListIterator it = revisions.listIterator(revisions.size()); it.hasPrevious();) {
 390  0
                                 final RevisionData data = (RevisionData) it.previous();
 391  
 
 392  0
                                 SvnConfigurationOptions.getTaskLogger().log(
 393  
                                         "File REV " + data.getRevisionNumber() + " =>" + data.getDate() + " vs " + tagRevision + " Deletion:" + data.isDeletion());
 394  
 
 395  0
                                 final int dataRev = getRevisionAsInt(data.getRevisionNumber());
 396  
 
 397  0
                                 if (revisionData.isDeletion() && currentRevision < dataRev) {
 398  
                                         // the file is deleted (revisionData.isDeletion) AND the
 399  
                                         // currentRevision is BEFORE the current tag
 400  
                                         // so we should not tag this.
 401  0
                                         previousRevisionForThisFile = getRevisionAsInt(data.getRevisionNumber());
 402  0
                                         continue;
 403  0
                                 } else if (dataRev == tagRevision) {
 404  0
                                         revisionToTag = tagRevision;
 405  0
                                         break;
 406  0
                                 } else if (dataRev > tagRevision && tagRevision >= previousRevisionForThisFile) {
 407  0
                                         revisionToTag = previousRevisionForThisFile;
 408  0
                                         SvnConfigurationOptions.getTaskLogger().log("1/ Revision to TAG " + revisionToTag);
 409  0
                                         break;
 410  
                                 }
 411  
 
 412  0
                                 previousRevisionForThisFile = getRevisionAsInt(data.getRevisionNumber());
 413  0
                         }
 414  
 
 415  
                         // if the LAST revision for this fuke is before the TAG revision
 416  
                         // and the file is NOT deleted, then we should tag it!
 417  0
                         if (previousRevisionForThisFile < tagRevision && !revisionData.isDeletion()) {
 418  0
                                 revisionToTag = previousRevisionForThisFile;
 419  0
                                 SvnConfigurationOptions.getTaskLogger().log("2/ Revision to TAG " + revisionToTag);
 420  
                         }
 421  
 
 422  0
                         SvnConfigurationOptions.getTaskLogger().log("Revision to TAG " + revisionToTag);
 423  
 
 424  0
                         if (revisionToTag > 0 && revisionToTag == currentRevision) {
 425  
                                 // previous revision is the last one for this tag
 426  0
                                 if (symbolicNames == null) {
 427  0
                                         symbolicNames = new TreeSet();
 428  
                                 }
 429  0
                                 SvnConfigurationOptions.getTaskLogger().log(
 430  
                                         "adding revision " + name + "," + currentRevision + " to symname " + tag.getKey() + " Date:" + dateBySymnames.get(tag.getKey()) + " A:"
 431  
                                                 + revisionData.getLinesAdded() + " R:" + revisionData.getLinesRemoved());
 432  0
                                 symbolicNames.add(builder.getSymbolicName((String) tag.getKey(), (Date) dateBySymnames.get(tag.getKey())));
 433  
                         }
 434  0
                 }
 435  
 
 436  165768
                 return symbolicNames;
 437  
         }
 438  
 
 439  
         private int getRevisionAsInt(final String revisionNumber) {
 440  165768
                 int rev = 0;
 441  165768
                 if (revisionNumber != null && !revisionNumber.equals("0.0")) {
 442  165768
                         rev = Integer.valueOf(revisionNumber).intValue();
 443  
                 }
 444  165768
                 return rev;
 445  
         }
 446  
 
 447  
         /**
 448  
          * New in StatSVN: Gives the FileBuilder's filename.
 449  
          * 
 450  
          * @todo Beef up this interface to better encapsulate the data structure.
 451  
          * 
 452  
          * @return the filename
 453  
          */
 454  
         public String getName() {
 455  211800
                 return name;
 456  
         }
 457  
 
 458  
         /**
 459  
          * New in StatSVN: The list of revisions made on this file.
 460  
          * 
 461  
          * @todo Beef up this interface to better encapsulate the data structure.
 462  
          * 
 463  
          * @return the list of revisions on this file
 464  
          */
 465  
         public List getRevisions() {
 466  1965120
                 return revisions;
 467  
         }
 468  
 
 469  
         /**
 470  
          * New in StatSVN: Returns a particular revision made on this file or
 471  
          * <tt>null</tt> if it doesn't exist.
 472  
          * 
 473  
          * @return a particular revision made on this file or <tt>null</tt> if it
 474  
          *         doesn't exist.
 475  
          */
 476  
         private RevisionData findRevision(final String revisionNumber) {
 477  780480
                 for (int i = 0; i < revisions.size(); i++) {
 478  780480
                         final RevisionData data = (RevisionData) revisions.get(i);
 479  780480
                         if (data.getRevisionNumber().equals(revisionNumber)) {
 480  112320
                                 return data;
 481  
                         }
 482  
                 }
 483  0
                 return null;
 484  
         }
 485  
 
 486  
         /**
 487  
          * New in StatSVN: Returns <tt>true</tt> if this file is known to be
 488  
          * binary.
 489  
          * 
 490  
          * @todo Beef up this interface to better encapsulate the data structure.
 491  
          * 
 492  
          * @return <tt>true</tt> if this file is known to be binary,
 493  
          *         <tt>false</tt> otherwise.
 494  
          */
 495  
         public synchronized boolean isBinary() {
 496  76510
                 return binary;
 497  
         }
 498  
 
 499  
         /**
 500  
          * New in StatSVN: Sets the file's binary flag.
 501  
          * 
 502  
          * @todo Beef up this interface to better encapsulate the data structure.
 503  
          * 
 504  
          * @param binary
 505  
          *            is the file binary?
 506  
          */
 507  
         public synchronized void setBinary(final boolean isBinary) {
 508  0
                 this.binary = isBinary;
 509  0
         }
 510  
 
 511  
         /**
 512  
          * New in StatSVN: Updates a particular revision with new line count
 513  
          * information. If the file or revision does not exist, action will do
 514  
          * nothing.
 515  
          * 
 516  
          * Necessary because line counts are not given in the log file and hence can
 517  
          * only be added in a second pass.
 518  
          * 
 519  
          * @param revisionNumber
 520  
          *            the revision number to be updated
 521  
          * @param linesAdded
 522  
          *            the lines that were added
 523  
          * @param linesRemoved
 524  
          *            the lines that were removed
 525  
          */
 526  
         public void updateRevision(final String revisionNumber, final int linesAdded, final int linesRemoved) {
 527  112320
                 final RevisionData data = findRevision(revisionNumber);
 528  112320
                 if (data != null) {
 529  112320
                         data.setLines(linesAdded, linesRemoved);
 530  
                 }
 531  112320
         }
 532  
 
 533  
 }