View Javadoc

1   /**
2    * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3    */
4   package net.sourceforge.pmd.lang.plsql.rule.codesize;
5   
6   import java.util.Stack;
7   import java.util.logging.Logger;
8   
9   import net.sourceforge.pmd.lang.ast.Node;
10  import net.sourceforge.pmd.lang.plsql.ast.ASTExceptionHandler;
11  import net.sourceforge.pmd.lang.plsql.ast.ASTPackageSpecification;
12  import net.sourceforge.pmd.lang.plsql.ast.ASTPackageBody;
13  import net.sourceforge.pmd.lang.plsql.ast.ASTTypeSpecification;
14  import net.sourceforge.pmd.lang.plsql.ast.ASTInput;
15  import net.sourceforge.pmd.lang.plsql.ast.ASTConditionalOrExpression;
16  import net.sourceforge.pmd.lang.plsql.ast.ASTLoopStatement;
17  import net.sourceforge.pmd.lang.plsql.ast.ASTExpression;
18  import net.sourceforge.pmd.lang.plsql.ast.ASTForStatement;
19  import net.sourceforge.pmd.lang.plsql.ast.ASTIfStatement;
20  import net.sourceforge.pmd.lang.plsql.ast.ASTElsifClause;
21  import net.sourceforge.pmd.lang.plsql.ast.ASTMethodDeclarator;
22  import net.sourceforge.pmd.lang.plsql.ast.ASTProgramUnit;
23  import net.sourceforge.pmd.lang.plsql.ast.ASTTriggerUnit;
24  import net.sourceforge.pmd.lang.plsql.ast.ASTTriggerTimingPointSection;
25  import net.sourceforge.pmd.lang.plsql.ast.ASTCaseStatement;
26  import net.sourceforge.pmd.lang.plsql.ast.ASTCaseWhenClause;
27  import net.sourceforge.pmd.lang.plsql.ast.ASTTypeMethod;
28  import net.sourceforge.pmd.lang.plsql.ast.ASTWhileStatement;
29  import net.sourceforge.pmd.lang.plsql.rule.AbstractPLSQLRule;
30  
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 AbstractPLSQLRule {
41      private final static Logger LOGGER = Logger.getLogger(CyclomaticComplexityRule.class.getName()); 
42      private final static String CLASS_NAME =CyclomaticComplexityRule.class.getName(); 
43  
44      public static final IntegerProperty REPORT_LEVEL_DESCRIPTOR = new IntegerProperty("reportLevel",
45  	    "Cyclomatic Complexity reporting threshold", 1, 30, 10, 1.0f);
46  
47      public static final BooleanProperty SHOW_CLASSES_COMPLEXITY_DESCRIPTOR = new BooleanProperty("showClassesComplexity",
48  	"Add class average violations to the report", true, 2.0f);
49  
50      public static final BooleanProperty SHOW_METHODS_COMPLEXITY_DESCRIPTOR = new BooleanProperty("showMethodsComplexity",
51  	"Add method average violations to the report", true, 3.0f);
52  
53    private int reportLevel;
54    private boolean showClassesComplexity = true;
55    private boolean showMethodsComplexity = true;
56  
57    private static class Entry {
58      private Node node;
59      private int decisionPoints = 1;
60      public int highestDecisionPoints;
61      public int methodCount;
62  
63      private Entry(Node node) {
64        this.node = node;
65      }
66  
67      public void bumpDecisionPoints() {
68        decisionPoints++;
69      }
70  
71      public void bumpDecisionPoints(int size) {
72        decisionPoints += size;
73      }
74  
75      public int getComplexityAverage() {
76        return (double) methodCount == 0 ? 1
77            : (int) Math.rint( (double) decisionPoints / (double) methodCount );
78      }
79    }
80  
81    private Stack<Entry> entryStack = new Stack<Entry>();
82  
83    public CyclomaticComplexityRule() {
84        definePropertyDescriptor(REPORT_LEVEL_DESCRIPTOR);
85        definePropertyDescriptor(SHOW_CLASSES_COMPLEXITY_DESCRIPTOR);
86        definePropertyDescriptor(SHOW_METHODS_COMPLEXITY_DESCRIPTOR);
87    }
88  
89    @Override
90  public Object visit(ASTInput node, Object data) {
91      LOGGER.entering(CLASS_NAME,"visit(ASTInput)");
92      reportLevel = getProperty(REPORT_LEVEL_DESCRIPTOR);
93      showClassesComplexity = getProperty(SHOW_CLASSES_COMPLEXITY_DESCRIPTOR);
94      showMethodsComplexity = getProperty(SHOW_METHODS_COMPLEXITY_DESCRIPTOR);
95      super.visit( node, data );
96      LOGGER.exiting(CLASS_NAME,"visit(ASTInput)");
97      return data;
98    }
99  
100 
101   @Override
102 public Object visit(ASTElsifClause node, Object data) {
103     LOGGER.entering(CLASS_NAME,"visit(ASTElsifClause)");
104     int boolCompIf = NPathComplexityRule.sumExpressionComplexity( node.getFirstChildOfType( ASTExpression.class ) );
105     // If statement always has a complexity of at least 1
106     boolCompIf++;
107 
108     entryStack.peek().bumpDecisionPoints( boolCompIf );
109     super.visit( node, data );
110     LOGGER.exiting(CLASS_NAME,"visit(ASTElsifClause)");
111     return data;
112   }
113 
114   @Override
115 public Object visit(ASTIfStatement node, Object data) {
116     LOGGER.entering(CLASS_NAME,"visit(ASTIfClause)");
117     int boolCompIf = NPathComplexityRule.sumExpressionComplexity( node.getFirstChildOfType( ASTExpression.class ) );
118     // If statement always has a complexity of at least 1
119     boolCompIf++;
120 
121     entryStack.peek().bumpDecisionPoints( boolCompIf );
122     LOGGER.exiting(CLASS_NAME,"visit(ASTIfClause)");
123     super.visit( node, data );
124     return data;
125   }
126 
127   @Override
128 public Object visit(ASTExceptionHandler node, Object data) {
129     LOGGER.entering(CLASS_NAME,"visit(ASTExceptionHandler)");
130     entryStack.peek().bumpDecisionPoints();
131     LOGGER.exiting(CLASS_NAME,"visit(ASTExceptionHandler)");
132     super.visit( node, data );
133     return data;
134   }
135 
136   @Override
137 public Object visit(ASTForStatement node, Object data) {
138     LOGGER.entering(CLASS_NAME,"visit(ASTForStatement)");
139     int boolCompFor = NPathComplexityRule.sumExpressionComplexity( node.getFirstDescendantOfType( ASTExpression.class ) );
140     // For statement always has a complexity of at least 1
141     boolCompFor++;
142 
143     entryStack.peek().bumpDecisionPoints( boolCompFor );
144     super.visit( node, data );
145     LOGGER.exiting(CLASS_NAME,"visit(ASTForStatement)");
146     return data;
147   }
148 
149   @Override
150 public Object visit(ASTLoopStatement node, Object data) {
151     LOGGER.entering(CLASS_NAME,"visit(ASTLoopStatement)");
152     int boolCompDo = NPathComplexityRule.sumExpressionComplexity( node.getFirstChildOfType( ASTExpression.class ) );
153     // Do statement always has a complexity of at least 1
154     boolCompDo++;
155 
156     entryStack.peek().bumpDecisionPoints( boolCompDo );
157     super.visit( node, data );
158     LOGGER.exiting(CLASS_NAME,"visit(ASTLoopStatement)");
159     return data;
160   }
161 
162   @Override
163 public Object visit(ASTCaseStatement node, Object data) {
164     LOGGER.entering(CLASS_NAME,"visit(ASTCaseStatement)");
165     Entry entry = entryStack.peek();
166 
167     int boolCompSwitch = NPathComplexityRule.sumExpressionComplexity( node.getFirstChildOfType( ASTExpression.class ) );
168     entry.bumpDecisionPoints( boolCompSwitch );
169 
170     super.visit( node, data );
171     LOGGER.exiting(CLASS_NAME,"visit(ASTCaseStatement)");
172     return data;
173   }
174 
175   @Override
176 public Object visit(ASTCaseWhenClause node, Object data) {
177     LOGGER.entering(CLASS_NAME,"visit(ASTCaseWhenClause)");
178     Entry entry = entryStack.peek();
179 
180     entry.bumpDecisionPoints();
181     super.visit( node, data );
182     LOGGER.exiting(CLASS_NAME,"visit(ASTCaseWhenClause)");
183     return data;
184   }
185 
186 @Override
187 public Object visit(ASTWhileStatement node, Object data) {
188     LOGGER.entering(CLASS_NAME,"visit(ASTWhileStatement)");
189     int boolCompWhile = NPathComplexityRule.sumExpressionComplexity( node.getFirstChildOfType( ASTExpression.class ) );
190     // While statement always has a complexity of at least 1
191     boolCompWhile++;
192 
193     entryStack.peek().bumpDecisionPoints( boolCompWhile );
194     super.visit( node, data );
195     LOGGER.exiting(CLASS_NAME,"visit(ASTWhileStatement)");
196     return data;
197   }
198 
199   @Override
200 public Object visit(ASTConditionalOrExpression node, Object data) {
201     return data;
202   }
203 
204   @Override
205 public Object visit(ASTPackageSpecification node, Object data) {
206     LOGGER.entering(CLASS_NAME,"visit(ASTPackageSpecification)");
207     //Treat Package Specification like an Interface
208     LOGGER.exiting(CLASS_NAME,"visit(ASTPackageSpecification)");
209     return data;
210   }
211 
212   @Override
213 public Object visit(ASTTypeSpecification node, Object data) {
214     LOGGER.entering(CLASS_NAME,"visit(ASTTypeSpecification)");
215     //Treat Type Specification like an Interface
216     LOGGER.exiting(CLASS_NAME,"visit(ASTTypeSpecification)");
217     return data;
218   }
219 
220   @Override
221 public Object visit(ASTPackageBody node, Object data) {
222     LOGGER.entering(CLASS_NAME,"visit(ASTPackageBody)");
223 
224     entryStack.push( new Entry( node ) );
225     super.visit( node, data );
226     Entry classEntry = entryStack.pop();
227     LOGGER.finest("ASTPackageBody: ComplexityAverage==" + classEntry.getComplexityAverage() 
228                    +", highestDecisionPoint=" 
229                    + classEntry.highestDecisionPoints
230                  );
231     if ( showClassesComplexity ) {
232 	    if ( classEntry.getComplexityAverage() >= reportLevel
233 	        || classEntry.highestDecisionPoints >= reportLevel ) {
234 	      addViolation( data, node, new String[] {
235 	          "class",
236 	          node.getImage(),
237 	          classEntry.getComplexityAverage() + " (Highest = "
238 	              + classEntry.highestDecisionPoints + ')' } );
239 	    }
240     }
241     LOGGER.exiting(CLASS_NAME,"visit(ASTPackageBody)");
242     return data;
243   }
244 
245   @Override
246 public Object visit(ASTTriggerUnit node, Object data) {
247     LOGGER.entering(CLASS_NAME,"visit(ASTTriggerUnit)");
248 
249     entryStack.push( new Entry( node ) );
250     super.visit( node, data );
251     Entry classEntry = entryStack.pop();
252     LOGGER.finest("ASTTriggerUnit: ComplexityAverage==" + classEntry.getComplexityAverage() 
253                    +", highestDecisionPoint=" 
254                    + classEntry.highestDecisionPoints
255                  );
256     if ( showClassesComplexity ) {
257 	    if ( classEntry.getComplexityAverage() >= reportLevel
258 	        || classEntry.highestDecisionPoints >= reportLevel ) {
259 	      addViolation( data, node, new String[] {
260 	          "class",
261 	          node.getImage(),
262 	          classEntry.getComplexityAverage() + " (Highest = "
263 	              + classEntry.highestDecisionPoints + ')' } );
264 	    }
265     }
266     LOGGER.exiting(CLASS_NAME,"visit(ASTTriggerUnit)");
267     return data;
268   }
269 
270 @Override
271 public Object visit(ASTProgramUnit node, Object data) {
272     LOGGER.entering(CLASS_NAME,"visit(ASTProgramUnit)");
273     entryStack.push( new Entry( node ) );
274     super.visit( node, data );
275     Entry methodEntry = entryStack.pop();
276     LOGGER.finest("ASTProgramUnit: ComplexityAverage==" + methodEntry.getComplexityAverage() 
277                    +", highestDecisionPoint=" 
278                    + methodEntry.highestDecisionPoints
279                  );
280     if ( showMethodsComplexity ) {
281 	    //Entry methodEntry = entryStack.pop();
282 	    int methodDecisionPoints = methodEntry.decisionPoints;
283             if ( 
284                     null != node.getFirstParentOfType(ASTPackageBody.class) //PackageBody (including Object Type Body)
285                     || null != node.getFirstParentOfType(ASTTriggerUnit.class) //Trigger of any form
286                     //@TODO || null != node.getFirstParentOfType(ASTProgramUnit.class) //Another Procedure
287                     //@TODO || null != node.getFirstParentOfType(ASTTypeMethod.class) //Another Type method
288                )
289             {
290               /* @TODO This does not cope with nested methods 
291                * We need the outer most 
292                * ASTPackageBody
293                * ASTTriggerUni
294                * ASTProgramUnit
295                * ASTTypeMethod
296                * 
297                */
298               Entry classEntry = entryStack.peek();
299               classEntry.methodCount++;
300               classEntry.bumpDecisionPoints( methodDecisionPoints );
301 
302               if ( methodDecisionPoints > classEntry.highestDecisionPoints ) {
303                 classEntry.highestDecisionPoints = methodDecisionPoints;
304               }
305             }
306 
307 	    ASTMethodDeclarator methodDeclarator = null;
308 	    for ( int n = 0; n < node.jjtGetNumChildren(); n++ ) {
309 	      Node childNode = node.jjtGetChild( n );
310 	      if ( childNode instanceof ASTMethodDeclarator ) {
311 	        methodDeclarator = (ASTMethodDeclarator) childNode;
312 	        break;
313 	      }
314 	    }
315 
316 	    if ( methodEntry.decisionPoints >= reportLevel ) {
317 	        addViolation( data, node, new String[] { "method",
318 	            methodDeclarator == null ? "" : methodDeclarator.getImage(),
319 	            String.valueOf( methodEntry.decisionPoints ) } );
320 	      }
321     }
322     LOGGER.exiting(CLASS_NAME,"visit(ASTProgramUnit)");
323     return data;
324   }
325 
326 @Override
327 public Object visit(ASTTypeMethod node, Object data) {
328     LOGGER.entering(CLASS_NAME,"visit(ASTTypeMethod)");
329     entryStack.push( new Entry( node ) );
330     super.visit( node, data );
331     Entry methodEntry = entryStack.pop();
332     LOGGER.finest("ASTProgramUnit: ComplexityAverage==" + methodEntry.getComplexityAverage() 
333                    +", highestDecisionPoint=" 
334                    + methodEntry.highestDecisionPoints
335                  );
336     if ( showMethodsComplexity ) {
337 	    //Entry methodEntry = entryStack.pop();
338 	    int methodDecisionPoints = methodEntry.decisionPoints;
339             if ( 
340                null != node.getFirstParentOfType(ASTPackageBody.class) //PackageBody (including Object Type Body)
341                )
342             {
343               /* @TODO This does not cope with nested methods 
344                * We need the outer most 
345                * ASTPackageBody
346                */
347               Entry classEntry = entryStack.peek();
348               classEntry.methodCount++;
349               classEntry.bumpDecisionPoints( methodDecisionPoints );
350 
351               if ( methodDecisionPoints > classEntry.highestDecisionPoints ) {
352                 classEntry.highestDecisionPoints = methodDecisionPoints;
353               }
354             }
355 
356 	    ASTMethodDeclarator methodDeclarator = null;
357 	    for ( int n = 0; n < node.jjtGetNumChildren(); n++ ) {
358 	      Node childNode = node.jjtGetChild( n );
359 	      if ( childNode instanceof ASTMethodDeclarator ) {
360 	        methodDeclarator = (ASTMethodDeclarator) childNode;
361 	        break;
362 	      }
363 	    }
364 
365 	    if ( methodEntry.decisionPoints >= reportLevel ) {
366 	        addViolation( data, node, new String[] { "method",
367 	            methodDeclarator == null ? "" : methodDeclarator.getImage(),
368 	            String.valueOf( methodEntry.decisionPoints ) } );
369 	      }
370     }
371     LOGGER.exiting(CLASS_NAME,"visit(ASTTypeMethod)");
372     return data;
373   }
374 
375 
376   @Override
377 public Object visit(ASTTriggerTimingPointSection node, Object data) {
378     LOGGER.entering(CLASS_NAME,"visit(ASTTriggerTimingPointSection)");
379     entryStack.push( new Entry( node ) );
380     super.visit( node, data );
381     Entry methodEntry = entryStack.pop();
382     LOGGER.fine("ASTTriggerTimingPointSection: ComplexityAverage==" + methodEntry.getComplexityAverage() 
383                    +", highestDecisionPoint=" 
384                    + methodEntry.highestDecisionPoints
385                  );
386     if ( showMethodsComplexity ) {
387 	    int methodDecisionPoints = methodEntry.decisionPoints;
388 	    Entry classEntry = entryStack.peek();
389 	    classEntry.methodCount++;
390 	    classEntry.bumpDecisionPoints( methodDecisionPoints );
391 
392 	    if ( methodDecisionPoints > classEntry.highestDecisionPoints ) {
393 	      classEntry.highestDecisionPoints = methodDecisionPoints;
394 	    }
395 
396 	    ASTMethodDeclarator methodDeclarator = null;
397 	    for ( int n = 0; n < node.jjtGetNumChildren(); n++ ) {
398 	      Node childNode = node.jjtGetChild( n );
399 	      if ( childNode instanceof ASTMethodDeclarator ) {
400 	        methodDeclarator = (ASTMethodDeclarator) childNode;
401 	        break;
402 	      }
403 	    }
404 
405 	    if ( methodEntry.decisionPoints >= reportLevel ) {
406 	        addViolation( data, node, new String[] { "method",
407 	            methodDeclarator == null ? "" : methodDeclarator.getImage(),
408 	            String.valueOf( methodEntry.decisionPoints ) } );
409 	      }
410     }
411     LOGGER.exiting(CLASS_NAME,"visit(ASTTriggerTimingPointSection)");
412     return data;
413   }
414 
415 
416 }