Coverage Report - net.sf.statcvs.input.Builder
 
Classes in this File Line Coverage Branch Coverage Complexity
Builder
69%
160/231
41%
83/204
2.9
 
 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: 2009/03/18 16:25:47 $
 22  
 */
 23  
 package net.sf.statcvs.input;
 24  
 
 25  
 import java.io.IOException;
 26  
 import java.util.ArrayList;
 27  
 import java.util.Date;
 28  
 import java.util.HashMap;
 29  
 import java.util.HashSet;
 30  
 import java.util.Iterator;
 31  
 import java.util.List;
 32  
 import java.util.Locale;
 33  
 import java.util.Map;
 34  
 import java.util.Properties;
 35  
 import java.util.Set;
 36  
 import java.util.SortedSet;
 37  
 import java.util.TreeSet;
 38  
 import java.util.logging.Logger;
 39  
 import java.util.regex.Pattern;
 40  
 
 41  
 import net.sf.statcvs.model.Author;
 42  
 import net.sf.statcvs.model.Directory;
 43  
 import net.sf.statcvs.model.Repository;
 44  
 import net.sf.statcvs.model.SymbolicName;
 45  
 import net.sf.statcvs.model.VersionedFile;
 46  
 import net.sf.statcvs.output.ConfigurationOptions;
 47  
 import net.sf.statcvs.util.FilePatternMatcher;
 48  
 import net.sf.statcvs.util.FileUtils;
 49  
 import net.sf.statcvs.util.StringUtils;
 50  
 
 51  
 /**
 52  
  * <p>Helps building the {@link net.sf.statcvs.model.Repository} from a CVS
 53  
  * log. The <tt>Builder</tt> is fed by some CVS history data source, for
 54  
  * example a CVS log parser. The <tt>Repository</tt> can be retrieved
 55  
  * using the {@link #createCvsContent} method.</p>
 56  
  * 
 57  
  * <p>The class also takes care of the creation of <tt>Author</tt> and 
 58  
  * </tt>Directory</tt> objects and makes sure that there's only one of these
 59  
  * for each author name and path. It also provides LOC count services.</p>
 60  
  * 
 61  
  * @author Richard Cyganiak <richard@cyganiak.de>
 62  
  * @version $Id: Builder.java,v 1.44 2009/03/18 16:25:47 benoitx Exp $
 63  32
  */
 64  0
 public class Builder implements CvsLogBuilder {
 65  16
     private static Logger logger = Logger.getLogger(Builder.class.getName());
 66  
 
 67  
     private final RepositoryFileManager repositoryFileManager;
 68  
     private final FilePatternMatcher includePattern;
 69  
     private final FilePatternMatcher excludePattern;
 70  156
     private final Pattern tagsPattern;
 71  156
 
 72  234
     private final Map authors = new HashMap();
 73  78
     private final Map directories = new HashMap();
 74  234
     private final Map symbolicNames = new HashMap();
 75  156
 
 76  78
     private final List fileBuilders = new ArrayList();
 77  234
     private final Set atticFileNames = new HashSet();
 78  156
 
 79  234
     private FileBuilder currentFileBuilder = null;
 80  78
     private Date startDate = null;
 81  234
     private String projectName = null;
 82  156
 
 83  234
     private int countRejectedByExclude = 0;
 84  234
     private int countAcceptedByExclude = 0;
 85  234
     private int countRejectedByInclude = 0;
 86  234
     private int countAcceptedByInclude = 0;
 87  234
     private boolean flagOutOfSync = false;
 88  234
     private boolean flagHasLocalCVSMetadata = false;
 89  78
     private int countFoundLocalFiles = 0;
 90  78
     private int countNotFoundLocalFiles = 0;
 91  
 
 92  
     /**
 93  
      * Creates a new <tt>Builder</tt>
 94  
      * @param repositoryFileManager the {@link RepositoryFileManager} that
 95  
      *                                                                 can be used to retrieve LOC counts for
 96  
      *                                                                 the files that this builder will create
 97  
      * @param includePattern a list of Ant-style wildcard patterns, seperated
 98  
      *                       by : or ;
 99  
      * @param excludePattern a list of Ant-style wildcard patterns, seperated
 100  
      *                       by : or ;
 101  
      * @param tagsPattern A regular expression; matching symbolic names are recorded
 102  156
      */
 103  156
     public Builder(final RepositoryFileManager repositoryFileManager, final FilePatternMatcher includePattern, final FilePatternMatcher excludePattern,
 104  234
             final Pattern tagsPattern) {
 105  234
         this.repositoryFileManager = repositoryFileManager;
 106  234
         this.includePattern = includePattern;
 107  234
         this.excludePattern = excludePattern;
 108  234
         this.tagsPattern = tagsPattern;
 109  78
         directories.put("", Directory.createRoot());
 110  78
     }
 111  
 
 112  
     /**
 113  
      * Starts building the module.
 114  
      * 
 115  
      * @param moduleName name of the module
 116  60
      */
 117  60
     public void buildModule(final String moduleName) {
 118  30
         this.projectName = moduleName;
 119  30
     }
 120  
 
 121  
     /**
 122  
      * Starts building a new file. The files are not expected to be created
 123  
      * in any particular order.
 124  
      * @param filename the file's name with path, for example "path/file.txt"
 125  
      * @param isBinary <tt>true</tt> if it's a binary file
 126  
      * @param isInAttic <tt>true</tt> if the file is dead on the main branch
 127  
      * @param revBySymnames maps revision (string) by symbolic name (string)
 128  124
      */
 129  40
     public void buildFile(final String filename, final boolean isBinary, final boolean isInAttic, final Map revBySymnames) {
 130  62
         if (currentFileBuilder != null) {
 131  144
             fileBuilders.add(currentFileBuilder);
 132  124
         }
 133  74
         currentFileBuilder = new FileBuilder(this, filename, isBinary, revBySymnames);
 134  62
         if (isInAttic) {
 135  130
             atticFileNames.add(filename);
 136  0
         }
 137  62
     }
 138  
 
 139  
     /**
 140  
      * Adds a revision to the current file. The revisions must be added in
 141  
      * CVS logfile order, that is starting with the most recent one.
 142  
      * 
 143  
      * @param data the revision
 144  156
      */
 145  156
     public void buildRevision(final RevisionData data) {
 146  222
         currentFileBuilder.addRevisionData(data);
 147  78
         if (startDate == null || startDate.compareTo(data.getDate()) > 0) {
 148  228
             startDate = data.getDate();
 149  0
         }
 150  78
     }
 151  
 
 152  
     /**
 153  
      * Returns a Repository object of all files.
 154  
      * 
 155  
      * @return Repository a Repository object
 156  84
      */
 157  76
     public Repository createCvsContent() {
 158  118
         if (currentFileBuilder != null) {
 159  38
             fileBuilders.add(currentFileBuilder);
 160  38
             currentFileBuilder = null;
 161  84
         }
 162  84
 
 163  230
         final Repository result = new Repository();
 164  146
         final Iterator it = fileBuilders.iterator();
 165  198
         while (it.hasNext()) {
 166  156
             final FileBuilder fileBuilder = (FileBuilder) it.next();
 167  72
             final VersionedFile file = fileBuilder.createFile(startDate);
 168  52
             if (file == null) {
 169  94
                 continue;
 170  52
             }
 171  42
             if (fileBuilder.hasUnexpectedLocalRevision()) {
 172  110
                 this.flagOutOfSync = true;
 173  28
             }
 174  42
             if (fileBuilder.hasLocalCVSMetadata()) {
 175  98
                 this.flagHasLocalCVSMetadata = true;
 176  40
             }
 177  82
             if (fileBuilder.hasLocalFileNotFound()) {
 178  64
                 this.countNotFoundLocalFiles++;
 179  40
                 this.flagOutOfSync = true;
 180  22
             } else if (file.getCurrentLinesOfCode() > 0) {
 181  94
                 this.countFoundLocalFiles++;
 182  84
             }
 183  126
             result.addFile(file);
 184  42
             logger.finer("adding " + file.getFilenameWithPath() + " (" + file.getRevisions().size() + " revisions)");
 185  21
         }
 186  84
 
 187  84
         // Uh oh...
 188  126
         final SortedSet revisions = result.getRevisions();
 189  126
         final List commits = new CommitListBuilder(revisions).createCommitList();
 190  126
         result.setCommits(commits);
 191  42
         result.setSymbolicNames(getMatchingSymbolicNames());
 192  42
         return result;
 193  
     }
 194  4
 
 195  0
     public String getProjectName() {
 196  2
         return projectName;
 197  
     }
 198  
 
 199  
     /**
 200  
      * Returns the <tt>Set</tt> of filenames that are "in the attic".
 201  
      * @return a <tt>Set</tt> of <tt>String</tt>s
 202  12
      */
 203  0
     public Set getAtticFileNames() {
 204  6
         return atticFileNames;
 205  
     }
 206  
 
 207  
     /**
 208  
      * @return <tt>true</tt> if there was an exclude pattern, and it rejected all files
 209  0
      */
 210  0
     public boolean allRejectedByExcludePattern() {
 211  0
         return this.countRejectedByExclude > 0 && this.countAcceptedByExclude == 0;
 212  
     }
 213  
 
 214  
     /**
 215  
      * @return <tt>true</tt> if there was an include pattern, and it rejected all files
 216  0
      */
 217  0
     public boolean allRejectedByIncludePattern() {
 218  0
         return this.countRejectedByInclude > 0 && this.countAcceptedByInclude == 0;
 219  
     }
 220  
 
 221  
     /**
 222  
      * Returns <tt>true</tt> if the local working copy is out of
 223  
      * sync with the log. The current implementation spots if
 224  
      * local files have been deleted and not yet committed, or
 225  
      * if the log file was generated before the latest commit.
 226  0
      */
 227  0
     public boolean isLogAndLocalFilesOutOfSync() {
 228  0
         return this.flagHasLocalCVSMetadata && this.flagOutOfSync;
 229  
     }
 230  
 
 231  
     /**
 232  
      * Returns <tt>true</tt> if no local copy was found for
 233  
      * the majority of files in the log. This is a strong indication
 234  
      * that the log is not for the specified local working copy. 
 235  0
      */
 236  0
     public boolean isLocalFilesNotFound() {
 237  0
         return this.countNotFoundLocalFiles > this.countFoundLocalFiles;
 238  
     }
 239  
 
 240  
     /**
 241  
      * Returns <tt>true</tt> if at least some local files have matching
 242  
      * entries in local CVS metada directories. If this is not the case,
 243  
      * then the local copy is probably just an export, not a checkout,
 244  
      * and we can't check if the log and working copy are in sync.
 245  0
      */
 246  0
     public boolean hasLocalCVSMetadata() {
 247  0
         return this.flagHasLocalCVSMetadata;
 248  
     }
 249  
 
 250  
     /**
 251  
      * returns the <tt>Author</tt> of the given name or creates it
 252  
      * if it does not yet exist. Author names are handled as case-insensitive.
 253  
      * @param name the author's name
 254  
      * @return a corresponding <tt>Author</tt> object
 255  164
      */
 256  64
     public Author getAuthor(final String name) {
 257  82
         String nameForConfig = name.toLowerCase(Locale.getDefault());
 258  182
         if (this.authors.containsKey(nameForConfig)) {
 259  132
             return (Author) this.authors.get(nameForConfig);
 260  100
         }
 261  150
         final Properties p = ConfigurationOptions.getConfigProperties();
 262  150
         Author newAuthor = new Author(name);
 263  150
         if (p != null) {
 264  150
             String replacementUser = p.getProperty("user." + nameForConfig + ".replacedBy");
 265  100
 
 266  50
             if (StringUtils.isNotEmpty(replacementUser)) {
 267  100
                 replacementUser = replacementUser.toLowerCase();
 268  0
                 if (this.authors.containsKey(replacementUser)) {
 269  0
                     return (Author) this.authors.get(replacementUser);
 270  0
                 }
 271  0
                 nameForConfig = replacementUser;
 272  0
                 newAuthor = new Author(nameForConfig);
 273  0
             }
 274  0
 
 275  50
             newAuthor.setRealName(p.getProperty("user." + nameForConfig + ".realName"));
 276  50
             newAuthor.setHomePageUrl(p.getProperty("user." + nameForConfig + ".url"));
 277  166
             newAuthor.setImageUrl(p.getProperty("user." + nameForConfig + ".image"));
 278  166
             newAuthor.setEmail(p.getProperty("user." + nameForConfig + ".email"));
 279  142
             newAuthor.setTwitterUserName(p.getProperty("user." + nameForConfig + ".twitterUsername"));
 280  50
             newAuthor.setTwitterUserId(p.getProperty("user." + nameForConfig + ".twitterUserId"));
 281  74
             String val = p.getProperty("user." + nameForConfig + ".twitterIncludeFlash");
 282  50
             if (StringUtils.isNotEmpty(val)) {
 283  0
                 newAuthor.setTwitterIncludeFlash(Boolean.valueOf(val).booleanValue());
 284  
             }
 285  50
             val = p.getProperty("user." + nameForConfig + ".twitterIncludeHtml");
 286  50
             if (StringUtils.isNotEmpty(val)) {
 287  0
                 newAuthor.setTwitterIncludeHtml(Boolean.valueOf(val).booleanValue());
 288  0
             }
 289  0
         }
 290  50
         this.authors.put(nameForConfig, newAuthor);
 291  50
         return newAuthor;
 292  0
     }
 293  
 
 294  0
     /**
 295  0
      * Returns the <tt>Directory</tt> of the given filename or creates it
 296  
      * if it does not yet exist.
 297  0
      * @param filename the name and path of a file, for example "src/Main.java"
 298  0
      * @return a corresponding <tt>Directory</tt> object
 299  
      */
 300  0
     public Directory getDirectory(final String filename) {
 301  58
         final int lastSlash = filename.lastIndexOf('/');
 302  58
         if (lastSlash == -1) {
 303  46
             return getDirectoryForPath("");
 304  
         }
 305  92
         return getDirectoryForPath(filename.substring(0, lastSlash + 1));
 306  24
     }
 307  
 
 308  56
     /**
 309  0
      * Returns the {@link SymbolicName} with the given name or creates it
 310  
      * if it does not yet exist.
 311  0
      * 
 312  
      * @param name the symbolic name's name
 313  
      * @return the corresponding symbolic name object
 314  
      */
 315  84
     public SymbolicName getSymbolicName(final String name) {
 316  44
         SymbolicName sym = (SymbolicName) symbolicNames.get(name);
 317  0
 
 318  40
         if (sym != null) {
 319  0
             return sym;
 320  
         } else {
 321  0
             sym = new SymbolicName(name);
 322  0
             symbolicNames.put(name, sym);
 323  
 
 324  0
             return sym;
 325  
         }
 326  0
     }
 327  0
 
 328  
     public int getLOC(final String filename) throws NoLineCountException {
 329  40
         if (repositoryFileManager == null) {
 330  12
             throw new NoLineCountException("no RepositoryFileManager");
 331  
         }
 332  172
         return repositoryFileManager.getLinesOfCode(filename);
 333  12
     }
 334  8
 
 335  8
     /**
 336  
      * @see RepositoryFileManager#getRevision(String)
 337  4
      */
 338  
     public String getRevision(final String filename) throws IOException {
 339  42
         if (repositoryFileManager == null) {
 340  158
             throw new IOException("no RepositoryFileManager");
 341  4
         }
 342  24
         return repositoryFileManager.getRevision(filename);
 343  0
     }
 344  0
 
 345  0
     /**
 346  0
      * Matches a filename against the include and exclude patterns. If no
 347  
      * include pattern was specified, all files will be included. If no
 348  136
      * exclude pattern was specified, no files will be excluded.
 349  
      * @param filename a filename
 350  
      * @return <tt>true</tt> if the filename matches one of the include
 351  0
      *         patterns and does not match any of the exclude patterns.
 352  0
      *         If it matches an include and an exclude pattern, <tt>false</tt>
 353  0
      *         will be returned.
 354  
      */
 355  0
     public boolean matchesPatterns(final String filename) {
 356  232
         if (excludePattern != null) {
 357  122
             if (excludePattern.matches(filename)) {
 358  4
                 this.countRejectedByExclude++;
 359  48
                 return false;
 360  44
             } else {
 361  46
                 this.countAcceptedByExclude++;
 362  44
             }
 363  
         }
 364  68
         if (includePattern != null) {
 365  2
             if (includePattern.matches(filename)) {
 366  86
                 this.countAcceptedByInclude++;
 367  84
             } else {
 368  84
                 this.countRejectedByInclude++;
 369  0
                 return false;
 370  0
             }
 371  0
         }
 372  68
         return true;
 373  0
     }
 374  
 
 375  0
     /**
 376  0
      * @param path for example "src/net/sf/statcvs/"
 377  0
      * @return the <tt>Directory</tt> corresponding to <tt>statcvs</tt>
 378  0
      */
 379  0
     private Directory getDirectoryForPath(final String path) {
 380  80
         if (directories.containsKey(path)) {
 381  58
             return (Directory) directories.get(path);
 382  0
         }
 383  22
         final Directory parent = getDirectoryForPath(FileUtils.getParentDirectoryPath(path));
 384  22
         final Directory newDirectory = parent.createSubdirectory(FileUtils.getDirectoryName(path));
 385  22
         directories.put(path, newDirectory);
 386  22
         return newDirectory;
 387  0
     }
 388  
 
 389  
     private SortedSet getMatchingSymbolicNames() {
 390  42
         final TreeSet result = new TreeSet();
 391  42
         if (this.tagsPattern == null) {
 392  42
             return result;
 393  
         }
 394  0
         for (final Iterator it = this.symbolicNames.values().iterator(); it.hasNext();) {
 395  0
             final SymbolicName sn = (SymbolicName) it.next();
 396  0
             if (sn.getDate() != null && this.tagsPattern.matcher(sn.getName()).matches()) {
 397  0
                 result.add(sn);
 398  
             }
 399  0
         }
 400  0
         return result;
 401  
     }
 402  
 }