View Javadoc

1   /***
2    * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3   */
4   package net.sourceforge.pmd.rules;
5   
6   import net.sourceforge.pmd.AbstractRule;
7   import net.sourceforge.pmd.RuleContext;
8   import net.sourceforge.pmd.RuleViolation;
9   import net.sourceforge.pmd.ast.ASTBlockStatement;
10  import net.sourceforge.pmd.ast.ASTConstructorDeclaration;
11  import net.sourceforge.pmd.ast.ASTForStatement;
12  import net.sourceforge.pmd.ast.ASTIfStatement;
13  import net.sourceforge.pmd.ast.ASTInterfaceDeclaration;
14  import net.sourceforge.pmd.ast.ASTMethodDeclaration;
15  import net.sourceforge.pmd.ast.ASTMethodDeclarator;
16  import net.sourceforge.pmd.ast.ASTSwitchLabel;
17  import net.sourceforge.pmd.ast.ASTSwitchStatement;
18  import net.sourceforge.pmd.ast.ASTUnmodifiedClassDeclaration;
19  import net.sourceforge.pmd.ast.ASTWhileStatement;
20  import net.sourceforge.pmd.ast.Node;
21  import net.sourceforge.pmd.ast.SimpleNode;
22  
23  import java.text.MessageFormat;
24  import java.util.Stack;
25  
26  /***
27   *
28   * @author Donald A. Leckie
29   * @since January 14, 2003
30   * @version $Revision: 1.12 $, $Date: 2004/04/15 19:04:26 $
31   */
32  public class CyclomaticComplexityRule extends AbstractRule {
33      private Stack m_entryStack = new Stack();
34  
35      /***
36       **************************************************************************
37       *
38       * @param node
39       * @param data
40       *
41       * @return
42       */
43      public Object visit(ASTIfStatement node, Object data) {
44          Entry entry = (Entry) m_entryStack.peek();
45          entry.m_decisionPoints++;
46          super.visit(node, data);
47  
48          return data;
49      }
50  
51      /***
52       **************************************************************************
53       *
54       * @param node
55       * @param data
56       *
57       * @return
58       */
59      public Object visit(ASTForStatement node, Object data) {
60          Entry entry = (Entry) m_entryStack.peek();
61          entry.m_decisionPoints++;
62          super.visit(node, data);
63  
64          return data;
65      }
66  
67      /***
68       **************************************************************************
69       *
70       * @param node
71       * @param data
72       *
73       * @return
74       */
75      public Object visit(ASTSwitchStatement node, Object data) {
76          Entry entry = (Entry) m_entryStack.peek();
77  
78          int childCount = node.jjtGetNumChildren();
79          int lastIndex = childCount - 1;
80  
81          for (int n = 0; n < lastIndex; n++) {
82              Node childNode = node.jjtGetChild(n);
83  
84              if (childNode instanceof ASTSwitchLabel) {
85                  childNode = node.jjtGetChild(n + 1);
86  
87                  if (childNode instanceof ASTBlockStatement) {
88                      entry.m_decisionPoints++;
89                  }
90              }
91          }
92  
93          super.visit(node, data);
94  
95          return data;
96      }
97  
98      /***
99       **************************************************************************
100      *
101      * @param node
102      * @param data
103      *
104      * @return
105      */
106     public Object visit(ASTWhileStatement node, Object data) {
107         Entry entry = (Entry) m_entryStack.peek();
108         entry.m_decisionPoints++;
109         super.visit(node, data);
110 
111         return data;
112     }
113 
114     /***
115      **************************************************************************
116      *
117      * @param node
118      * @param data
119      *
120      * @return
121      */
122     public Object visit(ASTUnmodifiedClassDeclaration node, Object data) {
123         m_entryStack.push(new Entry(node));
124         super.visit(node, data);
125         Entry classEntry = (Entry) m_entryStack.pop();
126         double decisionPoints = (double) classEntry.m_decisionPoints;
127         double methodCount = (double) classEntry.m_methodCount;
128         int complexityAverage = (methodCount == 0) ? 1 : (int) (Math.rint(decisionPoints / methodCount));
129 
130         if ((complexityAverage >= getIntProperty("reportLevel")) || (classEntry.m_highestDecisionPoints >= getIntProperty("reportLevel"))) {
131             // The {0} "{1}" has a cyclomatic complexity of {2}.
132             RuleContext ruleContext = (RuleContext) data;
133             String template = getMessage();
134             String className = node.getImage();
135             String complexityHighest = String.valueOf(classEntry.m_highestDecisionPoints);
136             String complexity = String.valueOf(complexityAverage) + " (Highest = " + complexityHighest + ")";
137             String[] args = {"class", className, complexity};
138             String message = MessageFormat.format(template, args);
139             int lineNumber = node.getBeginLine();
140             RuleViolation ruleViolation = createRuleViolation(ruleContext, lineNumber, message);
141             ruleContext.getReport().addRuleViolation(ruleViolation);
142         }
143 
144         return data;
145     }
146 
147     /***
148      **************************************************************************
149      *
150      * @param node
151      * @param data
152      *
153      * @return
154      */
155     public Object visit(ASTMethodDeclaration node, Object data) {
156         Node parentNode = node.jjtGetParent();
157 
158         while (parentNode != null) {
159             if (parentNode instanceof ASTInterfaceDeclaration) {
160                 return data;
161             }
162 
163             parentNode = parentNode.jjtGetParent();
164         }
165 
166         m_entryStack.push(new Entry(node));
167         super.visit(node, data);
168         Entry methodEntry = (Entry) m_entryStack.pop();
169         int methodDecisionPoints = methodEntry.m_decisionPoints;
170         Entry classEntry = (Entry) m_entryStack.peek();
171         classEntry.m_methodCount++;
172         classEntry.m_decisionPoints += methodDecisionPoints;
173 
174         if (methodDecisionPoints > classEntry.m_highestDecisionPoints) {
175             classEntry.m_highestDecisionPoints = methodDecisionPoints;
176         }
177 
178         ASTMethodDeclarator methodDeclarator = null;
179 
180         for (int n = 0; n < node.jjtGetNumChildren(); n++) {
181             Node childNode = node.jjtGetChild(n);
182 
183             if (childNode instanceof ASTMethodDeclarator) {
184                 methodDeclarator = (ASTMethodDeclarator) childNode;
185                 break;
186             }
187         }
188 
189         if (methodEntry.m_decisionPoints >= getIntProperty("reportLevel")) {
190             // The {0} "{1}" has a cyclomatic complexity of {2}.
191             RuleContext ruleContext = (RuleContext) data;
192             String template = getMessage();
193             String methodName = (methodDeclarator == null) ? "" : methodDeclarator.getImage();
194             String complexity = String.valueOf(methodEntry.m_decisionPoints);
195             String[] args = {"method", methodName, complexity};
196             String message = MessageFormat.format(template, args);
197             int lineNumber = node.getBeginLine();
198             RuleViolation ruleViolation = createRuleViolation(ruleContext, lineNumber, message);
199             ruleContext.getReport().addRuleViolation(ruleViolation);
200         }
201 
202         return data;
203     }
204 
205     /***
206      **************************************************************************
207      *
208      * @param node
209      * @param data
210      *
211      * @return
212      */
213     public Object visit(ASTConstructorDeclaration node, Object data) {
214         m_entryStack.push(new Entry(node));
215         super.visit(node, data);
216         Entry constructorEntry = (Entry) m_entryStack.pop();
217         int constructorDecisionPointCount = constructorEntry.m_decisionPoints;
218         Entry classEntry = (Entry) m_entryStack.peek();
219         classEntry.m_methodCount++;
220         classEntry.m_decisionPoints += constructorDecisionPointCount;
221 
222         if (constructorDecisionPointCount > classEntry.m_highestDecisionPoints) {
223             classEntry.m_highestDecisionPoints = constructorDecisionPointCount;
224         }
225 
226         if (constructorEntry.m_decisionPoints >= getIntProperty("reportLevel")) {
227             // The {0} "{1}" has a cyclomatic complexity of {2}.
228             RuleContext ruleContext = (RuleContext) data;
229             String template = getMessage();
230             String constructorName = classEntry.m_node.getImage();
231             String complexity = String.valueOf(constructorDecisionPointCount);
232             String[] args = {"constructor", constructorName, complexity};
233             String message = MessageFormat.format(template, args);
234             int lineNumber = node.getBeginLine();
235             RuleViolation ruleViolation = createRuleViolation(ruleContext, lineNumber, message);
236             ruleContext.getReport().addRuleViolation(ruleViolation);
237         }
238 
239         return data;
240     }
241 
242     /***
243      ***************************************************************************
244      ***************************************************************************
245      ***************************************************************************
246      */
247     private class Entry {
248         // ASTUnmodifedClassDeclaration or ASTMethodDeclarator or ASTConstructorDeclaration
249         private SimpleNode m_node;
250         public int m_decisionPoints = 1;
251         public int m_highestDecisionPoints;
252         public int m_methodCount;
253 
254         /***
255          ***********************************************************************
256          *
257          * @param node
258          */
259         private Entry(SimpleNode node) {
260             m_node = node;
261         }
262     }
263 }