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.Level;
8   import java.util.logging.Logger;
9   
10  import net.sourceforge.pmd.lang.ast.Node;
11  import net.sourceforge.pmd.lang.plsql.ast.ASTExceptionHandler;
12  import net.sourceforge.pmd.lang.plsql.ast.ASTPackageSpecification;
13  import net.sourceforge.pmd.lang.plsql.ast.ASTPackageBody;
14  import net.sourceforge.pmd.lang.plsql.ast.ASTTypeSpecification;
15  import net.sourceforge.pmd.lang.plsql.ast.ASTInput;
16  import net.sourceforge.pmd.lang.plsql.ast.ASTConditionalOrExpression;
17  import net.sourceforge.pmd.lang.plsql.ast.ASTLoopStatement;
18  import net.sourceforge.pmd.lang.plsql.ast.ASTExpression;
19  import net.sourceforge.pmd.lang.plsql.ast.ASTForStatement;
20  import net.sourceforge.pmd.lang.plsql.ast.ASTIfStatement;
21  import net.sourceforge.pmd.lang.plsql.ast.ASTElsifClause;
22  import net.sourceforge.pmd.lang.plsql.ast.ASTMethodDeclarator;
23  import net.sourceforge.pmd.lang.plsql.ast.ASTProgramUnit;
24  import net.sourceforge.pmd.lang.plsql.ast.ASTTriggerUnit;
25  import net.sourceforge.pmd.lang.plsql.ast.ASTTriggerTimingPointSection;
26  import net.sourceforge.pmd.lang.plsql.ast.ASTCaseStatement;
27  import net.sourceforge.pmd.lang.plsql.ast.ASTCaseWhenClause;
28  import net.sourceforge.pmd.lang.plsql.ast.ASTTypeMethod;
29  import net.sourceforge.pmd.lang.plsql.ast.ASTWhileStatement;
30  import net.sourceforge.pmd.lang.plsql.rule.AbstractPLSQLRule;
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<>();
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     if (LOGGER.isLoggable(Level.FINEST)) {
228     LOGGER.finest("ASTPackageBody: ComplexityAverage==" + classEntry.getComplexityAverage() 
229                    +", highestDecisionPoint=" 
230                    + classEntry.highestDecisionPoints
231                  );
232     }
233     if ( showClassesComplexity ) {
234 	    if ( classEntry.getComplexityAverage() >= reportLevel
235 	        || classEntry.highestDecisionPoints >= reportLevel ) {
236 	      addViolation( data, node, new String[] {
237 	          "class",
238 	          node.getImage(),
239 	          classEntry.getComplexityAverage() + " (Highest = "
240 	              + classEntry.highestDecisionPoints + ')' } );
241 	    }
242     }
243     LOGGER.exiting(CLASS_NAME,"visit(ASTPackageBody)");
244     return data;
245   }
246 
247   @Override
248 public Object visit(ASTTriggerUnit node, Object data) {
249     LOGGER.entering(CLASS_NAME,"visit(ASTTriggerUnit)");
250 
251     entryStack.push( new Entry( node ) );
252     super.visit( node, data );
253     Entry classEntry = entryStack.pop();
254     if (LOGGER.isLoggable(Level.FINEST)) {
255     LOGGER.finest("ASTTriggerUnit: ComplexityAverage==" + classEntry.getComplexityAverage() 
256                    +", highestDecisionPoint=" 
257                    + classEntry.highestDecisionPoints
258                  );
259     }
260     if ( showClassesComplexity ) {
261 	    if ( classEntry.getComplexityAverage() >= reportLevel
262 	        || classEntry.highestDecisionPoints >= reportLevel ) {
263 	      addViolation( data, node, new String[] {
264 	          "class",
265 	          node.getImage(),
266 	          classEntry.getComplexityAverage() + " (Highest = "
267 	              + classEntry.highestDecisionPoints + ')' } );
268 	    }
269     }
270     LOGGER.exiting(CLASS_NAME,"visit(ASTTriggerUnit)");
271     return data;
272   }
273 
274 @Override
275 public Object visit(ASTProgramUnit node, Object data) {
276     LOGGER.entering(CLASS_NAME,"visit(ASTProgramUnit)");
277     entryStack.push( new Entry( node ) );
278     super.visit( node, data );
279     Entry methodEntry = entryStack.pop();
280     if (LOGGER.isLoggable(Level.FINEST)) {
281     LOGGER.finest("ASTProgramUnit: ComplexityAverage==" + methodEntry.getComplexityAverage() 
282                    +", highestDecisionPoint=" 
283                    + methodEntry.highestDecisionPoints
284                  );
285     }
286     if ( showMethodsComplexity ) {
287 	    //Entry methodEntry = entryStack.pop();
288 	    int methodDecisionPoints = methodEntry.decisionPoints;
289             if ( 
290                     null != node.getFirstParentOfType(ASTPackageBody.class) //PackageBody (including Object Type Body)
291                     || null != node.getFirstParentOfType(ASTTriggerUnit.class) //Trigger of any form
292                     //@TODO || null != node.getFirstParentOfType(ASTProgramUnit.class) //Another Procedure
293                     //@TODO || null != node.getFirstParentOfType(ASTTypeMethod.class) //Another Type method
294                )
295             {
296               /* @TODO This does not cope with nested methods 
297                * We need the outer most 
298                * ASTPackageBody
299                * ASTTriggerUni
300                * ASTProgramUnit
301                * ASTTypeMethod
302                * 
303                */
304               Entry classEntry = entryStack.peek();
305               classEntry.methodCount++;
306               classEntry.bumpDecisionPoints( methodDecisionPoints );
307 
308               if ( methodDecisionPoints > classEntry.highestDecisionPoints ) {
309                 classEntry.highestDecisionPoints = methodDecisionPoints;
310               }
311             }
312 
313 	    ASTMethodDeclarator methodDeclarator = null;
314 	    for ( int n = 0; n < node.jjtGetNumChildren(); n++ ) {
315 	      Node childNode = node.jjtGetChild( n );
316 	      if ( childNode instanceof ASTMethodDeclarator ) {
317 	        methodDeclarator = (ASTMethodDeclarator) childNode;
318 	        break;
319 	      }
320 	    }
321 
322 	    if ( methodEntry.decisionPoints >= reportLevel ) {
323 	        addViolation( data, node, new String[] { "method",
324 	            methodDeclarator == null ? "" : methodDeclarator.getImage(),
325 	            String.valueOf( methodEntry.decisionPoints ) } );
326 	      }
327     }
328     LOGGER.exiting(CLASS_NAME,"visit(ASTProgramUnit)");
329     return data;
330   }
331 
332 @Override
333 public Object visit(ASTTypeMethod node, Object data) {
334     LOGGER.entering(CLASS_NAME,"visit(ASTTypeMethod)");
335     entryStack.push( new Entry( node ) );
336     super.visit( node, data );
337     Entry methodEntry = entryStack.pop();
338     if (LOGGER.isLoggable(Level.FINEST)) {
339     LOGGER.finest("ASTProgramUnit: ComplexityAverage==" + methodEntry.getComplexityAverage() 
340                    +", highestDecisionPoint=" 
341                    + methodEntry.highestDecisionPoints
342                  );
343     }
344     if ( showMethodsComplexity ) {
345 	    //Entry methodEntry = entryStack.pop();
346 	    int methodDecisionPoints = methodEntry.decisionPoints;
347             if ( 
348                null != node.getFirstParentOfType(ASTPackageBody.class) //PackageBody (including Object Type Body)
349                )
350             {
351               /* @TODO This does not cope with nested methods 
352                * We need the outer most 
353                * ASTPackageBody
354                */
355               Entry classEntry = entryStack.peek();
356               classEntry.methodCount++;
357               classEntry.bumpDecisionPoints( methodDecisionPoints );
358 
359               if ( methodDecisionPoints > classEntry.highestDecisionPoints ) {
360                 classEntry.highestDecisionPoints = methodDecisionPoints;
361               }
362             }
363 
364 	    ASTMethodDeclarator methodDeclarator = null;
365 	    for ( int n = 0; n < node.jjtGetNumChildren(); n++ ) {
366 	      Node childNode = node.jjtGetChild( n );
367 	      if ( childNode instanceof ASTMethodDeclarator ) {
368 	        methodDeclarator = (ASTMethodDeclarator) childNode;
369 	        break;
370 	      }
371 	    }
372 
373 	    if ( methodEntry.decisionPoints >= reportLevel ) {
374 	        addViolation( data, node, new String[] { "method",
375 	            methodDeclarator == null ? "" : methodDeclarator.getImage(),
376 	            String.valueOf( methodEntry.decisionPoints ) } );
377 	      }
378     }
379     LOGGER.exiting(CLASS_NAME,"visit(ASTTypeMethod)");
380     return data;
381   }
382 
383 
384   @Override
385 public Object visit(ASTTriggerTimingPointSection node, Object data) {
386     LOGGER.entering(CLASS_NAME,"visit(ASTTriggerTimingPointSection)");
387     entryStack.push( new Entry( node ) );
388     super.visit( node, data );
389     Entry methodEntry = entryStack.pop();
390     if (LOGGER.isLoggable(Level.FINE)) {
391     LOGGER.fine("ASTTriggerTimingPointSection: ComplexityAverage==" + methodEntry.getComplexityAverage() 
392                    +", highestDecisionPoint=" 
393                    + methodEntry.highestDecisionPoints
394                  );
395     }
396     if ( showMethodsComplexity ) {
397 	    int methodDecisionPoints = methodEntry.decisionPoints;
398 	    Entry classEntry = entryStack.peek();
399 	    classEntry.methodCount++;
400 	    classEntry.bumpDecisionPoints( methodDecisionPoints );
401 
402 	    if ( methodDecisionPoints > classEntry.highestDecisionPoints ) {
403 	      classEntry.highestDecisionPoints = methodDecisionPoints;
404 	    }
405 
406 	    ASTMethodDeclarator methodDeclarator = null;
407 	    for ( int n = 0; n < node.jjtGetNumChildren(); n++ ) {
408 	      Node childNode = node.jjtGetChild( n );
409 	      if ( childNode instanceof ASTMethodDeclarator ) {
410 	        methodDeclarator = (ASTMethodDeclarator) childNode;
411 	        break;
412 	      }
413 	    }
414 
415 	    if ( methodEntry.decisionPoints >= reportLevel ) {
416 	        addViolation( data, node, new String[] { "method",
417 	            methodDeclarator == null ? "" : methodDeclarator.getImage(),
418 	            String.valueOf( methodEntry.decisionPoints ) } );
419 	      }
420     }
421     LOGGER.exiting(CLASS_NAME,"visit(ASTTriggerTimingPointSection)");
422     return data;
423   }
424 
425 
426 }