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