View Javadoc

1   /**
2    * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3    */
4   package net.sourceforge.pmd.lang.java.rule.codesize;
5   
6   import java.util.List;
7   import java.util.Stack;
8   
9   import net.sourceforge.pmd.lang.ast.Node;
10  import net.sourceforge.pmd.lang.java.ast.ASTAnnotation;
11  import net.sourceforge.pmd.lang.java.ast.ASTBlockStatement;
12  import net.sourceforge.pmd.lang.java.ast.ASTCatchStatement;
13  import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceBodyDeclaration;
14  import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration;
15  import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit;
16  import net.sourceforge.pmd.lang.java.ast.ASTConditionalExpression;
17  import net.sourceforge.pmd.lang.java.ast.ASTConstructorDeclaration;
18  import net.sourceforge.pmd.lang.java.ast.ASTDoStatement;
19  import net.sourceforge.pmd.lang.java.ast.ASTEnumDeclaration;
20  import net.sourceforge.pmd.lang.java.ast.ASTExpression;
21  import net.sourceforge.pmd.lang.java.ast.ASTForStatement;
22  import net.sourceforge.pmd.lang.java.ast.ASTIfStatement;
23  import net.sourceforge.pmd.lang.java.ast.ASTLiteral;
24  import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration;
25  import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclarator;
26  import net.sourceforge.pmd.lang.java.ast.ASTName;
27  import net.sourceforge.pmd.lang.java.ast.ASTSwitchLabel;
28  import net.sourceforge.pmd.lang.java.ast.ASTSwitchStatement;
29  import net.sourceforge.pmd.lang.java.ast.ASTWhileStatement;
30  import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
31  import net.sourceforge.pmd.lang.rule.properties.BooleanProperty;
32  import net.sourceforge.pmd.lang.rule.properties.IntegerProperty;
33  
34  /**
35   * @author Donald A. Leckie,
36   *
37   * @version $Revision: 5956 $, $Date: 2008-04-04 04:59:25 -0500 (Fri, 04 Apr 2008) $
38   * @since January 14, 2003
39   */
40  public class CyclomaticComplexityRule extends AbstractJavaRule {
41  
42      public static final IntegerProperty REPORT_LEVEL_DESCRIPTOR = new IntegerProperty("reportLevel",
43  	    "Cyclomatic Complexity reporting threshold", 1, 30, 10, 1.0f);
44  
45      public static final BooleanProperty SHOW_CLASSES_COMPLEXITY_DESCRIPTOR = new BooleanProperty("showClassesComplexity",
46  	"Add class average violations to the report", true, 2.0f);
47  
48      public static final BooleanProperty SHOW_METHODS_COMPLEXITY_DESCRIPTOR = new BooleanProperty("showMethodsComplexity",
49  	"Add method average violations to the report", true, 3.0f);
50  
51    private int reportLevel;
52    private boolean showClassesComplexity = true;
53    private boolean showMethodsComplexity = true;
54  
55    private static class Entry {
56      private Node node;
57      private int decisionPoints = 1;
58      public int highestDecisionPoints;
59      public int methodCount;
60  
61      private Entry(Node node) {
62        this.node = node;
63      }
64  
65      public void bumpDecisionPoints() {
66        decisionPoints++;
67      }
68  
69      public void bumpDecisionPoints(int size) {
70        decisionPoints += size;
71      }
72  
73      public int getComplexityAverage() {
74        return (double) methodCount == 0 ? 1
75            : (int) Math.rint( (double) decisionPoints / (double) methodCount );
76      }
77    }
78  
79    private Stack<Entry> entryStack = new Stack<Entry>();
80  
81    public CyclomaticComplexityRule() {
82        definePropertyDescriptor(REPORT_LEVEL_DESCRIPTOR);
83        definePropertyDescriptor(SHOW_CLASSES_COMPLEXITY_DESCRIPTOR);
84        definePropertyDescriptor(SHOW_METHODS_COMPLEXITY_DESCRIPTOR);
85    }
86  
87    @Override
88  public Object visit(ASTCompilationUnit node, Object data) {
89      reportLevel = getProperty(REPORT_LEVEL_DESCRIPTOR);
90      showClassesComplexity = getProperty(SHOW_CLASSES_COMPLEXITY_DESCRIPTOR);
91      showMethodsComplexity = getProperty(SHOW_METHODS_COMPLEXITY_DESCRIPTOR);
92      super.visit( node, data );
93      return data;
94    }
95  
96    @Override
97  public Object visit(ASTIfStatement node, Object data) {
98      int boolCompIf = NPathComplexityRule.sumExpressionComplexity( node.getFirstChildOfType( ASTExpression.class ) );
99      // If statement always has a complexity of at least 1
100     boolCompIf++;
101 
102     entryStack.peek().bumpDecisionPoints( boolCompIf );
103     super.visit( node, data );
104     return data;
105   }
106 
107   @Override
108 public Object visit(ASTCatchStatement node, Object data) {
109     entryStack.peek().bumpDecisionPoints();
110     super.visit( node, data );
111     return data;
112   }
113 
114   @Override
115 public Object visit(ASTForStatement node, Object data) {
116     int boolCompFor = NPathComplexityRule.sumExpressionComplexity( node.getFirstDescendantOfType( ASTExpression.class ) );
117     // For statement always has a complexity of at least 1
118     boolCompFor++;
119 
120     entryStack.peek().bumpDecisionPoints( boolCompFor );
121     super.visit( node, data );
122     return data;
123   }
124 
125   @Override
126 public Object visit(ASTDoStatement node, Object data) {
127     int boolCompDo = NPathComplexityRule.sumExpressionComplexity( node.getFirstChildOfType( ASTExpression.class ) );
128     // Do statement always has a complexity of at least 1
129     boolCompDo++;
130 
131     entryStack.peek().bumpDecisionPoints( boolCompDo );
132     super.visit( node, data );
133     return data;
134   }
135 
136   @Override
137 public Object visit(ASTSwitchStatement node, Object data) {
138     Entry entry = entryStack.peek();
139 
140     int boolCompSwitch = NPathComplexityRule.sumExpressionComplexity( node.getFirstChildOfType( ASTExpression.class ) );
141     entry.bumpDecisionPoints( boolCompSwitch );
142 
143     int childCount = node.jjtGetNumChildren();
144     int lastIndex = childCount - 1;
145     for ( int n = 0; n < lastIndex; n++ ) {
146       Node childNode = node.jjtGetChild( n );
147       if ( childNode instanceof ASTSwitchLabel ) {
148         // default is generally not considered a decision (same as "else")
149         ASTSwitchLabel sl = (ASTSwitchLabel) childNode;
150         if ( !sl.isDefault() ) {
151           childNode = node.jjtGetChild( n + 1 );
152           if ( childNode instanceof ASTBlockStatement ) {
153             entry.bumpDecisionPoints();
154           }
155         }
156       }
157     }
158     super.visit( node, data );
159     return data;
160   }
161 
162   @Override
163 public Object visit(ASTWhileStatement node, Object data) {
164     int boolCompWhile = NPathComplexityRule.sumExpressionComplexity( node.getFirstChildOfType( ASTExpression.class ) );
165     // While statement always has a complexity of at least 1
166     boolCompWhile++;
167 
168     entryStack.peek().bumpDecisionPoints( boolCompWhile );
169     super.visit( node, data );
170     return data;
171   }
172 
173   @Override
174 public Object visit(ASTConditionalExpression node, Object data) {
175     if ( node.isTernary() ) {
176       int boolCompTern = NPathComplexityRule.sumExpressionComplexity( node.getFirstChildOfType( ASTExpression.class ) );
177       // Ternary statement always has a complexity of at least 1
178       boolCompTern++;
179 
180       entryStack.peek().bumpDecisionPoints( boolCompTern );
181       super.visit( node, data );
182     }
183     return data;
184   }
185 
186   @Override
187 public Object visit(ASTClassOrInterfaceDeclaration node, Object data) {
188     if ( node.isInterface() ) {
189       return data;
190     }
191 
192     entryStack.push( new Entry( node ) );
193     super.visit( node, data );
194     if ( showClassesComplexity ) {
195     	Entry classEntry = entryStack.pop();
196 	    if ( classEntry.getComplexityAverage() >= reportLevel
197 	        || classEntry.highestDecisionPoints >= reportLevel ) {
198 	      addViolation( data, node, new String[] {
199 	          "class",
200 	          node.getImage(),
201 	          classEntry.getComplexityAverage() + " (Highest = "
202 	              + classEntry.highestDecisionPoints + ')' } );
203 	    }
204     }
205     return data;
206   }
207 
208   @Override
209 public Object visit(ASTMethodDeclaration node, Object data) {
210     entryStack.push( new Entry( node ) );
211     super.visit( node, data );
212 	    Entry methodEntry = entryStack.pop();
213     if (!isSuppressed(node)) {
214 	    int methodDecisionPoints = methodEntry.decisionPoints;
215 	    Entry classEntry = entryStack.peek();
216 	    classEntry.methodCount++;
217 	    classEntry.bumpDecisionPoints( methodDecisionPoints );
218 
219 	    if ( methodDecisionPoints > classEntry.highestDecisionPoints ) {
220 	      classEntry.highestDecisionPoints = methodDecisionPoints;
221 	    }
222 
223 	    ASTMethodDeclarator methodDeclarator = null;
224 	    for ( int n = 0; n < node.jjtGetNumChildren(); n++ ) {
225 	      Node childNode = node.jjtGetChild( n );
226 	      if ( childNode instanceof ASTMethodDeclarator ) {
227 	        methodDeclarator = (ASTMethodDeclarator) childNode;
228 	        break;
229 	      }
230 	    }
231 
232 	    if ( showMethodsComplexity && methodEntry.decisionPoints >= reportLevel ) {
233 	        addViolation( data, node, new String[] { "method",
234 	            methodDeclarator == null ? "" : methodDeclarator.getImage(),
235 	            String.valueOf( methodEntry.decisionPoints ) } );
236 	      }
237     }
238     return data;
239   }
240 
241   @Override
242 public Object visit(ASTEnumDeclaration node, Object data) {
243     entryStack.push( new Entry( node ) );
244     super.visit( node, data );
245     Entry classEntry = entryStack.pop();
246     if ( classEntry.getComplexityAverage() >= reportLevel
247         || classEntry.highestDecisionPoints >= reportLevel ) {
248       addViolation( data, node, new String[] {
249           "class",
250           node.getImage(),
251           classEntry.getComplexityAverage() + "(Highest = "
252               + classEntry.highestDecisionPoints + ')' } );
253     }
254     return data;
255   }
256 
257   @Override
258 public Object visit(ASTConstructorDeclaration node, Object data) {
259     entryStack.push( new Entry( node ) );
260     super.visit( node, data );
261     Entry constructorEntry = entryStack.pop();
262     if (!isSuppressed(node)) {
263     int constructorDecisionPointCount = constructorEntry.decisionPoints;
264     Entry classEntry = entryStack.peek();
265     classEntry.methodCount++;
266     classEntry.decisionPoints += constructorDecisionPointCount;
267     if ( constructorDecisionPointCount > classEntry.highestDecisionPoints ) {
268       classEntry.highestDecisionPoints = constructorDecisionPointCount;
269     }
270     if ( showMethodsComplexity && constructorEntry.decisionPoints >= reportLevel ) {
271       addViolation( data, node, new String[] { "constructor",
272           classEntry.node.getImage(),
273           String.valueOf( constructorDecisionPointCount ) } );
274     }
275     }
276     return data;
277   }
278 
279   private boolean isSuppressed(Node node) {
280       boolean result = false;
281 
282       ASTClassOrInterfaceBodyDeclaration parent = node.getFirstParentOfType(ASTClassOrInterfaceBodyDeclaration.class);
283       List<ASTAnnotation> annotations = parent.findChildrenOfType(ASTAnnotation.class);
284       for (ASTAnnotation a : annotations) {
285           ASTName name = a.getFirstDescendantOfType(ASTName.class);
286           if ("SuppressWarnings".equals(name.getImage())) {
287               List<ASTLiteral> literals = a.findDescendantsOfType(ASTLiteral.class);
288               for (ASTLiteral l : literals) {
289                   if (l.isStringLiteral() && "\"PMD.CyclomaticComplexity\"".equals(l.getImage())) {
290                       result = true;
291                       break;
292                   }
293               }
294           }
295           if (result) {
296               break;
297           }
298       }
299 
300       return result;
301   }
302 }