Coverage Report - com.puppycrawl.tools.checkstyle.checks.metrics.BooleanExpressionComplexityCheck
 
Classes in this File Line Coverage Branch Coverage Complexity
BooleanExpressionComplexityCheck
100%
45/45
100%
23/23
2.059
BooleanExpressionComplexityCheck$Context
100%
12/12
100%
4/4
2.059
 
 1  
 ////////////////////////////////////////////////////////////////////////////////
 2  
 // checkstyle: Checks Java source code for adherence to a set of rules.
 3  
 // Copyright (C) 2001-2017 the original author or authors.
 4  
 //
 5  
 // This library is free software; you can redistribute it and/or
 6  
 // modify it under the terms of the GNU Lesser General Public
 7  
 // License as published by the Free Software Foundation; either
 8  
 // version 2.1 of the License, or (at your option) any later version.
 9  
 //
 10  
 // This library is distributed in the hope that it will be useful,
 11  
 // but WITHOUT ANY WARRANTY; without even the implied warranty of
 12  
 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 13  
 // Lesser General Public License for more details.
 14  
 //
 15  
 // You should have received a copy of the GNU Lesser General Public
 16  
 // License along with this library; if not, write to the Free Software
 17  
 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 18  
 ////////////////////////////////////////////////////////////////////////////////
 19  
 
 20  
 package com.puppycrawl.tools.checkstyle.checks.metrics;
 21  
 
 22  
 import java.util.ArrayDeque;
 23  
 import java.util.Deque;
 24  
 
 25  
 import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
 26  
 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
 27  
 import com.puppycrawl.tools.checkstyle.api.DetailAST;
 28  
 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
 29  
 import com.puppycrawl.tools.checkstyle.utils.CheckUtils;
 30  
 
 31  
 /**
 32  
  * Restricts nested boolean operators (&&, ||, &, | and ^) to
 33  
  * a specified depth (default = 3).
 34  
  * Note: &, | and ^ are not checked if they are part of constructor or
 35  
  * method call because they can be applied to non boolean variables and
 36  
  * Checkstyle does not know types of methods from different classes.
 37  
  *
 38  
  * @author <a href="mailto:simon@redhillconsulting.com.au">Simon Harris</a>
 39  
  * @author o_sukhodolsky
 40  
  */
 41  
 @FileStatefulCheck
 42  36
 public final class BooleanExpressionComplexityCheck extends AbstractCheck {
 43  
 
 44  
     /**
 45  
      * A key is pointing to the warning message text in "messages.properties"
 46  
      * file.
 47  
      */
 48  
     public static final String MSG_KEY = "booleanExpressionComplexity";
 49  
 
 50  
     /** Default allowed complexity. */
 51  
     private static final int DEFAULT_MAX = 3;
 52  
 
 53  
     /** Stack of contexts. */
 54  13
     private final Deque<Context> contextStack = new ArrayDeque<>();
 55  
     /** Maximum allowed complexity. */
 56  
     private int max;
 57  
     /** Current context. */
 58  13
     private Context context = new Context(false);
 59  
 
 60  
     /** Creates new instance of the check. */
 61  13
     public BooleanExpressionComplexityCheck() {
 62  13
         max = DEFAULT_MAX;
 63  13
     }
 64  
 
 65  
     @Override
 66  
     public int[] getDefaultTokens() {
 67  17
         return new int[] {
 68  
             TokenTypes.CTOR_DEF,
 69  
             TokenTypes.METHOD_DEF,
 70  
             TokenTypes.EXPR,
 71  
             TokenTypes.LAND,
 72  
             TokenTypes.BAND,
 73  
             TokenTypes.LOR,
 74  
             TokenTypes.BOR,
 75  
             TokenTypes.BXOR,
 76  
         };
 77  
     }
 78  
 
 79  
     @Override
 80  
     public int[] getRequiredTokens() {
 81  22
         return new int[] {
 82  
             TokenTypes.CTOR_DEF,
 83  
             TokenTypes.METHOD_DEF,
 84  
             TokenTypes.EXPR,
 85  
         };
 86  
     }
 87  
 
 88  
     @Override
 89  
     public int[] getAcceptableTokens() {
 90  7
         return new int[] {
 91  
             TokenTypes.CTOR_DEF,
 92  
             TokenTypes.METHOD_DEF,
 93  
             TokenTypes.EXPR,
 94  
             TokenTypes.LAND,
 95  
             TokenTypes.BAND,
 96  
             TokenTypes.LOR,
 97  
             TokenTypes.BOR,
 98  
             TokenTypes.BXOR,
 99  
         };
 100  
     }
 101  
 
 102  
     /**
 103  
      * Setter for maximum allowed complexity.
 104  
      * @param max new maximum allowed complexity.
 105  
      */
 106  
     public void setMax(int max) {
 107  2
         this.max = max;
 108  2
     }
 109  
 
 110  
     @Override
 111  
     public void visitToken(DetailAST ast) {
 112  144
         switch (ast.getType()) {
 113  
             case TokenTypes.CTOR_DEF:
 114  
             case TokenTypes.METHOD_DEF:
 115  14
                 visitMethodDef(ast);
 116  14
                 break;
 117  
             case TokenTypes.EXPR:
 118  60
                 visitExpr();
 119  60
                 break;
 120  
             case TokenTypes.BOR:
 121  7
                 if (!isPipeOperator(ast) && !isPassedInParameter(ast)) {
 122  5
                     context.visitBooleanOperator();
 123  
                 }
 124  
                 break;
 125  
             case TokenTypes.BAND:
 126  
             case TokenTypes.BXOR:
 127  26
                 if (!isPassedInParameter(ast)) {
 128  23
                     context.visitBooleanOperator();
 129  
                 }
 130  
                 break;
 131  
             case TokenTypes.LAND:
 132  
             case TokenTypes.LOR:
 133  36
                 context.visitBooleanOperator();
 134  36
                 break;
 135  
             default:
 136  1
                 throw new IllegalArgumentException("Unknown type: " + ast);
 137  
         }
 138  143
     }
 139  
 
 140  
     /**
 141  
      * Checks if logical operator is part of constructor or method call.
 142  
      * @param logicalOperator logical operator
 143  
      * @return true if logical operator is part of constructor or method call
 144  
      */
 145  
     private static boolean isPassedInParameter(DetailAST logicalOperator) {
 146  64
         return logicalOperator.getParent().getType() == TokenTypes.EXPR
 147  6
             && logicalOperator.getParent().getParent().getType() == TokenTypes.ELIST;
 148  
     }
 149  
 
 150  
     /**
 151  
      * Checks if {@link TokenTypes#BOR binary OR} is applied to exceptions
 152  
      * in
 153  
      * <a href="https://docs.oracle.com/javase/specs/jls/se8/html/jls-14.html#jls-14.20">
 154  
      * multi-catch</a> (pipe-syntax).
 155  
      * @param binaryOr {@link TokenTypes#BOR binary or}
 156  
      * @return true if binary or is applied to exceptions in multi-catch.
 157  
      */
 158  
     private static boolean isPipeOperator(DetailAST binaryOr) {
 159  7
         return binaryOr.getParent().getType() == TokenTypes.TYPE;
 160  
     }
 161  
 
 162  
     @Override
 163  
     public void leaveToken(DetailAST ast) {
 164  143
         switch (ast.getType()) {
 165  
             case TokenTypes.CTOR_DEF:
 166  
             case TokenTypes.METHOD_DEF:
 167  14
                 leaveMethodDef();
 168  14
                 break;
 169  
             case TokenTypes.EXPR:
 170  60
                 leaveExpr(ast);
 171  60
                 break;
 172  
             default:
 173  
                 // Do nothing
 174  
         }
 175  143
     }
 176  
 
 177  
     /**
 178  
      * Creates new context for a given method.
 179  
      * @param ast a method we start to check.
 180  
      */
 181  
     private void visitMethodDef(DetailAST ast) {
 182  14
         contextStack.push(context);
 183  14
         final boolean check = !CheckUtils.isEqualsMethod(ast);
 184  14
         context = new Context(check);
 185  14
     }
 186  
 
 187  
     /** Removes old context. */
 188  
     private void leaveMethodDef() {
 189  14
         context = contextStack.pop();
 190  14
     }
 191  
 
 192  
     /** Creates and pushes new context. */
 193  
     private void visitExpr() {
 194  60
         contextStack.push(context);
 195  60
         context = new Context(context.isChecking());
 196  60
     }
 197  
 
 198  
     /**
 199  
      * Restores previous context.
 200  
      * @param ast expression we leave.
 201  
      */
 202  
     private void leaveExpr(DetailAST ast) {
 203  60
         context.checkCount(ast);
 204  60
         context = contextStack.pop();
 205  60
     }
 206  
 
 207  
     /**
 208  
      * Represents context (method/expression) in which we check complexity.
 209  
      *
 210  
      * @author <a href="mailto:simon@redhillconsulting.com.au">Simon Harris</a>
 211  
      * @author o_sukhodolsky
 212  
      */
 213  
     private class Context {
 214  
         /**
 215  
          * Should we perform check in current context or not.
 216  
          * Usually false if we are inside equals() method.
 217  
          */
 218  
         private final boolean checking;
 219  
         /** Count of boolean operators. */
 220  
         private int count;
 221  
 
 222  
         /**
 223  
          * Creates new instance.
 224  
          * @param checking should we check in current context or not.
 225  
          */
 226  87
         Context(boolean checking) {
 227  87
             this.checking = checking;
 228  87
             count = 0;
 229  87
         }
 230  
 
 231  
         /**
 232  
          * Getter for checking property.
 233  
          * @return should we check in current context or not.
 234  
          */
 235  
         public boolean isChecking() {
 236  60
             return checking;
 237  
         }
 238  
 
 239  
         /** Increases operator counter. */
 240  
         public void visitBooleanOperator() {
 241  64
             ++count;
 242  64
         }
 243  
 
 244  
         /**
 245  
          * Checks if we violates maximum allowed complexity.
 246  
          * @param ast a node we check now.
 247  
          */
 248  
         public void checkCount(DetailAST ast) {
 249  60
             if (checking && count > max) {
 250  4
                 final DetailAST parentAST = ast.getParent();
 251  
 
 252  8
                 log(parentAST.getLineNo(), parentAST.getColumnNo(),
 253  4
                     MSG_KEY, count, max);
 254  
             }
 255  60
         }
 256  
     }
 257  
 }