Coverage Report - net.sourceforge.cobertura.reporting.ComplexityCalculator
 
Classes in this File Line Coverage Branch Coverage Complexity
ComplexityCalculator
100%
48/48
100%
14/14
2.167
ComplexityCalculator$Complexity
100%
12/12
100%
2/2
2.167
 
 1  
 /*
 2  
  * Cobertura - http://cobertura.sourceforge.net/
 3  
  *
 4  
  * Copyright (C) 2005 Mark Doliner
 5  
  * Copyright (C) 2005 Jeremy Thomerson
 6  
  * Copyright (C) 2005 Grzegorz Lukasik
 7  
  *
 8  
  * Cobertura is free software; you can redistribute it and/or modify
 9  
  * it under the terms of the GNU General Public License as published
 10  
  * by the Free Software Foundation; either version 2 of the License,
 11  
  * or (at your option) any later version.
 12  
  *
 13  
  * Cobertura is distributed in the hope that it will be useful, but
 14  
  * WITHOUT ANY WARRANTY; without even the implied warranty of
 15  
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 16  
  * General Public License for more details.
 17  
  *
 18  
  * You should have received a copy of the GNU General Public License
 19  
  * along with Cobertura; if not, write to the Free Software
 20  
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 21  
  * USA
 22  
  */
 23  
 package net.sourceforge.cobertura.reporting;
 24  
 
 25  
 import java.io.File;
 26  
 import java.io.IOException;
 27  
 import java.util.HashMap;
 28  
 import java.util.Iterator;
 29  
 import java.util.List;
 30  
 import java.util.Map;
 31  
 
 32  
 import net.sourceforge.cobertura.coveragedata.ClassData;
 33  
 import net.sourceforge.cobertura.coveragedata.PackageData;
 34  
 import net.sourceforge.cobertura.coveragedata.ProjectData;
 35  
 import net.sourceforge.cobertura.coveragedata.SourceFileData;
 36  
 import net.sourceforge.cobertura.javancss.Javancss;
 37  
 import net.sourceforge.cobertura.util.FileFinder;
 38  
 
 39  
 import org.apache.log4j.Logger;
 40  
 
 41  
 
 42  
 /**
 43  
  * Allows complexity computing for source files, packages and a whole project. Average
 44  
  * McCabe's number for methods contained in the specified entity is returned. This class
 45  
  * depends on FileFinder which is used to map source file names to existing files.
 46  
  * 
 47  
  * <p>One instance of this class should be used for the same set of source files - an 
 48  
  * object of this class can cache computed results.</p>
 49  
  * 
 50  
  * @author Grzegorz Lukasik
 51  
  */
 52  
 public class ComplexityCalculator {
 53  9
          private static final Logger logger = Logger.getLogger(ComplexityCalculator.class);
 54  
 
 55  9
         public static final Complexity ZERO_COMPLEXITY = new Complexity(0,0);
 56  
         
 57  
         // Finder used to map source file names to existing files
 58  
         private final FileFinder finder;
 59  
         
 60  
         // Contains pairs (String sourceFileName, Complexity complexity)
 61  23
         private Map sourceFileCNNCache = new HashMap();
 62  
 
 63  
         // Contains pairs (String packageName, Complexity complexity)
 64  23
         private Map packageCNNCache = new HashMap();
 65  
 
 66  
         /**
 67  
          * Creates new calculator. Passed {@link FileFinder} will be used to 
 68  
          * map source file names to existing files when needed. 
 69  
          * 
 70  
          * @param finder {@link FileFinder} that allows to find source files
 71  
          * @throws NullPointerException if finder is null
 72  
          */
 73  23
         public ComplexityCalculator( FileFinder finder) {
 74  23
                 if( finder==null)
 75  1
                         throw new NullPointerException();
 76  22
                 this.finder = finder;
 77  22
         }
 78  
         
 79  
          /**
 80  
          * Calculates the code complexity number for single source file.
 81  
          * "CCN" stands for "code complexity number."  This is
 82  
          * sometimes referred to as McCabe's number.  This method
 83  
          * calculates the average cyclomatic code complexity of all
 84  
          * methods of all classes in a given directory.  
 85  
          *
 86  
          * @param file The source file for which you want to calculate
 87  
          *        the complexity
 88  
          * @return average complexity for the specified source file 
 89  
          */
 90  
         private Complexity getAccumlatedCCNForSingleFile(File file) {
 91  38
                 Javancss javancss = new Javancss(file.getAbsolutePath());
 92  
 
 93  38
                 List methodComplexities = javancss.getMethodComplexities();
 94  38
                 if (methodComplexities.size() <= 0)
 95  2
                         return ZERO_COMPLEXITY;
 96  
 
 97  36
                 int ccnAccumulator = 0;
 98  36
                 Iterator iter = methodComplexities.iterator();
 99  106
                 while (iter.hasNext())
 100  
                 {
 101  70
                         ccnAccumulator += ((Integer)iter.next()).intValue();
 102  70
                 }
 103  
                 
 104  36
                 return new Complexity( ccnAccumulator, methodComplexities.size());
 105  
         }
 106  
 
 107  
 
 108  
         /**
 109  
          * Computes CCN for all sources contained in the project.
 110  
          * CCN for whole project is an average CCN for source files.
 111  
          * All source files for which CCN cannot be computed are ignored.
 112  
          * 
 113  
          * @param projectData project to compute CCN for
 114  
          * @throws NullPointerException if projectData is null
 115  
          * @return CCN for project or 0 if no source files were found
 116  
          */
 117  
         public double getCCNForProject( ProjectData projectData) {
 118  
                 // Sum complexity for all packages
 119  9
                 Complexity act = new Complexity();
 120  9
                 for( Iterator it = projectData.getPackages().iterator(); it.hasNext();) {
 121  10
                         PackageData packageData = (PackageData)it.next();
 122  10
                         act.add( getCCNForPackageInternal( packageData));
 123  10
                 }
 124  
 
 125  
                 // Return average CCN for source files
 126  8
                 return act.averageCCN();
 127  
         }
 128  
         
 129  
         /**
 130  
          * Computes CCN for all sources contained in the specified package.
 131  
          * All source files that cannot be mapped to existing files are ignored.
 132  
          * 
 133  
          * @param packageData package to compute CCN for
 134  
          * @throws NullPointerException if <code>packageData</code> is <code>null</code>
 135  
          * @return CCN for the specified package or 0 if no source files were found
 136  
          */
 137  
         public double getCCNForPackage(PackageData packageData) {
 138  27
                 return getCCNForPackageInternal(packageData).averageCCN();
 139  
         }
 140  
 
 141  
         private Complexity getCCNForPackageInternal(PackageData packageData) {
 142  
                 // Return CCN if computed earlier
 143  37
                 Complexity cachedCCN = (Complexity) packageCNNCache.get( packageData.getName());
 144  36
                 if( cachedCCN!=null) {
 145  15
                         return cachedCCN;
 146  
                 }
 147  
                 
 148  
                 // Compute CCN for all source files inside package
 149  21
                 Complexity act = new Complexity();
 150  21
                 for( Iterator it = packageData.getSourceFiles().iterator(); it.hasNext();) {
 151  38
                         SourceFileData sourceData = (SourceFileData)it.next();
 152  38
                         act.add( getCCNForSourceFileNameInternal( sourceData.getName()));
 153  38
                 }
 154  
                 
 155  
                 // Cache result and return it
 156  21
                 packageCNNCache.put( packageData.getName(), act);
 157  21
                 return act;
 158  
         }
 159  
 
 160  
         
 161  
         /**
 162  
          * Computes CCN for single source file.
 163  
          * 
 164  
          * @param sourceFile source file to compute CCN for
 165  
          * @throws NullPointerException if <code>sourceFile</code> is <code>null</code>
 166  
          * @return CCN for the specified source file, 0 if cannot map <code>sourceFile</code> to existing file
 167  
          */
 168  
         public double getCCNForSourceFile(SourceFileData sourceFile) {
 169  35
                 return getCCNForSourceFileNameInternal( sourceFile.getName()).averageCCN();
 170  
         }
 171  
 
 172  
         private Complexity getCCNForSourceFileNameInternal(String sourceFileName) {
 173  
                 // Return CCN if computed earlier
 174  93
                 Complexity cachedCCN = (Complexity) sourceFileCNNCache.get( sourceFileName);
 175  93
                 if( cachedCCN!=null) {
 176  43
                         return cachedCCN;
 177  
                 }
 178  
 
 179  
             // Compute CCN and cache it for further use
 180  50
                 Complexity result = ZERO_COMPLEXITY;
 181  
                 try {
 182  50
                         result = getAccumlatedCCNForSingleFile( finder.getFileForSource(sourceFileName));
 183  12
                 } catch( IOException ex) {
 184  12
                         logger.info( "Cannot find source file during CCN computation, source=["+sourceFileName+"]");
 185  38
                 }
 186  50
                 sourceFileCNNCache.put( sourceFileName, result);
 187  50
                 return result;
 188  
         }
 189  
 
 190  
         /**
 191  
          * Computes CCN for source file the specified class belongs to.
 192  
          * 
 193  
          * @param classData package to compute CCN for
 194  
          * @return CCN for source file the specified class belongs to
 195  
          * @throws NullPointerException if <code>classData</code> is <code>null</code>
 196  
          */
 197  
         public double getCCNForClass(ClassData classData) {
 198  21
                 return getCCNForSourceFileNameInternal( classData.getSourceFileName()).averageCCN();
 199  
         }
 200  
 
 201  
 
 202  
         /**
 203  
          * Represents complexity of source file, package or project. Stores the number of
 204  
          * methods inside entity and accumlated complexity for these methods.
 205  
          */
 206  
         private static class Complexity {
 207  
                 private double accumlatedCCN;
 208  
                 private int methodsNum;
 209  75
                 public Complexity(double accumlatedCCN, int methodsNum) {
 210  75
                         this.accumlatedCCN = accumlatedCCN;
 211  75
                         this.methodsNum = methodsNum;
 212  75
                 }
 213  
                 public Complexity() {
 214  30
                         this(0,0);
 215  30
                 }
 216  
                 public double averageCCN() {
 217  89
                         if( methodsNum==0) {
 218  20
                                 return 0;
 219  
                         }
 220  69
                         return accumlatedCCN/methodsNum;
 221  
                 }
 222  
                 public void add( Complexity second) {
 223  48
                         accumlatedCCN += second.accumlatedCCN;
 224  48
                         methodsNum += second.methodsNum;
 225  48
                 }
 226  
         }
 227  
 }