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 java.util.Stack;
    7
    8   import net.sourceforge.pmd.AbstractJavaRule;
    9   import net.sourceforge.pmd.ast.ASTBlockStatement;
    10  import net.sourceforge.pmd.ast.ASTCatchStatement;
    11  import net.sourceforge.pmd.ast.ASTClassOrInterfaceDeclaration;
    12  import net.sourceforge.pmd.ast.ASTCompilationUnit;
    13  import net.sourceforge.pmd.ast.ASTConditionalExpression;
    14  import net.sourceforge.pmd.ast.ASTConstructorDeclaration;
    15  import net.sourceforge.pmd.ast.ASTDoStatement;
    16  import net.sourceforge.pmd.ast.ASTEnumDeclaration;
    17  import net.sourceforge.pmd.ast.ASTExpression;
    18  import net.sourceforge.pmd.ast.ASTForStatement;
    19  import net.sourceforge.pmd.ast.ASTIfStatement;
    20  import net.sourceforge.pmd.ast.ASTMethodDeclaration;
    21  import net.sourceforge.pmd.ast.ASTMethodDeclarator;
    22  import net.sourceforge.pmd.ast.ASTSwitchLabel;
    23  import net.sourceforge.pmd.ast.ASTSwitchStatement;
    24  import net.sourceforge.pmd.ast.ASTWhileStatement;
    25  import net.sourceforge.pmd.ast.Node;
    26  import net.sourceforge.pmd.ast.SimpleNode;
    27  import net.sourceforge.pmd.rules.design.NpathComplexity;
    28
    29  /**
    30   * @author Donald A. Leckie,
    31   *
    32   * @version $Revision: 5773 $, $Date: 2008-02-14 01:06:49 -0800 (Thu, 14 Feb 2008) $
    33   * @since January 14, 2003
    34   */
    35  public class CyclomaticComplexity extends AbstractJavaRule {
    36
    37    private int reportLevel;
    38    private boolean showClassesComplexity = true;
    39    private boolean showMethodsComplexity = true;
    40
    41    private static class Entry {
    42      private SimpleNode node;
    43      private int decisionPoints = 1;
    44      public int highestDecisionPoints;
    45      public int methodCount;
    46
    47      private Entry(SimpleNode node) {
    48        this.node = node;
    49      }
    50
    51      public void bumpDecisionPoints() {
    52        decisionPoints++;
    53      }
    54
    55      public void bumpDecisionPoints(int size) {
    56        decisionPoints += size;
    57      }
    58
    59      public int getComplexityAverage() {
    60        return ( (double) methodCount == 0 ) ? 1
    61            : (int) ( Math.rint( (double) decisionPoints / (double) methodCount ) );
    62      }
    63    }
    64
    65    private Stack<Entry> entryStack = new Stack<Entry>();
    66
    67    public Object visit(ASTCompilationUnit node, Object data) {
    68      reportLevel = getIntProperty("reportLevel" );
    69      showClassesComplexity = getBooleanProperty("showClassesComplexity");
    70      showMethodsComplexity = getBooleanProperty("showMethodsComplexity");
    71      super.visit( node, data );
    72      return data;
    73    }
    74
    75    public Object visit(ASTIfStatement node, Object data) {
    76      int boolCompIf = NpathComplexity.sumExpressionComplexity( node.getFirstChildOfType( ASTExpression.class ) );
    77      // If statement always has a complexity of at least 1
    78      boolCompIf++;
    79
    80      entryStack.peek().bumpDecisionPoints( boolCompIf );
    81      super.visit( node, data );
    82      return data;
    83    }
    84
    85    public Object visit(ASTCatchStatement node, Object data) {
    86      entryStack.peek().bumpDecisionPoints();
    87      super.visit( node, data );
    88      return data;
    89    }
    90
    91    public Object visit(ASTForStatement node, Object data) {
    92      int boolCompFor = NpathComplexity.sumExpressionComplexity( node.getFirstChildOfType( ASTExpression.class ) );
    93      // For statement always has a complexity of at least 1
    94      boolCompFor++;
    95
    96      entryStack.peek().bumpDecisionPoints( boolCompFor );
    97      super.visit( node, data );
    98      return data;
    99    }
    100
    101   public Object visit(ASTDoStatement node, Object data) {
    102     int boolCompDo = NpathComplexity.sumExpressionComplexity( node.getFirstChildOfType( ASTExpression.class ) );
    103     // Do statement always has a complexity of at least 1
    104     boolCompDo++;
    105
    106     entryStack.peek().bumpDecisionPoints( boolCompDo );
    107     super.visit( node, data );
    108     return data;
    109   }
    110
    111   public Object visit(ASTSwitchStatement node, Object data) {
    112     Entry entry = entryStack.peek();
    113
    114     int boolCompSwitch = NpathComplexity.sumExpressionComplexity( node.getFirstChildOfType( ASTExpression.class ) );
    115     entry.bumpDecisionPoints( boolCompSwitch );
    116
    117     int childCount = node.jjtGetNumChildren();
    118     int lastIndex = childCount - 1;
    119     for ( int n = 0; n < lastIndex; n++ ) {
    120       Node childNode = node.jjtGetChild( n );
    121       if ( childNode instanceof ASTSwitchLabel ) {
    122         // default is generally not considered a decision (same as "else")
    123         ASTSwitchLabel sl = (ASTSwitchLabel) childNode;
    124         if ( !sl.isDefault() ) {
    125           childNode = node.jjtGetChild( n + 1 );
    126           if ( childNode instanceof ASTBlockStatement ) {
    127             entry.bumpDecisionPoints();
    128           }
    129         }
    130       }
    131     }
    132     super.visit( node, data );
    133     return data;
    134   }
    135
    136   public Object visit(ASTWhileStatement node, Object data) {
    137     int boolCompWhile = NpathComplexity.sumExpressionComplexity( node.getFirstChildOfType( ASTExpression.class ) );
    138     // While statement always has a complexity of at least 1
    139     boolCompWhile++;
    140
    141     entryStack.peek().bumpDecisionPoints( boolCompWhile );
    142     super.visit( node, data );
    143     return data;
    144   }
    145
    146   public Object visit(ASTConditionalExpression node, Object data) {
    147     if ( node.isTernary() ) {
    148       int boolCompTern = NpathComplexity.sumExpressionComplexity( node.getFirstChildOfType( ASTExpression.class ) );
    149       // Ternary statement always has a complexity of at least 1
    150       boolCompTern++;
    151
    152       entryStack.peek().bumpDecisionPoints( boolCompTern );
    153       super.visit( node, data );
    154     }
    155     return data;
    156   }
    157
    158   public Object visit(ASTClassOrInterfaceDeclaration node, Object data) {
    159     if ( node.isInterface() ) {
    160       return data;
    161     }
    162
    163     entryStack.push( new Entry( node ) );
    164     super.visit( node, data );
    165     if ( showClassesComplexity ) {
    166     	Entry classEntry = entryStack.pop();
    167 	    if ( ( classEntry.getComplexityAverage() >= reportLevel )
    168 	        || ( classEntry.highestDecisionPoints >= reportLevel ) ) {
    169 	      addViolation( data, node, new String[] {
    170 	          "class",
    171 	          node.getImage(),
    172 	          classEntry.getComplexityAverage() + " (Highest = "
    173 	              + classEntry.highestDecisionPoints + ')' } );
    174 	    }
    175     }
    176     return data;
    177   }
    178
    179   public Object visit(ASTMethodDeclaration node, Object data) {
    180     entryStack.push( new Entry( node ) );
    181     super.visit( node, data );
    182     if ( showMethodsComplexity ) {
    183 	    Entry methodEntry = entryStack.pop();
    184 	    int methodDecisionPoints = methodEntry.decisionPoints;
    185 	    Entry classEntry = entryStack.peek();
    186 	    classEntry.methodCount++;
    187 	    classEntry.bumpDecisionPoints( methodDecisionPoints );
    188
    189 	    if ( methodDecisionPoints > classEntry.highestDecisionPoints ) {
    190 	      classEntry.highestDecisionPoints = methodDecisionPoints;
    191 	    }
    192
    193 	    ASTMethodDeclarator methodDeclarator = null;
    194 	    for ( int n = 0; n < node.jjtGetNumChildren(); n++ ) {
    195 	      Node childNode = node.jjtGetChild( n );
    196 	      if ( childNode instanceof ASTMethodDeclarator ) {
    197 	        methodDeclarator = (ASTMethodDeclarator) childNode;
    198 	        break;
    199 	      }
    200 	    }
    201
    202 	    if ( methodEntry.decisionPoints >= reportLevel ) {
    203 	        addViolation( data, node, new String[] { "method",
    204 	            ( methodDeclarator == null ) ? "" : methodDeclarator.getImage(),
    205 	            String.valueOf( methodEntry.decisionPoints ) } );
    206 	      }
    207     }
    208     return data;
    209   }
    210
    211   public Object visit(ASTEnumDeclaration node, Object data) {
    212     entryStack.push( new Entry( node ) );
    213     super.visit( node, data );
    214     Entry classEntry = entryStack.pop();
    215     if ( ( classEntry.getComplexityAverage() >= reportLevel )
    216         || ( classEntry.highestDecisionPoints >= reportLevel ) ) {
    217       addViolation( data, node, new String[] {
    218           "class",
    219           node.getImage(),
    220           classEntry.getComplexityAverage() + "(Highest = "
    221               + classEntry.highestDecisionPoints + ')' } );
    222     }
    223     return data;
    224   }
    225
    226   public Object visit(ASTConstructorDeclaration node, Object data) {
    227     entryStack.push( new Entry( node ) );
    228     super.visit( node, data );
    229     Entry constructorEntry = entryStack.pop();
    230     int constructorDecisionPointCount = constructorEntry.decisionPoints;
    231     Entry classEntry = entryStack.peek();
    232     classEntry.methodCount++;
    233     classEntry.decisionPoints += constructorDecisionPointCount;
    234     if ( constructorDecisionPointCount > classEntry.highestDecisionPoints ) {
    235       classEntry.highestDecisionPoints = constructorDecisionPointCount;
    236     }
    237     if ( constructorEntry.decisionPoints >= reportLevel ) {
    238       addViolation( data, node, new String[] { "constructor",
    239           classEntry.node.getImage(),
    240           String.valueOf( constructorDecisionPointCount ) } );
    241     }
    242     return data;
    243   }
    244
    245 }