001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2017 the original author or authors. 004// 005// This library is free software; you can redistribute it and/or 006// modify it under the terms of the GNU Lesser General Public 007// License as published by the Free Software Foundation; either 008// version 2.1 of the License, or (at your option) any later version. 009// 010// This library is distributed in the hope that it will be useful, 011// but WITHOUT ANY WARRANTY; without even the implied warranty of 012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013// Lesser General Public License for more details. 014// 015// You should have received a copy of the GNU Lesser General Public 016// License along with this library; if not, write to the Free Software 017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 018//////////////////////////////////////////////////////////////////////////////// 019 020package com.puppycrawl.tools.checkstyle.checks.metrics; 021 022import java.util.ArrayDeque; 023import java.util.Deque; 024 025import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 026import com.puppycrawl.tools.checkstyle.api.DetailAST; 027import com.puppycrawl.tools.checkstyle.api.TokenTypes; 028import com.puppycrawl.tools.checkstyle.utils.CheckUtils; 029 030/** 031 * Restricts nested boolean operators (&&, ||, &, | and ^) to 032 * a specified depth (default = 3). 033 * Note: &, | and ^ are not checked if they are part of constructor or 034 * method call because they can be applied to non boolean variables and 035 * Checkstyle does not know types of methods from different classes. 036 * 037 * @author <a href="mailto:simon@redhillconsulting.com.au">Simon Harris</a> 038 * @author o_sukhodolsky 039 */ 040public final class BooleanExpressionComplexityCheck extends AbstractCheck { 041 042 /** 043 * A key is pointing to the warning message text in "messages.properties" 044 * file. 045 */ 046 public static final String MSG_KEY = "booleanExpressionComplexity"; 047 048 /** Default allowed complexity. */ 049 private static final int DEFAULT_MAX = 3; 050 051 /** Stack of contexts. */ 052 private final Deque<Context> contextStack = new ArrayDeque<>(); 053 /** Maximum allowed complexity. */ 054 private int max; 055 /** Current context. */ 056 private Context context = new Context(false); 057 058 /** Creates new instance of the check. */ 059 public BooleanExpressionComplexityCheck() { 060 max = DEFAULT_MAX; 061 } 062 063 @Override 064 public int[] getDefaultTokens() { 065 return new int[] { 066 TokenTypes.CTOR_DEF, 067 TokenTypes.METHOD_DEF, 068 TokenTypes.EXPR, 069 TokenTypes.LAND, 070 TokenTypes.BAND, 071 TokenTypes.LOR, 072 TokenTypes.BOR, 073 TokenTypes.BXOR, 074 }; 075 } 076 077 @Override 078 public int[] getRequiredTokens() { 079 return new int[] { 080 TokenTypes.CTOR_DEF, 081 TokenTypes.METHOD_DEF, 082 TokenTypes.EXPR, 083 }; 084 } 085 086 @Override 087 public int[] getAcceptableTokens() { 088 return new int[] { 089 TokenTypes.CTOR_DEF, 090 TokenTypes.METHOD_DEF, 091 TokenTypes.EXPR, 092 TokenTypes.LAND, 093 TokenTypes.BAND, 094 TokenTypes.LOR, 095 TokenTypes.BOR, 096 TokenTypes.BXOR, 097 }; 098 } 099 100 /** 101 * Getter for maximum allowed complexity. 102 * @return value of maximum allowed complexity. 103 */ 104 public int getMax() { 105 return max; 106 } 107 108 /** 109 * Setter for maximum allowed complexity. 110 * @param max new maximum allowed complexity. 111 */ 112 public void setMax(int max) { 113 this.max = max; 114 } 115 116 @Override 117 public void visitToken(DetailAST ast) { 118 switch (ast.getType()) { 119 case TokenTypes.CTOR_DEF: 120 case TokenTypes.METHOD_DEF: 121 visitMethodDef(ast); 122 break; 123 case TokenTypes.EXPR: 124 visitExpr(); 125 break; 126 case TokenTypes.BOR: 127 if (!isPipeOperator(ast) && !isPassedInParameter(ast)) { 128 context.visitBooleanOperator(); 129 } 130 break; 131 case TokenTypes.BAND: 132 case TokenTypes.BXOR: 133 if (!isPassedInParameter(ast)) { 134 context.visitBooleanOperator(); 135 } 136 break; 137 case TokenTypes.LAND: 138 case TokenTypes.LOR: 139 context.visitBooleanOperator(); 140 break; 141 default: 142 throw new IllegalArgumentException("Unknown type: " + ast); 143 } 144 } 145 146 /** 147 * Checks if logical operator is part of constructor or method call. 148 * @param logicalOperator logical operator 149 * @return true if logical operator is part of constructor or method call 150 */ 151 private static boolean isPassedInParameter(DetailAST logicalOperator) { 152 return logicalOperator.getParent().getType() == TokenTypes.EXPR 153 && logicalOperator.getParent().getParent().getType() == TokenTypes.ELIST; 154 } 155 156 /** 157 * Checks if {@link TokenTypes#BOR binary OR} is applied to exceptions 158 * in 159 * <a href="http://docs.oracle.com/javase/specs/jls/se8/html/jls-14.html#jls-14.20"> 160 * multi-catch</a> (pipe-syntax). 161 * @param binaryOr {@link TokenTypes#BOR binary or} 162 * @return true if binary or is applied to exceptions in multi-catch. 163 */ 164 private static boolean isPipeOperator(DetailAST binaryOr) { 165 return binaryOr.getParent().getType() == TokenTypes.TYPE; 166 } 167 168 @Override 169 public void leaveToken(DetailAST ast) { 170 switch (ast.getType()) { 171 case TokenTypes.CTOR_DEF: 172 case TokenTypes.METHOD_DEF: 173 leaveMethodDef(); 174 break; 175 case TokenTypes.EXPR: 176 leaveExpr(ast); 177 break; 178 default: 179 // Do nothing 180 } 181 } 182 183 /** 184 * Creates new context for a given method. 185 * @param ast a method we start to check. 186 */ 187 private void visitMethodDef(DetailAST ast) { 188 contextStack.push(context); 189 final boolean check = !CheckUtils.isEqualsMethod(ast); 190 context = new Context(check); 191 } 192 193 /** Removes old context. */ 194 private void leaveMethodDef() { 195 context = contextStack.pop(); 196 } 197 198 /** Creates and pushes new context. */ 199 private void visitExpr() { 200 contextStack.push(context); 201 context = new Context(context.isChecking()); 202 } 203 204 /** 205 * Restores previous context. 206 * @param ast expression we leave. 207 */ 208 private void leaveExpr(DetailAST ast) { 209 context.checkCount(ast); 210 context = contextStack.pop(); 211 } 212 213 /** 214 * Represents context (method/expression) in which we check complexity. 215 * 216 * @author <a href="mailto:simon@redhillconsulting.com.au">Simon Harris</a> 217 * @author o_sukhodolsky 218 */ 219 private class Context { 220 /** 221 * Should we perform check in current context or not. 222 * Usually false if we are inside equals() method. 223 */ 224 private final boolean checking; 225 /** Count of boolean operators. */ 226 private int count; 227 228 /** 229 * Creates new instance. 230 * @param checking should we check in current context or not. 231 */ 232 Context(boolean checking) { 233 this.checking = checking; 234 count = 0; 235 } 236 237 /** 238 * Getter for checking property. 239 * @return should we check in current context or not. 240 */ 241 public boolean isChecking() { 242 return checking; 243 } 244 245 /** Increases operator counter. */ 246 public void visitBooleanOperator() { 247 ++count; 248 } 249 250 /** 251 * Checks if we violates maximum allowed complexity. 252 * @param ast a node we check now. 253 */ 254 public void checkCount(DetailAST ast) { 255 if (checking && count > getMax()) { 256 final DetailAST parentAST = ast.getParent(); 257 258 log(parentAST.getLineNo(), parentAST.getColumnNo(), 259 MSG_KEY, count, getMax()); 260 } 261 } 262 } 263}