001 /**
002 * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
003 */
004 package net.sourceforge.pmd.lang.java.rule.codesize;
005
006 import java.util.Stack;
007
008 import net.sourceforge.pmd.lang.ast.Node;
009 import net.sourceforge.pmd.lang.java.ast.ASTBlockStatement;
010 import net.sourceforge.pmd.lang.java.ast.ASTCatchStatement;
011 import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration;
012 import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit;
013 import net.sourceforge.pmd.lang.java.ast.ASTConditionalExpression;
014 import net.sourceforge.pmd.lang.java.ast.ASTConstructorDeclaration;
015 import net.sourceforge.pmd.lang.java.ast.ASTDoStatement;
016 import net.sourceforge.pmd.lang.java.ast.ASTEnumDeclaration;
017 import net.sourceforge.pmd.lang.java.ast.ASTExpression;
018 import net.sourceforge.pmd.lang.java.ast.ASTForStatement;
019 import net.sourceforge.pmd.lang.java.ast.ASTIfStatement;
020 import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration;
021 import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclarator;
022 import net.sourceforge.pmd.lang.java.ast.ASTSwitchLabel;
023 import net.sourceforge.pmd.lang.java.ast.ASTSwitchStatement;
024 import net.sourceforge.pmd.lang.java.ast.ASTWhileStatement;
025 import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
026 import net.sourceforge.pmd.lang.rule.properties.BooleanProperty;
027 import net.sourceforge.pmd.lang.rule.properties.IntegerProperty;
028
029 /**
030 * @author Donald A. Leckie,
031 *
032 * @version $Revision: 5956 $, $Date: 2008-04-04 04:59:25 -0500 (Fri, 04 Apr 2008) $
033 * @since January 14, 2003
034 */
035 public class CyclomaticComplexityRule extends AbstractJavaRule {
036
037 public static final IntegerProperty REPORT_LEVEL_DESCRIPTOR = new IntegerProperty("reportLevel",
038 "Cyclomatic Complexity reporting threshold", 1, 30, 10, 1.0f);
039
040 public static final BooleanProperty SHOW_CLASSES_COMPLEXITY_DESCRIPTOR = new BooleanProperty("showClassesComplexity",
041 "Add class average violations to the report", true, 2.0f);
042
043 public static final BooleanProperty SHOW_METHODS_COMPLEXITY_DESCRIPTOR = new BooleanProperty("showMethodsComplexity",
044 "Add method average violations to the report", true, 3.0f);
045
046 private int reportLevel;
047 private boolean showClassesComplexity = true;
048 private boolean showMethodsComplexity = true;
049
050 private static class Entry {
051 private Node node;
052 private int decisionPoints = 1;
053 public int highestDecisionPoints;
054 public int methodCount;
055
056 private Entry(Node node) {
057 this.node = node;
058 }
059
060 public void bumpDecisionPoints() {
061 decisionPoints++;
062 }
063
064 public void bumpDecisionPoints(int size) {
065 decisionPoints += size;
066 }
067
068 public int getComplexityAverage() {
069 return (double) methodCount == 0 ? 1
070 : (int) Math.rint( (double) decisionPoints / (double) methodCount );
071 }
072 }
073
074 private Stack<Entry> entryStack = new Stack<Entry>();
075
076 public CyclomaticComplexityRule() {
077 definePropertyDescriptor(REPORT_LEVEL_DESCRIPTOR);
078 definePropertyDescriptor(SHOW_CLASSES_COMPLEXITY_DESCRIPTOR);
079 definePropertyDescriptor(SHOW_METHODS_COMPLEXITY_DESCRIPTOR);
080 }
081
082 @Override
083 public Object visit(ASTCompilationUnit node, Object data) {
084 reportLevel = getProperty(REPORT_LEVEL_DESCRIPTOR);
085 showClassesComplexity = getProperty(SHOW_CLASSES_COMPLEXITY_DESCRIPTOR);
086 showMethodsComplexity = getProperty(SHOW_METHODS_COMPLEXITY_DESCRIPTOR);
087 super.visit( node, data );
088 return data;
089 }
090
091 @Override
092 public Object visit(ASTIfStatement node, Object data) {
093 int boolCompIf = NPathComplexityRule.sumExpressionComplexity( node.getFirstChildOfType( ASTExpression.class ) );
094 // If statement always has a complexity of at least 1
095 boolCompIf++;
096
097 entryStack.peek().bumpDecisionPoints( boolCompIf );
098 super.visit( node, data );
099 return data;
100 }
101
102 @Override
103 public Object visit(ASTCatchStatement node, Object data) {
104 entryStack.peek().bumpDecisionPoints();
105 super.visit( node, data );
106 return data;
107 }
108
109 @Override
110 public Object visit(ASTForStatement node, Object data) {
111 int boolCompFor = NPathComplexityRule.sumExpressionComplexity( node.getFirstDescendantOfType( ASTExpression.class ) );
112 // For statement always has a complexity of at least 1
113 boolCompFor++;
114
115 entryStack.peek().bumpDecisionPoints( boolCompFor );
116 super.visit( node, data );
117 return data;
118 }
119
120 @Override
121 public Object visit(ASTDoStatement node, Object data) {
122 int boolCompDo = NPathComplexityRule.sumExpressionComplexity( node.getFirstChildOfType( ASTExpression.class ) );
123 // Do statement always has a complexity of at least 1
124 boolCompDo++;
125
126 entryStack.peek().bumpDecisionPoints( boolCompDo );
127 super.visit( node, data );
128 return data;
129 }
130
131 @Override
132 public Object visit(ASTSwitchStatement node, Object data) {
133 Entry entry = entryStack.peek();
134
135 int boolCompSwitch = NPathComplexityRule.sumExpressionComplexity( node.getFirstChildOfType( ASTExpression.class ) );
136 entry.bumpDecisionPoints( boolCompSwitch );
137
138 int childCount = node.jjtGetNumChildren();
139 int lastIndex = childCount - 1;
140 for ( int n = 0; n < lastIndex; n++ ) {
141 Node childNode = node.jjtGetChild( n );
142 if ( childNode instanceof ASTSwitchLabel ) {
143 // default is generally not considered a decision (same as "else")
144 ASTSwitchLabel sl = (ASTSwitchLabel) childNode;
145 if ( !sl.isDefault() ) {
146 childNode = node.jjtGetChild( n + 1 );
147 if ( childNode instanceof ASTBlockStatement ) {
148 entry.bumpDecisionPoints();
149 }
150 }
151 }
152 }
153 super.visit( node, data );
154 return data;
155 }
156
157 @Override
158 public Object visit(ASTWhileStatement node, Object data) {
159 int boolCompWhile = NPathComplexityRule.sumExpressionComplexity( node.getFirstChildOfType( ASTExpression.class ) );
160 // While statement always has a complexity of at least 1
161 boolCompWhile++;
162
163 entryStack.peek().bumpDecisionPoints( boolCompWhile );
164 super.visit( node, data );
165 return data;
166 }
167
168 @Override
169 public Object visit(ASTConditionalExpression node, Object data) {
170 if ( node.isTernary() ) {
171 int boolCompTern = NPathComplexityRule.sumExpressionComplexity( node.getFirstChildOfType( ASTExpression.class ) );
172 // Ternary statement always has a complexity of at least 1
173 boolCompTern++;
174
175 entryStack.peek().bumpDecisionPoints( boolCompTern );
176 super.visit( node, data );
177 }
178 return data;
179 }
180
181 @Override
182 public Object visit(ASTClassOrInterfaceDeclaration node, Object data) {
183 if ( node.isInterface() ) {
184 return data;
185 }
186
187 entryStack.push( new Entry( node ) );
188 super.visit( node, data );
189 if ( showClassesComplexity ) {
190 Entry classEntry = entryStack.pop();
191 if ( classEntry.getComplexityAverage() >= reportLevel
192 || classEntry.highestDecisionPoints >= reportLevel ) {
193 addViolation( data, node, new String[] {
194 "class",
195 node.getImage(),
196 classEntry.getComplexityAverage() + " (Highest = "
197 + classEntry.highestDecisionPoints + ')' } );
198 }
199 }
200 return data;
201 }
202
203 @Override
204 public Object visit(ASTMethodDeclaration node, Object data) {
205 entryStack.push( new Entry( node ) );
206 super.visit( node, data );
207 if ( showMethodsComplexity ) {
208 Entry methodEntry = entryStack.pop();
209 int methodDecisionPoints = methodEntry.decisionPoints;
210 Entry classEntry = entryStack.peek();
211 classEntry.methodCount++;
212 classEntry.bumpDecisionPoints( methodDecisionPoints );
213
214 if ( methodDecisionPoints > classEntry.highestDecisionPoints ) {
215 classEntry.highestDecisionPoints = methodDecisionPoints;
216 }
217
218 ASTMethodDeclarator methodDeclarator = null;
219 for ( int n = 0; n < node.jjtGetNumChildren(); n++ ) {
220 Node childNode = node.jjtGetChild( n );
221 if ( childNode instanceof ASTMethodDeclarator ) {
222 methodDeclarator = (ASTMethodDeclarator) childNode;
223 break;
224 }
225 }
226
227 if ( methodEntry.decisionPoints >= reportLevel ) {
228 addViolation( data, node, new String[] { "method",
229 methodDeclarator == null ? "" : methodDeclarator.getImage(),
230 String.valueOf( methodEntry.decisionPoints ) } );
231 }
232 }
233 return data;
234 }
235
236 @Override
237 public Object visit(ASTEnumDeclaration node, Object data) {
238 entryStack.push( new Entry( node ) );
239 super.visit( node, data );
240 Entry classEntry = entryStack.pop();
241 if ( classEntry.getComplexityAverage() >= reportLevel
242 || classEntry.highestDecisionPoints >= reportLevel ) {
243 addViolation( data, node, new String[] {
244 "class",
245 node.getImage(),
246 classEntry.getComplexityAverage() + "(Highest = "
247 + classEntry.highestDecisionPoints + ')' } );
248 }
249 return data;
250 }
251
252 @Override
253 public Object visit(ASTConstructorDeclaration node, Object data) {
254 entryStack.push( new Entry( node ) );
255 super.visit( node, data );
256 Entry constructorEntry = entryStack.pop();
257 int constructorDecisionPointCount = constructorEntry.decisionPoints;
258 Entry classEntry = entryStack.peek();
259 classEntry.methodCount++;
260 classEntry.decisionPoints += constructorDecisionPointCount;
261 if ( constructorDecisionPointCount > classEntry.highestDecisionPoints ) {
262 classEntry.highestDecisionPoints = constructorDecisionPointCount;
263 }
264 if ( constructorEntry.decisionPoints >= reportLevel ) {
265 addViolation( data, node, new String[] { "constructor",
266 classEntry.node.getImage(),
267 String.valueOf( constructorDecisionPointCount ) } );
268 }
269 return data;
270 }
271
272 }
|