Coverage Report - net.sf.statsvn.input.Builder
 
Classes in this File Line Coverage Branch Coverage Complexity
Builder
86%
243/282
65%
136/210
2.818
Builder$AuthorAnonymizingProvider
0%
0/16
N/A
2.818
 
 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: Builder.java,v $
 21  
  $Date: 2004/12/14 13:38:13 $
 22  
  */
 23  
 package net.sf.statsvn.input;
 24  
 
 25  
 import java.io.IOException;
 26  
 import java.util.Date;
 27  
 import java.util.HashMap;
 28  
 import java.util.HashSet;
 29  
 import java.util.Iterator;
 30  
 import java.util.List;
 31  
 import java.util.Locale;
 32  
 import java.util.Map;
 33  
 import java.util.Properties;
 34  
 import java.util.Set;
 35  
 import java.util.SortedSet;
 36  
 import java.util.TreeSet;
 37  
 import java.util.regex.Pattern;
 38  
 
 39  
 import net.sf.statcvs.Messages;
 40  
 import net.sf.statcvs.input.CommitListBuilder;
 41  
 import net.sf.statcvs.input.NoLineCountException;
 42  
 import net.sf.statcvs.model.Author;
 43  
 import net.sf.statcvs.model.Directory;
 44  
 import net.sf.statcvs.model.Repository;
 45  
 import net.sf.statcvs.model.SymbolicName;
 46  
 import net.sf.statcvs.model.VersionedFile;
 47  
 import net.sf.statcvs.output.ConfigurationOptions;
 48  
 import net.sf.statcvs.util.FilePatternMatcher;
 49  
 import net.sf.statcvs.util.FileUtils;
 50  
 import net.sf.statcvs.util.StringUtils;
 51  
 import net.sf.statsvn.output.SvnConfigurationOptions;
 52  
 
 53  
 /**
 54  
  * <p>
 55  
  * Helps building the {@link net.sf.statsvn.model.Repository} from a SVN log. The <tt>Builder</tt> is fed by some SVN history data source, for example a SVN
 56  
  * log parser. The <tt>Repository</tt> can be retrieved using the {@link #createRepository} method.
 57  
  * </p>
 58  
  * 
 59  
  * <p>
 60  
  * The class also takes care of the creation of <tt>Author</tt> and </tt>Directory</tt> objects and makes sure that there's only one of these for each
 61  
  * author name and path. It also provides LOC count services.
 62  
  * </p>
 63  
  * 
 64  
  * @author Richard Cyganiak <richard@cyganiak.de>
 65  
  * @author Jason Kealey <jkealey@shade.ca>
 66  
  * @author Gunter Mussbacher <gunterm@site.uottawa.ca>
 67  
  * 
 68  
  * @version $Id: Builder.java 389 2009-05-27 18:17:59Z benoitx $
 69  
  * 
 70  
  */
 71  814
 public class Builder implements SvnLogBuilder {
 72  262
     private final Set atticFileNames = new HashSet();
 73  814
 
 74  262
     private final Map authors = new HashMap();
 75  814
 
 76  262
     private FileBuilder currentFileBuilder = null;
 77  814
 
 78  262
     private final Map directories = new HashMap();
 79  
 
 80  
     private final FilePatternMatcher excludePattern;
 81  814
 
 82  262
     private final Map fileBuilders = new HashMap();
 83  
 
 84  
     private final FilePatternMatcher includePattern;
 85  814
 
 86  262
     private String projectName = null;
 87  
 
 88  
     private final RepositoryFileManager repositoryFileManager;
 89  814
 
 90  262
     private Date startDate = null;
 91  814
 
 92  262
     private final Map symbolicNames = new HashMap();
 93  
 
 94  
     private final Pattern tagsPattern;
 95  
     
 96  
     public void clean() {
 97  2
         atticFileNames.clear();
 98  2
         authors.clear();
 99  2
         directories.clear();
 100  2
         fileBuilders.clear();
 101  2
         symbolicNames.clear();
 102  2
     }
 103  
 
 104  
     /**
 105  
      * Creates a new <tt>Builder</tt>
 106  814
      * 
 107  1010
      * @param repositoryFileManager
 108  1010
      *            the {@link RepositoryFileManager} that can be used to retrieve LOC counts for the files that this builder will create
 109  1010
      * @param includePattern
 110  1010
      *            a list of Ant-style wildcard patterns, seperated by : or ;
 111  1010
      * @param excludePattern
 112  1010
      *            a list of Ant-style wildcard patterns, seperated by : or ;
 113  196
      */
 114  
     public Builder(final RepositoryFileManager repositoryFileManager, final FilePatternMatcher includePattern, final FilePatternMatcher excludePattern,
 115  66
             final Pattern tagsPattern) {
 116  66
         this.repositoryFileManager = repositoryFileManager;
 117  66
         this.includePattern = includePattern;
 118  66
         this.excludePattern = excludePattern;
 119  66
         this.tagsPattern = tagsPattern;
 120  66
         directories.put("", Directory.createRoot());
 121  66
     }
 122  
 
 123  
     /**
 124  11954
      * Adds a file to the attic. This method should only be called if our first invocation to (@link #buildFile(String, boolean, boolean, Map)) was given an
 125  8622
      * invalid isInAttic field.
 126  1108
      * 
 127  11954
      * This is a hack to handle post-processing of implicit deletions at the same time as the implicit additions that can be found in Subversion.
 128  1996
      * 
 129  
      * @param filename
 130  
      *            the filename to add to the attic.
 131  
      */
 132  
     public void addToAttic(final String filename) {
 133  996
         if (!atticFileNames.contains(filename)) {
 134  552
             atticFileNames.add(filename);
 135  
         }
 136  996
     }
 137  
 
 138  
     /**
 139  
      * <p>
 140  
      * Starts building a new file. The files are not expected to be created in any particular order. Subsequent calls to (@link #buildRevision(RevisionData))
 141  
      * will add revisions to this file.
 142  
      * </p>
 143  
      * 
 144  
      * <p>
 145  
      * New in StatSVN: If the method has already been invoked with the same filename, the original file will be re-loaded and the other arguments are ignored.
 146  
      * </p>
 147  
      * 
 148  
      * @param filename
 149  
      *            the file's name with path, for example "path/file.txt"
 150  
      * @param isBinary
 151  136405
      *            <tt>true</tt> if it's a binary file
 152  131024
      * @param isInAttic
 153  18040
      *            <tt>true</tt> if the file is dead on the main branch
 154  28165
      * @param revBySymnames
 155  32909
      *            maps revision (string) by symbolic name (string)
 156  32909
      * @param dateBySymnames
 157  10122
      *            maps date (date) by symbolic name (string)
 158  900
      */
 159  
     public void buildFile(final String filename, final boolean isBinary, final boolean isInAttic, final Map revBySymnames, final Map dateBySymnames) {
 160  147771
         if (fileBuilders.containsKey(filename)) {
 161  31804
             currentFileBuilder = (FileBuilder) fileBuilders.get(filename);
 162  
         } else {
 163  2346
             currentFileBuilder = new FileBuilder(this, filename, isBinary, revBySymnames, dateBySymnames);
 164  2346
             fileBuilders.put(filename, currentFileBuilder);
 165  2346
             if (isInAttic) {
 166  448
                 addToAttic(filename);
 167  
             }
 168  
         }
 169  11765
     }
 170  493
 
 171  94
     /**
 172  
      * Starts building the module.
 173  
      * 
 174  
      * @param moduleName
 175  
      *            name of the module
 176  
      */
 177  
     public void buildModule(final String moduleName) {
 178  32
         this.projectName = moduleName;
 179  32
     }
 180  162706
 
 181  27176
     /**
 182  162706
      * Adds a revision to the current file. The revisions must be added in SVN logfile order, that is starting with the most recent one.
 183  42352
      * 
 184  2584
      * @param data
 185  162706
      *            the revision
 186  27176
      */
 187  
     public void buildRevision(final RevisionData data) {
 188  
 
 189  13558
         currentFileBuilder.addRevisionData(data);
 190  
 
 191  13558
         if (startDate == null || startDate.compareTo(data.getDate()) > 0) {
 192  1264
             startDate = data.getDate();
 193  
         }
 194  13971
     }
 195  125
 
 196  6
     /**
 197  
      * Returns a Repository object of all files.
 198  388
      * 
 199  482
      * @return Repository a Repository object
 200  26216
      */
 201  30158
     public Repository createRepository() {
 202  30064
 
 203  30098
         if (startDate == null) {
 204  4404
             return new Repository();
 205  18
         }
 206  25662
 
 207  30006
         final Repository result = new Repository();
 208  26799
         final Iterator it = fileBuilders.values().iterator();
 209  6488
         while (it.hasNext()) {
 210  2144
             final FileBuilder fileBuilder = (FileBuilder) it.next();
 211  2532
             final VersionedFile file = fileBuilder.createFile(startDate);
 212  2626
             if (file == null) {
 213  488
                 continue;
 214  94
             }
 215  2138
             result.addFile(file);
 216  2526
             SvnConfigurationOptions.getTaskLogger().log("adding " + file.getFilenameWithPath() + " (" + file.getRevisions().size() + " revisions)");
 217  2232
         }
 218  388
 
 219  94
         // Uh oh...
 220  420
         final SortedSet revisions = result.getRevisions();
 221  126
         final List commits = new CommitListBuilder(revisions).createCommitList();
 222  32
         result.setCommits(commits);
 223  
 
 224  
         //        result.setSymbolicNames(new TreeSet(symbolicNames.values()));
 225  32
         result.setSymbolicNames(getMatchingSymbolicNames());
 226  
 
 227  32
         SvnConfigurationOptions.getTaskLogger().log("SYMBOLIC NAMES - " + symbolicNames);
 228  
 
 229  107
         return result;
 230  18
     }
 231  
 
 232  
     /**
 233  
      * Returns the <tt>Set</tt> of filenames that are "in the attic".
 234  
      * 
 235  
      * @return a <tt>Set</tt> of <tt>String</tt>s
 236  
      */
 237  
     public Set getAtticFileNames() {
 238  6
         return atticFileNames;
 239  
     }
 240  133241
 
 241  22282
     /**
 242  0
      * returns the <tt>Author</tt> of the given name or creates it if it does not yet exist. Author names are handled as case-insensitive.
 243  
      * 
 244  133241
      * @param name
 245  155523
      *            the author's name
 246  155523
      * @return a corresponding <tt>Author</tt> object
 247  154864
      */
 248  22132
     public Author getAuthor(String name) {
 249  11102
         if (name == null || name.length() == 0) {
 250  0
             name = Messages.getString("AUTHOR_UNKNOWN");
 251  659
         }
 252  150
 
 253  11102
         String lowerCaseName = name.toLowerCase(Locale.getDefault());
 254  11102
         final boolean bAnon = SvnConfigurationOptions.isAnonymize();
 255  11761
         if (this.authors.containsKey(lowerCaseName)) {
 256  11198
             return (Author) this.authors.get(lowerCaseName);
 257  
         }
 258  659
 
 259  809
         Author newAuthor;
 260  713
         if (bAnon) {
 261  809
             // The first time a particular name is encountered, create an anonymized name.
 262  809
             newAuthor = new Author(AuthorAnonymizingProvider.getNewName());
 263  659
         } else {
 264  863
             newAuthor = new Author(name);
 265  243
         }
 266  659
 
 267  297
         final Properties p = ConfigurationOptions.getConfigProperties();
 268  243
 
 269  54
         if (p != null) {
 270  54
             String replacementUser = p.getProperty("user." + lowerCaseName + ".replacedBy");
 271  243
 
 272  297
             if (StringUtils.isNotEmpty(replacementUser)) {
 273  0
                 replacementUser = replacementUser.toLowerCase();
 274  150
                 if (this.authors.containsKey(replacementUser)) {
 275  150
                     return (Author) this.authors.get(replacementUser);
 276  393
                 }
 277  16319
                 lowerCaseName = replacementUser;
 278  16319
                 newAuthor = new Author(lowerCaseName);
 279  653
             }
 280  150
         }
 281  15816
 
 282  204
         if (p != null && !bAnon) {
 283  54
             newAuthor.setRealName(p.getProperty("user." + lowerCaseName + ".realName"));
 284  54
             newAuthor.setHomePageUrl(p.getProperty("user." + lowerCaseName + ".url"));
 285  204
             newAuthor.setImageUrl(p.getProperty("user." + lowerCaseName + ".image"));
 286  204
             newAuthor.setEmail(p.getProperty("user." + lowerCaseName + ".email"));
 287  9747
             newAuthor.setTwitterUserName(p.getProperty("user." + name.toLowerCase() + ".twitterUsername"));
 288  9747
             newAuthor.setTwitterUserId(p.getProperty("user." + name.toLowerCase() + ".twitterUserId"));
 289  351
             String val = p.getProperty("user." + name.toLowerCase() + ".twitterIncludeFlash");
 290  17944
             if (val != null && val.length() > 0) {
 291  25715
                 newAuthor.setTwitterIncludeFlash(Boolean.valueOf(val).booleanValue());
 292  
             }
 293  1625
             val = p.getProperty("user." + name.toLowerCase() + ".twitterIncludeHtml");
 294  1625
             if (val != null && val.length() > 0) {
 295  1571
                 newAuthor.setTwitterIncludeHtml(Boolean.valueOf(val).booleanValue());
 296  1571
             }
 297  
         }
 298  54
         this.authors.put(lowerCaseName, newAuthor);
 299  54
         return newAuthor;
 300  10629
     }
 301  9693
 
 302  4360
     /**
 303  5296
      * Returns the <tt>Directory</tt> of the given filename or creates it if it does not yet exist.
 304  1108
      * 
 305  936
      * @param filename
 306  5124
      *            the name and path of a file, for example "src/Main.java"
 307  89910
      * @return a corresponding <tt>Directory</tt> object
 308  
      */
 309  
     public Directory getDirectory(final String filename) {
 310  2154
         final int lastSlash = filename.lastIndexOf('/');
 311  2154
         if (lastSlash == -1) {
 312  66
             return getDirectoryForPath("");
 313  
         }
 314  15937
         return getDirectoryForPath(filename.substring(0, lastSlash + 1));
 315  4862
     }
 316  4360
 
 317  53946
     /**
 318  14223
      * @param path
 319  438
      *            for example "src/net/sf/statcvs/"
 320  438
      * @return the <tt>Directory</tt> corresponding to <tt>statcvs</tt>
 321  438
      */
 322  16
     private Directory getDirectoryForPath(final String path) {
 323  2362
         if (directories.containsKey(path)) {
 324  10461
             return (Directory) directories.get(path);
 325  36
         }
 326  208
         final Directory parent = getDirectoryForPath(FileUtils.getParentDirectoryPath(path));
 327  208
         final Directory newDirectory = parent.createSubdirectory(FileUtils.getDirectoryName(path));
 328  8479
         directories.put(path, newDirectory);
 329  14061
         return newDirectory;
 330  128
     }
 331  
 
 332  37710
     /**
 333  
      * New in StatSVN: We need to have access to FileBuilders after they have been created to populate them with version numbers later on.
 334  
      * 
 335  
      * @todo Beef up this interface to better encapsulate the data structure.
 336  
      * 
 337  
      * @return this builder's contained (@link FileBuilder)s.
 338  
      */
 339  12035
     public Map getFileBuilders() {
 340  12084
         return fileBuilders;
 341  
     }
 342  8235
 
 343  3704
     /**
 344  
      * @see RepositoryFileManager#getLinesOfCode(String)
 345  0
      */
 346  0
     public int getLOC(final String filename) throws NoLineCountException {
 347  1852
         if (repositoryFileManager == null) {
 348  8
             throw new NoLineCountException("no RepositoryFileManager");
 349  0
         }
 350  
 
 351  1838
         return repositoryFileManager.getLinesOfCode(filename);
 352  
     }
 353  0
 
 354  3728
     public String getProjectName() {
 355  50
         return projectName;
 356  0
     }
 357  3680
 
 358  0
     /**
 359  0
      * @see RepositoryFileManager#getRevision(String)
 360  
      */
 361  0
     public String getRevision(final String filename) throws IOException {
 362  1846
         if (repositoryFileManager == null) {
 363  16
             throw new IOException("no RepositoryFileManager");
 364  
         }
 365  31619
         return repositoryFileManager.getRevision(filename);
 366  32
     }
 367  
 
 368  29757
     /**
 369  16
      * Returns the {@link SymbolicName} with the given name or creates it if it does not yet exist.
 370  0
      * 
 371  29741
      * @param name
 372  
      *            the symbolic name's name
 373  0
      * @return the corresponding symbolic name object
 374  0
      */
 375  17865
     public SymbolicName getSymbolicName(final String name, final Date date) {
 376  18
         SymbolicName sym = (SymbolicName) symbolicNames.get(name);
 377  
 
 378  17847
         if (sym != null) {
 379  9
             return sym;
 380  
         } else {
 381  17838
             sym = new SymbolicName(name, date);
 382  0
             symbolicNames.put(name, sym);
 383  0
 
 384  0
             return sym;
 385  0
         }
 386  
     }
 387  
 
 388  
     /**
 389  
      * Matches a filename against the include and exclude patterns. If no include pattern was specified, all files will be included. If no exclude pattern was
 390  7998
      * specified, no files will be excluded.
 391  12
      * 
 392  0
      * @param filename
 393  7986
      *            a filename
 394  6
      * @return <tt>true</tt> if the filename matches one of the include patterns and does not match any of the exclude patterns. If it matches an include and
 395  0
      *         an exclude pattern, <tt>false</tt> will be returned.
 396  7980
      */
 397  
     public boolean matchesPatterns(final String filename) {
 398  3970
         if (excludePattern != null && excludePattern.matches(filename)) {
 399  4
             return false;
 400  
         }
 401  3966
         if (includePattern != null) {
 402  2
             return includePattern.matches(filename);
 403  56160
         }
 404  60124
         return true;
 405  56160
     }
 406  
 
 407  56160
     /**
 408  0
      * Matches a tag against the tag patterns. 
 409  
      * 
 410  0
      * @param tag
 411  
      *            a tag
 412  
      * @return <tt>true</tt> if the tag matches the tag pattern.
 413  33940
      */
 414  33940
     public boolean matchesTagPatterns(final String tag) {
 415  33940
         if (tagsPattern != null) {
 416  0
             return tagsPattern.matcher(tag).matches();
 417  33696
         }
 418  0
         return false;
 419  0
     }
 420  0
 
 421  
     /**
 422  0
      * New in StatSVN: Updates a particular revision for a file with new line count information. If the file or revision does not exist, action will do nothing.
 423  144
      * 
 424  144
      * Necessary because line counts are not given in the log file and hence can only be added in a second pass.
 425  144
      * 
 426  
      * @param filename
 427  0
      *            the file to be updated
 428  14976
      * @param revisionNumber
 429  0
      *            the revision number to be updated
 430  14976
      * @param linesAdded
 431  0
      *            the lines that were added
 432  14976
      * @param linesRemoved
 433  0
      *            the lines that were removed
 434  0
      */
 435  
     public synchronized void updateRevision(final String filename, final String revisionNumber, final int linesAdded, final int linesRemoved) {
 436  7488
         final FileBuilder fb = (FileBuilder) fileBuilders.get(filename);
 437  0
         if (fb != null) {
 438  7582
             fb.updateRevision(revisionNumber, linesAdded, linesRemoved);
 439  0
         }
 440  7582
     }
 441  0
 
 442  0
     /**
 443  0
      * return only a set of matching tag names (from a list on the command line).
 444  0
      */
 445  0
     private SortedSet getMatchingSymbolicNames() {
 446  32
         final TreeSet result = new TreeSet();
 447  32
         if (this.tagsPattern == null) {
 448  32
             return result;
 449  
         }
 450  0
         for (final Iterator it = this.symbolicNames.values().iterator(); it.hasNext();) {
 451  0
             final SymbolicName sn = (SymbolicName) it.next();
 452  0
             if (sn.getDate() != null && this.tagsPattern.matcher(sn.getName()).matches()) {
 453  0
                 result.add(sn);
 454  0
             }
 455  0
         }
 456  0
         return result;
 457  
     }
 458  
 
 459  0
     private static final class AuthorAnonymizingProvider {
 460  0
         private AuthorAnonymizingProvider() {
 461  
             // no access
 462  0
         }
 463  
 
 464  0
         private static int count = 0;
 465  
 
 466  
         static synchronized String getNewName() {
 467  0
             return "author" + (String.valueOf(++count));
 468  
         }
 469  
 
 470  
     }
 471  
 }