001 /*
002 * Cobertura - http://cobertura.sourceforge.net/
003 *
004 * Copyright (C) 2005 Mark Doliner
005 * Copyright (C) 2005 Jeremy Thomerson
006 * Copyright (C) 2005 Grzegorz Lukasik
007 *
008 * Cobertura is free software; you can redistribute it and/or modify
009 * it under the terms of the GNU General Public License as published
010 * by the Free Software Foundation; either version 2 of the License,
011 * or (at your option) any later version.
012 *
013 * Cobertura is distributed in the hope that it will be useful, but
014 * WITHOUT ANY WARRANTY; without even the implied warranty of
015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
016 * General Public License for more details.
017 *
018 * You should have received a copy of the GNU General Public License
019 * along with Cobertura; if not, write to the Free Software
020 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
021 * USA
022 */
023 package net.sourceforge.cobertura.reporting;
024
025 import java.io.File;
026 import java.io.IOException;
027 import java.util.HashMap;
028 import java.util.Iterator;
029 import java.util.List;
030 import java.util.Map;
031
032 import net.sourceforge.cobertura.coveragedata.ClassData;
033 import net.sourceforge.cobertura.coveragedata.PackageData;
034 import net.sourceforge.cobertura.coveragedata.ProjectData;
035 import net.sourceforge.cobertura.coveragedata.SourceFileData;
036 import net.sourceforge.cobertura.javancss.Javancss;
037 import net.sourceforge.cobertura.util.FileFinder;
038
039 import org.apache.log4j.Logger;
040
041
042 /**
043 * Allows complexity computing for source files, packages and a whole project. Average
044 * McCabe's number for methods contained in the specified entity is returned. This class
045 * depends on FileFinder which is used to map source file names to existing files.
046 *
047 * <p>One instance of this class should be used for the same set of source files - an
048 * object of this class can cache computed results.</p>
049 *
050 * @author Grzegorz Lukasik
051 */
052 public class ComplexityCalculator {
053 private static final Logger logger = Logger.getLogger(ComplexityCalculator.class);
054
055 public static final Complexity ZERO_COMPLEXITY = new Complexity(0,0);
056
057 // Finder used to map source file names to existing files
058 private final FileFinder finder;
059
060 // Contains pairs (String sourceFileName, Complexity complexity)
061 private Map sourceFileCNNCache = new HashMap();
062
063 // Contains pairs (String packageName, Complexity complexity)
064 private Map packageCNNCache = new HashMap();
065
066 /**
067 * Creates new calculator. Passed {@link FileFinder} will be used to
068 * map source file names to existing files when needed.
069 *
070 * @param finder {@link FileFinder} that allows to find source files
071 * @throws NullPointerException if finder is null
072 */
073 public ComplexityCalculator( FileFinder finder) {
074 if( finder==null)
075 throw new NullPointerException();
076 this.finder = finder;
077 }
078
079 /**
080 * Calculates the code complexity number for single source file.
081 * "CCN" stands for "code complexity number." This is
082 * sometimes referred to as McCabe's number. This method
083 * calculates the average cyclomatic code complexity of all
084 * methods of all classes in a given directory.
085 *
086 * @param file The source file for which you want to calculate
087 * the complexity
088 * @return average complexity for the specified source file
089 */
090 private Complexity getAccumlatedCCNForSingleFile(File file) {
091 Javancss javancss = new Javancss(file.getAbsolutePath());
092
093 List methodComplexities = javancss.getMethodComplexities();
094 if (methodComplexities.size() <= 0)
095 return ZERO_COMPLEXITY;
096
097 int ccnAccumulator = 0;
098 Iterator iter = methodComplexities.iterator();
099 while (iter.hasNext())
100 {
101 ccnAccumulator += ((Integer)iter.next()).intValue();
102 }
103
104 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 Complexity act = new Complexity();
120 for( Iterator it = projectData.getPackages().iterator(); it.hasNext();) {
121 PackageData packageData = (PackageData)it.next();
122 act.add( getCCNForPackageInternal( packageData));
123 }
124
125 // Return average CCN for source files
126 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 return getCCNForPackageInternal(packageData).averageCCN();
139 }
140
141 private Complexity getCCNForPackageInternal(PackageData packageData) {
142 // Return CCN if computed earlier
143 Complexity cachedCCN = (Complexity) packageCNNCache.get( packageData.getName());
144 if( cachedCCN!=null) {
145 return cachedCCN;
146 }
147
148 // Compute CCN for all source files inside package
149 Complexity act = new Complexity();
150 for( Iterator it = packageData.getSourceFiles().iterator(); it.hasNext();) {
151 SourceFileData sourceData = (SourceFileData)it.next();
152 act.add( getCCNForSourceFileNameInternal( sourceData.getName()));
153 }
154
155 // Cache result and return it
156 packageCNNCache.put( packageData.getName(), act);
157 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 return getCCNForSourceFileNameInternal( sourceFile.getName()).averageCCN();
170 }
171
172 private Complexity getCCNForSourceFileNameInternal(String sourceFileName) {
173 // Return CCN if computed earlier
174 Complexity cachedCCN = (Complexity) sourceFileCNNCache.get( sourceFileName);
175 if( cachedCCN!=null) {
176 return cachedCCN;
177 }
178
179 // Compute CCN and cache it for further use
180 Complexity result = ZERO_COMPLEXITY;
181 try {
182 result = getAccumlatedCCNForSingleFile( finder.getFileForSource(sourceFileName));
183 } catch( IOException ex) {
184 logger.info( "Cannot find source file during CCN computation, source=["+sourceFileName+"]");
185 }
186 sourceFileCNNCache.put( sourceFileName, result);
187 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 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 public Complexity(double accumlatedCCN, int methodsNum) {
210 this.accumlatedCCN = accumlatedCCN;
211 this.methodsNum = methodsNum;
212 }
213 public Complexity() {
214 this(0,0);
215 }
216 public double averageCCN() {
217 if( methodsNum==0) {
218 return 0;
219 }
220 return accumlatedCCN/methodsNum;
221 }
222 public void add( Complexity second) {
223 accumlatedCCN += second.accumlatedCCN;
224 methodsNum += second.methodsNum;
225 }
226 }
227 }