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 }