View Javadoc

    1   /**
    2    * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
    3    */
    4   package net.sourceforge.pmd.lang.java.rule.codesize;
    5
    6   import java.util.List;
    7   import java.util.Stack;
    8
    9   import net.sourceforge.pmd.lang.ast.Node;
    10  import net.sourceforge.pmd.lang.java.ast.ASTAnnotation;
    11  import net.sourceforge.pmd.lang.java.ast.ASTBlockStatement;
    12  import net.sourceforge.pmd.lang.java.ast.ASTCatchStatement;
    13  import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceBodyDeclaration;
    14  import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration;
    15  import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit;
    16  import net.sourceforge.pmd.lang.java.ast.ASTConditionalExpression;
    17  import net.sourceforge.pmd.lang.java.ast.ASTConstructorDeclaration;
    18  import net.sourceforge.pmd.lang.java.ast.ASTDoStatement;
    19  import net.sourceforge.pmd.lang.java.ast.ASTEnumDeclaration;
    20  import net.sourceforge.pmd.lang.java.ast.ASTExpression;
    21  import net.sourceforge.pmd.lang.java.ast.ASTForStatement;
    22  import net.sourceforge.pmd.lang.java.ast.ASTIfStatement;
    23  import net.sourceforge.pmd.lang.java.ast.ASTLiteral;
    24  import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration;
    25  import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclarator;
    26  import net.sourceforge.pmd.lang.java.ast.ASTName;
    27  import net.sourceforge.pmd.lang.java.ast.ASTSwitchLabel;
    28  import net.sourceforge.pmd.lang.java.ast.ASTSwitchStatement;
    29  import net.sourceforge.pmd.lang.java.ast.ASTWhileStatement;
    30  import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
    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 AbstractJavaRule {
    41
    42      public static final IntegerProperty REPORT_LEVEL_DESCRIPTOR = new IntegerProperty("reportLevel",
    43  	    "Cyclomatic Complexity reporting threshold", 1, 30, 10, 1.0f);
    44
    45      public static final BooleanProperty SHOW_CLASSES_COMPLEXITY_DESCRIPTOR = new BooleanProperty("showClassesComplexity",
    46  	"Add class average violations to the report", true, 2.0f);
    47
    48      public static final BooleanProperty SHOW_METHODS_COMPLEXITY_DESCRIPTOR = new BooleanProperty("showMethodsComplexity",
    49  	"Add method average violations to the report", true, 3.0f);
    50
    51    private int reportLevel;
    52    private boolean showClassesComplexity = true;
    53    private boolean showMethodsComplexity = true;
    54
    55    private static class Entry {
    56      private Node node;
    57      private int decisionPoints = 1;
    58      public int highestDecisionPoints;
    59      public int methodCount;
    60
    61      private Entry(Node node) {
    62        this.node = node;
    63      }
    64
    65      public void bumpDecisionPoints() {
    66        decisionPoints++;
    67      }
    68
    69      public void bumpDecisionPoints(int size) {
    70        decisionPoints += size;
    71      }
    72
    73      public int getComplexityAverage() {
    74        return (double) methodCount == 0 ? 1
    75            : (int) Math.rint( (double) decisionPoints / (double) methodCount );
    76      }
    77    }
    78
    79    private Stack<Entry> entryStack = new Stack<Entry>();
    80
    81    public CyclomaticComplexityRule() {
    82        definePropertyDescriptor(REPORT_LEVEL_DESCRIPTOR);
    83        definePropertyDescriptor(SHOW_CLASSES_COMPLEXITY_DESCRIPTOR);
    84        definePropertyDescriptor(SHOW_METHODS_COMPLEXITY_DESCRIPTOR);
    85    }
    86
    87    @Override
    88  public Object visit(ASTCompilationUnit node, Object data) {
    89      reportLevel = getProperty(REPORT_LEVEL_DESCRIPTOR);
    90      showClassesComplexity = getProperty(SHOW_CLASSES_COMPLEXITY_DESCRIPTOR);
    91      showMethodsComplexity = getProperty(SHOW_METHODS_COMPLEXITY_DESCRIPTOR);
    92      super.visit( node, data );
    93      return data;
    94    }
    95
    96    @Override
    97  public Object visit(ASTIfStatement node, Object data) {
    98      int boolCompIf = NPathComplexityRule.sumExpressionComplexity( node.getFirstChildOfType( ASTExpression.class ) );
    99      // If statement always has a complexity of at least 1
    100     boolCompIf++;
    101
    102     entryStack.peek().bumpDecisionPoints( boolCompIf );
    103     super.visit( node, data );
    104     return data;
    105   }
    106
    107   @Override
    108 public Object visit(ASTCatchStatement node, Object data) {
    109     entryStack.peek().bumpDecisionPoints();
    110     super.visit( node, data );
    111     return data;
    112   }
    113
    114   @Override
    115 public Object visit(ASTForStatement node, Object data) {
    116     int boolCompFor = NPathComplexityRule.sumExpressionComplexity( node.getFirstDescendantOfType( ASTExpression.class ) );
    117     // For statement always has a complexity of at least 1
    118     boolCompFor++;
    119
    120     entryStack.peek().bumpDecisionPoints( boolCompFor );
    121     super.visit( node, data );
    122     return data;
    123   }
    124
    125   @Override
    126 public Object visit(ASTDoStatement node, Object data) {
    127     int boolCompDo = NPathComplexityRule.sumExpressionComplexity( node.getFirstChildOfType( ASTExpression.class ) );
    128     // Do statement always has a complexity of at least 1
    129     boolCompDo++;
    130
    131     entryStack.peek().bumpDecisionPoints( boolCompDo );
    132     super.visit( node, data );
    133     return data;
    134   }
    135
    136   @Override
    137 public Object visit(ASTSwitchStatement node, Object data) {
    138     Entry entry = entryStack.peek();
    139
    140     int boolCompSwitch = NPathComplexityRule.sumExpressionComplexity( node.getFirstChildOfType( ASTExpression.class ) );
    141     entry.bumpDecisionPoints( boolCompSwitch );
    142
    143     int childCount = node.jjtGetNumChildren();
    144     int lastIndex = childCount - 1;
    145     for ( int n = 0; n < lastIndex; n++ ) {
    146       Node childNode = node.jjtGetChild( n );
    147       if ( childNode instanceof ASTSwitchLabel ) {
    148         // default is generally not considered a decision (same as "else")
    149         ASTSwitchLabel sl = (ASTSwitchLabel) childNode;
    150         if ( !sl.isDefault() ) {
    151           childNode = node.jjtGetChild( n + 1 );
    152           if ( childNode instanceof ASTBlockStatement ) {
    153             entry.bumpDecisionPoints();
    154           }
    155         }
    156       }
    157     }
    158     super.visit( node, data );
    159     return data;
    160   }
    161
    162   @Override
    163 public Object visit(ASTWhileStatement node, Object data) {
    164     int boolCompWhile = NPathComplexityRule.sumExpressionComplexity( node.getFirstChildOfType( ASTExpression.class ) );
    165     // While statement always has a complexity of at least 1
    166     boolCompWhile++;
    167
    168     entryStack.peek().bumpDecisionPoints( boolCompWhile );
    169     super.visit( node, data );
    170     return data;
    171   }
    172
    173   @Override
    174 public Object visit(ASTConditionalExpression node, Object data) {
    175     if ( node.isTernary() ) {
    176       int boolCompTern = NPathComplexityRule.sumExpressionComplexity( node.getFirstChildOfType( ASTExpression.class ) );
    177       // Ternary statement always has a complexity of at least 1
    178       boolCompTern++;
    179
    180       entryStack.peek().bumpDecisionPoints( boolCompTern );
    181       super.visit( node, data );
    182     }
    183     return data;
    184   }
    185
    186   @Override
    187 public Object visit(ASTClassOrInterfaceDeclaration node, Object data) {
    188     if ( node.isInterface() ) {
    189       return data;
    190     }
    191
    192     entryStack.push( new Entry( node ) );
    193     super.visit( node, data );
    194     if ( showClassesComplexity ) {
    195     	Entry classEntry = entryStack.pop();
    196 	    if ( classEntry.getComplexityAverage() >= reportLevel
    197 	        || classEntry.highestDecisionPoints >= reportLevel ) {
    198 	      addViolation( data, node, new String[] {
    199 	          "class",
    200 	          node.getImage(),
    201 	          classEntry.getComplexityAverage() + " (Highest = "
    202 	              + classEntry.highestDecisionPoints + ')' } );
    203 	    }
    204     }
    205     return data;
    206   }
    207
    208   @Override
    209 public Object visit(ASTMethodDeclaration node, Object data) {
    210     entryStack.push( new Entry( node ) );
    211     super.visit( node, data );
    212 	    Entry methodEntry = entryStack.pop();
    213     if (!isSuppressed(node)) {
    214 	    int methodDecisionPoints = methodEntry.decisionPoints;
    215 	    Entry classEntry = entryStack.peek();
    216 	    classEntry.methodCount++;
    217 	    classEntry.bumpDecisionPoints( methodDecisionPoints );
    218
    219 	    if ( methodDecisionPoints > classEntry.highestDecisionPoints ) {
    220 	      classEntry.highestDecisionPoints = methodDecisionPoints;
    221 	    }
    222
    223 	    ASTMethodDeclarator methodDeclarator = null;
    224 	    for ( int n = 0; n < node.jjtGetNumChildren(); n++ ) {
    225 	      Node childNode = node.jjtGetChild( n );
    226 	      if ( childNode instanceof ASTMethodDeclarator ) {
    227 	        methodDeclarator = (ASTMethodDeclarator) childNode;
    228 	        break;
    229 	      }
    230 	    }
    231
    232 	    if ( showMethodsComplexity && methodEntry.decisionPoints >= reportLevel ) {
    233 	        addViolation( data, node, new String[] { "method",
    234 	            methodDeclarator == null ? "" : methodDeclarator.getImage(),
    235 	            String.valueOf( methodEntry.decisionPoints ) } );
    236 	      }
    237     }
    238     return data;
    239   }
    240
    241   @Override
    242 public Object visit(ASTEnumDeclaration node, Object data) {
    243     entryStack.push( new Entry( node ) );
    244     super.visit( node, data );
    245     Entry classEntry = entryStack.pop();
    246     if ( classEntry.getComplexityAverage() >= reportLevel
    247         || classEntry.highestDecisionPoints >= reportLevel ) {
    248       addViolation( data, node, new String[] {
    249           "class",
    250           node.getImage(),
    251           classEntry.getComplexityAverage() + "(Highest = "
    252               + classEntry.highestDecisionPoints + ')' } );
    253     }
    254     return data;
    255   }
    256
    257   @Override
    258 public Object visit(ASTConstructorDeclaration node, Object data) {
    259     entryStack.push( new Entry( node ) );
    260     super.visit( node, data );
    261     Entry constructorEntry = entryStack.pop();
    262     if (!isSuppressed(node)) {
    263     int constructorDecisionPointCount = constructorEntry.decisionPoints;
    264     Entry classEntry = entryStack.peek();
    265     classEntry.methodCount++;
    266     classEntry.decisionPoints += constructorDecisionPointCount;
    267     if ( constructorDecisionPointCount > classEntry.highestDecisionPoints ) {
    268       classEntry.highestDecisionPoints = constructorDecisionPointCount;
    269     }
    270     if ( showMethodsComplexity && constructorEntry.decisionPoints >= reportLevel ) {
    271       addViolation( data, node, new String[] { "constructor",
    272           classEntry.node.getImage(),
    273           String.valueOf( constructorDecisionPointCount ) } );
    274     }
    275     }
    276     return data;
    277   }
    278
    279   private boolean isSuppressed(Node node) {
    280       boolean result = false;
    281
    282       ASTClassOrInterfaceBodyDeclaration parent = node.getFirstParentOfType(ASTClassOrInterfaceBodyDeclaration.class);
    283       List<ASTAnnotation> annotations = parent.findChildrenOfType(ASTAnnotation.class);
    284       for (ASTAnnotation a : annotations) {
    285           ASTName name = a.getFirstDescendantOfType(ASTName.class);
    286           if ("SuppressWarnings".equals(name.getImage())) {
    287               List<ASTLiteral> literals = a.findDescendantsOfType(ASTLiteral.class);
    288               for (ASTLiteral l : literals) {
    289                   if (l.isStringLiteral() && "\"PMD.CyclomaticComplexity\"".equals(l.getImage())) {
    290                       result = true;
    291                       break;
    292                   }
    293               }
    294           }
    295           if (result) {
    296               break;
    297           }
    298       }
    299
    300       return result;
    301   }
    302 }