CyclomaticComplexityRule.java
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"130101.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 (doublemethodCount == 1
070           (intMath.rint( (doubledecisionPoints / (doublemethodCount );
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.visitnode, data );
088     return data;
089   }
090 
091   @Override
092 public Object visit(ASTIfStatement node, Object data) {
093     int boolCompIf = NPathComplexityRule.sumExpressionComplexitynode.getFirstChildOfTypeASTExpression.class ) );
094     // If statement always has a complexity of at least 1
095     boolCompIf++;
096 
097     entryStack.peek().bumpDecisionPointsboolCompIf );
098     super.visitnode, data );
099     return data;
100   }
101 
102   @Override
103 public Object visit(ASTCatchStatement node, Object data) {
104     entryStack.peek().bumpDecisionPoints();
105     super.visitnode, data );
106     return data;
107   }
108 
109   @Override
110 public Object visit(ASTForStatement node, Object data) {
111     int boolCompFor = NPathComplexityRule.sumExpressionComplexitynode.getFirstDescendantOfTypeASTExpression.class ) );
112     // For statement always has a complexity of at least 1
113     boolCompFor++;
114 
115     entryStack.peek().bumpDecisionPointsboolCompFor );
116     super.visitnode, data );
117     return data;
118   }
119 
120   @Override
121 public Object visit(ASTDoStatement node, Object data) {
122     int boolCompDo = NPathComplexityRule.sumExpressionComplexitynode.getFirstChildOfTypeASTExpression.class ) );
123     // Do statement always has a complexity of at least 1
124     boolCompDo++;
125 
126     entryStack.peek().bumpDecisionPointsboolCompDo );
127     super.visitnode, 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.sumExpressionComplexitynode.getFirstChildOfTypeASTExpression.class ) );
136     entry.bumpDecisionPointsboolCompSwitch );
137 
138     int childCount = node.jjtGetNumChildren();
139     int lastIndex = childCount - 1;
140     for int n = 0; n < lastIndex; n++ ) {
141       Node childNode = node.jjtGetChild);
142       if childNode instanceof ASTSwitchLabel ) {
143         // default is generally not considered a decision (same as "else")
144         ASTSwitchLabel sl = (ASTSwitchLabelchildNode;
145         if !sl.isDefault() ) {
146           childNode = node.jjtGetChildn + );
147           if childNode instanceof ASTBlockStatement ) {
148             entry.bumpDecisionPoints();
149           }
150         }
151       }
152     }
153     super.visitnode, data );
154     return data;
155   }
156 
157   @Override
158 public Object visit(ASTWhileStatement node, Object data) {
159     int boolCompWhile = NPathComplexityRule.sumExpressionComplexitynode.getFirstChildOfTypeASTExpression.class ) );
160     // While statement always has a complexity of at least 1
161     boolCompWhile++;
162 
163     entryStack.peek().bumpDecisionPointsboolCompWhile );
164     super.visitnode, data );
165     return data;
166   }
167 
168   @Override
169 public Object visit(ASTConditionalExpression node, Object data) {
170     if node.isTernary() ) {
171       int boolCompTern = NPathComplexityRule.sumExpressionComplexitynode.getFirstChildOfTypeASTExpression.class ) );
172       // Ternary statement always has a complexity of at least 1
173       boolCompTern++;
174 
175       entryStack.peek().bumpDecisionPointsboolCompTern );
176       super.visitnode, 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.pushnew Entrynode ) );
188     super.visitnode, data );
189     if showClassesComplexity ) {
190       Entry classEntry = entryStack.pop();
191       if classEntry.getComplexityAverage() >= reportLevel
192           || classEntry.highestDecisionPoints >= reportLevel ) {
193         addViolationdata, 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.pushnew Entrynode ) );
206     super.visitnode, data );
207     if showMethodsComplexity ) {
208       Entry methodEntry = entryStack.pop();
209       int methodDecisionPoints = methodEntry.decisionPoints;
210       Entry classEntry = entryStack.peek();
211       classEntry.methodCount++;
212       classEntry.bumpDecisionPointsmethodDecisionPoints );
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);
221         if childNode instanceof ASTMethodDeclarator ) {
222           methodDeclarator = (ASTMethodDeclaratorchildNode;
223           break;
224         }
225       }
226 
227       if methodEntry.decisionPoints >= reportLevel ) {
228           addViolationdata, node, new String[] { "method",
229               methodDeclarator == null "" : methodDeclarator.getImage(),
230               String.valueOfmethodEntry.decisionPoints ) } );
231         }
232     }
233     return data;
234   }
235 
236   @Override
237 public Object visit(ASTEnumDeclaration node, Object data) {
238     entryStack.pushnew Entrynode ) );
239     super.visitnode, data );
240     Entry classEntry = entryStack.pop();
241     if classEntry.getComplexityAverage() >= reportLevel
242         || classEntry.highestDecisionPoints >= reportLevel ) {
243       addViolationdata, 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.pushnew Entrynode ) );
255     super.visitnode, 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       addViolationdata, node, new String[] { "constructor",
266           classEntry.node.getImage(),
267           String.valueOfconstructorDecisionPointCount ) } );
268     }
269     return data;
270   }
271 
272 }