Coverage Report - net.sf.statsvn.input.Builder
 
Classes in this File Line Coverage Branch Coverage Complexity
Builder
85%
95/112
70%
39/56
2.619
Builder$AuthorAnonymizingProvider
0%
0/4
N/A
2.619
 
 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.statsvn.output.SvnConfigurationOptions;
 51  
 
 52  
 /**
 53  
  * <p>
 54  
  * 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
 55  
  * log parser. The <tt>Repository</tt> can be retrieved using the {@link #createRepository} method.
 56  
  * </p>
 57  
  * 
 58  
  * <p>
 59  
  * 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
 60  
  * author name and path. It also provides LOC count services.
 61  
  * </p>
 62  
  * 
 63  
  * @author Richard Cyganiak <richard@cyganiak.de>
 64  
  * @author Jason Kealey <jkealey@shade.ca>
 65  
  * @author Gunter Mussbacher <gunterm@site.uottawa.ca>
 66  
  * 
 67  
  * @version $Id: Builder.java 351 2008-03-28 18:46:26Z benoitx $
 68  
  * 
 69  
  */
 70  
 public class Builder implements SvnLogBuilder {
 71  99
         private final Set atticFileNames = new HashSet();
 72  
 
 73  99
         private final Map authors = new HashMap();
 74  
 
 75  99
         private FileBuilder currentFileBuilder = null;
 76  
 
 77  99
         private final Map directories = new HashMap();
 78  
 
 79  
         private final FilePatternMatcher excludePattern;
 80  
 
 81  99
         private final Map fileBuilders = new HashMap();
 82  
 
 83  
         private final FilePatternMatcher includePattern;
 84  
 
 85  99
         private String projectName = null;
 86  
 
 87  
         private final RepositoryFileManager repositoryFileManager;
 88  
 
 89  99
         private Date startDate = null;
 90  
 
 91  99
         private final Map symbolicNames = new HashMap();
 92  
 
 93  
         private final Pattern tagsPattern;
 94  
 
 95  
         /**
 96  
          * Creates a new <tt>Builder</tt>
 97  
          * 
 98  
          * @param repositoryFileManager
 99  
          *            the {@link RepositoryFileManager} that can be used to retrieve LOC counts for the files that this builder will create
 100  
          * @param includePattern
 101  
          *            a list of Ant-style wildcard patterns, seperated by : or ;
 102  
          * @param excludePattern
 103  
          *            a list of Ant-style wildcard patterns, seperated by : or ;
 104  
          */
 105  
         public Builder(final RepositoryFileManager repositoryFileManager, final FilePatternMatcher includePattern, final FilePatternMatcher excludePattern,
 106  99
                 final Pattern tagsPattern) {
 107  99
                 this.repositoryFileManager = repositoryFileManager;
 108  99
                 this.includePattern = includePattern;
 109  99
                 this.excludePattern = excludePattern;
 110  99
                 this.tagsPattern = tagsPattern;
 111  99
                 directories.put("", Directory.createRoot());
 112  99
         }
 113  
 
 114  
         /**
 115  
          * 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
 116  
          * invalid isInAttic field.
 117  
          * 
 118  
          * 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.
 119  
          * 
 120  
          * @param filename
 121  
          *            the filename to add to the attic.
 122  
          */
 123  
         public void addToAttic(final String filename) {
 124  1494
                 if (!atticFileNames.contains(filename)) {
 125  828
                         atticFileNames.add(filename);
 126  
                 }
 127  1494
         }
 128  
 
 129  
         /**
 130  
          * <p>
 131  
          * Starts building a new file. The files are not expected to be created in any particular order. Subsequent calls to (@link #buildRevision(RevisionData))
 132  
          * will add revisions to this file.
 133  
          * </p>
 134  
          * 
 135  
          * <p>
 136  
          * 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.
 137  
          * </p>
 138  
          * 
 139  
          * @param filename
 140  
          *            the file's name with path, for example "path/file.txt"
 141  
          * @param isBinary
 142  
          *            <tt>true</tt> if it's a binary file
 143  
          * @param isInAttic
 144  
          *            <tt>true</tt> if the file is dead on the main branch
 145  
          * @param revBySymnames
 146  
          *            maps revision (string) by symbolic name (string)
 147  
          * @param dateBySymnames
 148  
          *            maps date (date) by symbolic name (string)
 149  
          */
 150  
         public void buildFile(final String filename, final boolean isBinary, final boolean isInAttic, final Map revBySymnames, final Map dateBySymnames) {
 151  17049
                 if (fileBuilders.containsKey(filename)) {
 152  13530
                         currentFileBuilder = (FileBuilder) fileBuilders.get(filename);
 153  
                 } else {
 154  3519
                         currentFileBuilder = new FileBuilder(this, filename, isBinary, revBySymnames, dateBySymnames);
 155  3519
                         fileBuilders.put(filename, currentFileBuilder);
 156  3519
                         if (isInAttic) {
 157  672
                                 addToAttic(filename);
 158  
                         }
 159  
                 }
 160  17049
         }
 161  
 
 162  
         /**
 163  
          * Starts building the module.
 164  
          * 
 165  
          * @param moduleName
 166  
          *            name of the module
 167  
          */
 168  
         public void buildModule(final String moduleName) {
 169  48
                 this.projectName = moduleName;
 170  48
         }
 171  
 
 172  
         /**
 173  
          * Adds a revision to the current file. The revisions must be added in SVN logfile order, that is starting with the most recent one.
 174  
          * 
 175  
          * @param data
 176  
          *            the revision
 177  
          */
 178  
         public void buildRevision(final RevisionData data) {
 179  
 
 180  20337
                 currentFileBuilder.addRevisionData(data);
 181  
 
 182  20337
                 if (startDate == null || startDate.compareTo(data.getDate()) > 0) {
 183  1896
                         startDate = data.getDate();
 184  
                 }
 185  20337
         }
 186  
 
 187  
         /**
 188  
          * Returns a Repository object of all files.
 189  
          * 
 190  
          * @return Repository a Repository object
 191  
          */
 192  
         public Repository createRepository() {
 193  
 
 194  51
                 if (startDate == null) {
 195  3
                         return new Repository();
 196  
                 }
 197  
 
 198  48
                 final Repository result = new Repository();
 199  48
                 final Iterator it = fileBuilders.values().iterator();
 200  3264
                 while (it.hasNext()) {
 201  3216
                         final FileBuilder fileBuilder = (FileBuilder) it.next();
 202  3216
                         final VersionedFile file = fileBuilder.createFile(startDate);
 203  3216
                         if (file == null) {
 204  9
                                 continue;
 205  
                         }
 206  3207
                         result.addFile(file);
 207  3207
                         SvnConfigurationOptions.getTaskLogger().log("adding " + file.getFilenameWithPath() + " (" + file.getRevisions().size() + " revisions)");
 208  3207
                 }
 209  
 
 210  
                 // Uh oh...
 211  48
                 final SortedSet revisions = result.getRevisions();
 212  48
                 final List commits = new CommitListBuilder(revisions).createCommitList();
 213  48
                 result.setCommits(commits);
 214  
 
 215  
                 //        result.setSymbolicNames(new TreeSet(symbolicNames.values()));
 216  48
                 result.setSymbolicNames(getMatchingSymbolicNames());
 217  
 
 218  48
                 SvnConfigurationOptions.getTaskLogger().log("SYMBOLIC NAMES - " + symbolicNames);
 219  
 
 220  48
                 return result;
 221  
         }
 222  
 
 223  
         /**
 224  
          * Returns the <tt>Set</tt> of filenames that are "in the attic".
 225  
          * 
 226  
          * @return a <tt>Set</tt> of <tt>String</tt>s
 227  
          */
 228  
         public Set getAtticFileNames() {
 229  9
                 return atticFileNames;
 230  
         }
 231  
 
 232  
         /**
 233  
          * 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.
 234  
          * 
 235  
          * @param name
 236  
          *            the author's name
 237  
          * @return a corresponding <tt>Author</tt> object
 238  
          */
 239  
         public Author getAuthor(String name) {
 240  16653
                 if (name == null || name.length() == 0) {
 241  0
                         name = Messages.getString("AUTHOR_UNKNOWN");
 242  
                 }
 243  
 
 244  16653
                 final String lowerCaseName = name.toLowerCase(Locale.getDefault());
 245  16653
                 final boolean bAnon = SvnConfigurationOptions.isAnonymize();
 246  16653
                 if (this.authors.containsKey(lowerCaseName)) {
 247  16572
                         return (Author) this.authors.get(lowerCaseName);
 248  
                 }
 249  
 
 250  
                 Author newAuthor;
 251  81
                 if (bAnon) {
 252  
                         // The first time a particular name is encountered, create an anonymized name.
 253  0
                         newAuthor = new Author(AuthorAnonymizingProvider.getNewName());
 254  
                 } else {
 255  81
                         newAuthor = new Author(name);
 256  
                 }
 257  
 
 258  81
                 final Properties p = ConfigurationOptions.getConfigProperties();
 259  81
                 this.authors.put(lowerCaseName, newAuthor);
 260  81
                 if (p != null && !bAnon) {
 261  81
                         newAuthor.setRealName(p.getProperty("user." + lowerCaseName + ".realName"));
 262  81
                         newAuthor.setHomePageUrl(p.getProperty("user." + lowerCaseName + ".url"));
 263  81
                         newAuthor.setImageUrl(p.getProperty("user." + lowerCaseName + ".image"));
 264  81
                         newAuthor.setEmail(p.getProperty("user." + lowerCaseName + ".email"));
 265  
                 }
 266  81
                 return newAuthor;
 267  
         }
 268  
 
 269  
         /**
 270  
          * Returns the <tt>Directory</tt> of the given filename or creates it if it does not yet exist.
 271  
          * 
 272  
          * @param filename
 273  
          *            the name and path of a file, for example "src/Main.java"
 274  
          * @return a corresponding <tt>Directory</tt> object
 275  
          */
 276  
         public Directory getDirectory(final String filename) {
 277  3231
                 final int lastSlash = filename.lastIndexOf('/');
 278  3231
                 if (lastSlash == -1) {
 279  99
                         return getDirectoryForPath("");
 280  
                 }
 281  3132
                 return getDirectoryForPath(filename.substring(0, lastSlash + 1));
 282  
         }
 283  
 
 284  
         /**
 285  
          * @param path
 286  
          *            for example "src/net/sf/statcvs/"
 287  
          * @return the <tt>Directory</tt> corresponding to <tt>statcvs</tt>
 288  
          */
 289  
         private Directory getDirectoryForPath(final String path) {
 290  3543
                 if (directories.containsKey(path)) {
 291  3231
                         return (Directory) directories.get(path);
 292  
                 }
 293  312
                 final Directory parent = getDirectoryForPath(FileUtils.getParentDirectoryPath(path));
 294  312
                 final Directory newDirectory = parent.createSubdirectory(FileUtils.getDirectoryName(path));
 295  312
                 directories.put(path, newDirectory);
 296  312
                 return newDirectory;
 297  
         }
 298  
 
 299  
         /**
 300  
          * New in StatSVN: We need to have access to FileBuilders after they have been created to populate them with version numbers later on.
 301  
          * 
 302  
          * @todo Beef up this interface to better encapsulate the data structure.
 303  
          * 
 304  
          * @return this builder's contained (@link FileBuilder)s.
 305  
          */
 306  
         public Map getFileBuilders() {
 307  17982
                 return fileBuilders;
 308  
         }
 309  
 
 310  
         /**
 311  
          * @see RepositoryFileManager#getLinesOfCode(String)
 312  
          */
 313  
         public int getLOC(final String filename) throws NoLineCountException {
 314  2769
                 if (repositoryFileManager == null) {
 315  12
                         throw new NoLineCountException("no RepositoryFileManager");
 316  
                 }
 317  
 
 318  2757
                 return repositoryFileManager.getLinesOfCode(filename);
 319  
         }
 320  
 
 321  
         public String getProjectName() {
 322  3
                 return projectName;
 323  
         }
 324  
 
 325  
         /**
 326  
          * @see RepositoryFileManager#getRevision(String)
 327  
          */
 328  
         public String getRevision(final String filename) throws IOException {
 329  2769
                 if (repositoryFileManager == null) {
 330  24
                         throw new IOException("no RepositoryFileManager");
 331  
                 }
 332  2745
                 return repositoryFileManager.getRevision(filename);
 333  
         }
 334  
 
 335  
         /**
 336  
          * Returns the {@link SymbolicName} with the given name or creates it if it does not yet exist.
 337  
          * 
 338  
          * @param name
 339  
          *            the symbolic name's name
 340  
          * @return the corresponding symbolic name object
 341  
          */
 342  
         public SymbolicName getSymbolicName(final String name, final Date date) {
 343  0
                 SymbolicName sym = (SymbolicName) symbolicNames.get(name);
 344  
 
 345  0
                 if (sym != null) {
 346  0
                         return sym;
 347  
                 } else {
 348  0
                         sym = new SymbolicName(name, date);
 349  0
                         symbolicNames.put(name, sym);
 350  
 
 351  0
                         return sym;
 352  
                 }
 353  
         }
 354  
 
 355  
         /**
 356  
          * 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
 357  
          * specified, no files will be excluded.
 358  
          * 
 359  
          * @param filename
 360  
          *            a filename
 361  
          * @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
 362  
          *         an exclude pattern, <tt>false</tt> will be returned.
 363  
          */
 364  
         public boolean matchesPatterns(final String filename) {
 365  5955
                 if (excludePattern != null && excludePattern.matches(filename)) {
 366  6
                         return false;
 367  
                 }
 368  5949
                 if (includePattern != null) {
 369  3
                         return includePattern.matches(filename);
 370  
                 }
 371  5946
                 return true;
 372  
         }
 373  
 
 374  
         /**
 375  
          * Matches a tag against the tag patterns. 
 376  
          * 
 377  
          * @param tag
 378  
          *            a tag
 379  
          * @return <tt>true</tt> if the tag matches the tag pattern.
 380  
          */
 381  
         public boolean matchesTagPatterns(final String tag) {
 382  0
                 if (tagsPattern != null) {
 383  0
                         return tagsPattern.matcher(tag).matches();
 384  
                 }
 385  0
                 return false;
 386  
         }
 387  
 
 388  
         /**
 389  
          * 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.
 390  
          * 
 391  
          * Necessary because line counts are not given in the log file and hence can only be added in a second pass.
 392  
          * 
 393  
          * @param filename
 394  
          *            the file to be updated
 395  
          * @param revisionNumber
 396  
          *            the revision number to be updated
 397  
          * @param linesAdded
 398  
          *            the lines that were added
 399  
          * @param linesRemoved
 400  
          *            the lines that were removed
 401  
          */
 402  
         public synchronized void updateRevision(final String filename, final String revisionNumber, final int linesAdded, final int linesRemoved) {
 403  11232
                 final FileBuilder fb = (FileBuilder) fileBuilders.get(filename);
 404  11232
                 if (fb != null) {
 405  11232
                         fb.updateRevision(revisionNumber, linesAdded, linesRemoved);
 406  
                 }
 407  11232
         }
 408  
 
 409  
         /**
 410  
          * return only a set of matching tag names (from a list on the command line).
 411  
          */
 412  
         private SortedSet getMatchingSymbolicNames() {
 413  48
                 final TreeSet result = new TreeSet();
 414  48
                 if (this.tagsPattern == null) {
 415  48
                         return result;
 416  
                 }
 417  0
                 for (final Iterator it = this.symbolicNames.values().iterator(); it.hasNext();) {
 418  0
                         final SymbolicName sn = (SymbolicName) it.next();
 419  0
                         if (sn.getDate() != null && this.tagsPattern.matcher(sn.getName()).matches()) {
 420  0
                                 result.add(sn);
 421  
                         }
 422  0
                 }
 423  0
                 return result;
 424  
         }
 425  
 
 426  
         private static final class AuthorAnonymizingProvider {
 427  0
                 private AuthorAnonymizingProvider() {
 428  
                         // no access
 429  0
                 }
 430  
 
 431  0
                 private static int count = 0;
 432  
 
 433  
                 static synchronized String getNewName() {
 434  0
                         return "author" + (String.valueOf(++count));
 435  
                 }
 436  
 
 437  
         }
 438  
 }