001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2020 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.FileStatefulCheck; 026import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 027import com.puppycrawl.tools.checkstyle.api.DetailAST; 028import com.puppycrawl.tools.checkstyle.api.TokenTypes; 029import com.puppycrawl.tools.checkstyle.utils.CheckUtil; 030 031/** 032 * <p> 033 * Restricts the number of boolean operators ({@code &&}, {@code ||}, 034 * {@code &}, {@code |} and {@code ^}) in an expression. 035 * </p> 036 * <p> 037 * Rationale: Too many conditions leads to code that is difficult to read 038 * and hence debug and maintain. 039 * </p> 040 * <p> 041 * Note that the operators {@code &} and {@code |} are not only integer bitwise 042 * operators, they are also the 043 * <a href="https://docs.oracle.com/javase/specs/jls/se11/html/jls-15.html#jls-15.22.2"> 044 * non-shortcut versions</a> of the boolean operators {@code &&} and {@code ||}. 045 * </p> 046 * <p> 047 * Note that {@code &}, {@code |} and {@code ^} are not checked if they are part 048 * of constructor or method call because they can be applied to non boolean 049 * variables and Checkstyle does not know types of methods from different classes. 050 * </p> 051 * <ul> 052 * <li> 053 * Property {@code max} - Specify the maximum number of boolean operations 054 * allowed in one expression. 055 * Type is {@code int}. 056 * Default value is {@code 3}. 057 * </li> 058 * <li> 059 * Property {@code tokens} - tokens to check 060 * Type is {@code int[]}. 061 * Default value is: 062 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LAND"> 063 * LAND</a>, 064 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#BAND"> 065 * BAND</a>, 066 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LOR"> 067 * LOR</a>, 068 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#BOR"> 069 * BOR</a>, 070 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#BXOR"> 071 * BXOR</a>. 072 * </li> 073 * </ul> 074 * <p> 075 * To configure the check: 076 * </p> 077 * <pre> 078 * <module name="BooleanExpressionComplexity"/> 079 * </pre> 080 * <p>Code Example:</p> 081 * <pre> 082 * public class Test 083 * { 084 * public static void main(String ... args) 085 * { 086 * boolean a = true; 087 * boolean b = false; 088 * 089 * boolean c = (a & b) | (b ^ a); // OK, 1(&) + 1(|) + 1(^) = 3 (max allowed 3) 090 * 091 * boolean d = (a & b) ^ (a || b) | a; // violation, 1(&) + 1(^) + 1(||) + 1(|) = 4 092 * } 093 * } 094 * </pre> 095 * <p> 096 * To configure the check with 5 allowed operation in boolean expression: 097 * </p> 098 * <pre> 099 * <module name="BooleanExpressionComplexity"> 100 * <property name="max" value="5"/> 101 * </module> 102 * </pre> 103 * <p>Code Example:</p> 104 * <pre> 105 * public class Test 106 * { 107 * public static void main(String ... args) 108 * { 109 * boolean a = true; 110 * boolean b = false; 111 * 112 * boolean c = (a & b) | (b ^ a) | (a ^ b); // OK, 1(&) + 1(|) + 1(^) + 1(|) + 1(^) = 5 113 * 114 * boolean d = (a | b) ^ (a | b) ^ (a || b) & b; // violation, 115 * // 1(|) + 1(^) + 1(|) + 1(^) + 1(||) + 1(&) = 6 116 * } 117 * } 118 * </pre> 119 * <p> 120 * To configure the check to ignore {@code &} and {@code |}: 121 * </p> 122 * <pre> 123 * <module name="BooleanExpressionComplexity"> 124 * <property name="tokens" value="BXOR,LAND,LOR"/> 125 * </module> 126 * </pre> 127 * <p>Code Example:</p> 128 * <pre> 129 * public class Test 130 * { 131 * public static void main(String ... args) 132 * { 133 * boolean a = true; 134 * boolean b = false; 135 * 136 * boolean c = (!a && b) | (a || !b) ^ a; // OK, 1(&&) + 1(||) + 1(^) = 3 137 * // | is ignored here 138 * 139 * boolean d = a ^ (a || b) ^ (b || a) & a; // violation, 1(^) + 1(||) + 1(^) + 1(||) = 4 140 * // & is ignored here 141 * } 142 * } 143 * </pre> 144 * 145 * <p> 146 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 147 * </p> 148 * <p> 149 * Violation Message Keys: 150 * </p> 151 * <ul> 152 * <li> 153 * {@code booleanExpressionComplexity} 154 * </li> 155 * </ul> 156 * 157 * @since 3.4 158 */ 159@FileStatefulCheck 160public final class BooleanExpressionComplexityCheck extends AbstractCheck { 161 162 /** 163 * A key is pointing to the warning message text in "messages.properties" 164 * file. 165 */ 166 public static final String MSG_KEY = "booleanExpressionComplexity"; 167 168 /** Default allowed complexity. */ 169 private static final int DEFAULT_MAX = 3; 170 171 /** Stack of contexts. */ 172 private final Deque<Context> contextStack = new ArrayDeque<>(); 173 /** Specify the maximum number of boolean operations allowed in one expression. */ 174 private int max; 175 /** Current context. */ 176 private Context context = new Context(false); 177 178 /** Creates new instance of the check. */ 179 public BooleanExpressionComplexityCheck() { 180 max = DEFAULT_MAX; 181 } 182 183 @Override 184 public int[] getDefaultTokens() { 185 return new int[] { 186 TokenTypes.CTOR_DEF, 187 TokenTypes.METHOD_DEF, 188 TokenTypes.EXPR, 189 TokenTypes.LAND, 190 TokenTypes.BAND, 191 TokenTypes.LOR, 192 TokenTypes.BOR, 193 TokenTypes.BXOR, 194 }; 195 } 196 197 @Override 198 public int[] getRequiredTokens() { 199 return new int[] { 200 TokenTypes.CTOR_DEF, 201 TokenTypes.METHOD_DEF, 202 TokenTypes.EXPR, 203 }; 204 } 205 206 @Override 207 public int[] getAcceptableTokens() { 208 return new int[] { 209 TokenTypes.CTOR_DEF, 210 TokenTypes.METHOD_DEF, 211 TokenTypes.EXPR, 212 TokenTypes.LAND, 213 TokenTypes.BAND, 214 TokenTypes.LOR, 215 TokenTypes.BOR, 216 TokenTypes.BXOR, 217 }; 218 } 219 220 /** 221 * Setter to specify the maximum number of boolean operations allowed in one expression. 222 * 223 * @param max new maximum allowed complexity. 224 */ 225 public void setMax(int max) { 226 this.max = max; 227 } 228 229 @Override 230 public void visitToken(DetailAST ast) { 231 switch (ast.getType()) { 232 case TokenTypes.CTOR_DEF: 233 case TokenTypes.METHOD_DEF: 234 visitMethodDef(ast); 235 break; 236 case TokenTypes.EXPR: 237 visitExpr(); 238 break; 239 case TokenTypes.BOR: 240 if (!isPipeOperator(ast) && !isPassedInParameter(ast)) { 241 context.visitBooleanOperator(); 242 } 243 break; 244 case TokenTypes.BAND: 245 case TokenTypes.BXOR: 246 if (!isPassedInParameter(ast)) { 247 context.visitBooleanOperator(); 248 } 249 break; 250 case TokenTypes.LAND: 251 case TokenTypes.LOR: 252 context.visitBooleanOperator(); 253 break; 254 default: 255 throw new IllegalArgumentException("Unknown type: " + ast); 256 } 257 } 258 259 /** 260 * Checks if logical operator is part of constructor or method call. 261 * 262 * @param logicalOperator logical operator 263 * @return true if logical operator is part of constructor or method call 264 */ 265 private static boolean isPassedInParameter(DetailAST logicalOperator) { 266 return logicalOperator.getParent().getParent().getType() == TokenTypes.ELIST; 267 } 268 269 /** 270 * Checks if {@link TokenTypes#BOR binary OR} is applied to exceptions 271 * in 272 * <a href="https://docs.oracle.com/javase/specs/jls/se8/html/jls-14.html#jls-14.20"> 273 * multi-catch</a> (pipe-syntax). 274 * 275 * @param binaryOr {@link TokenTypes#BOR binary or} 276 * @return true if binary or is applied to exceptions in multi-catch. 277 */ 278 private static boolean isPipeOperator(DetailAST binaryOr) { 279 return binaryOr.getParent().getType() == TokenTypes.TYPE; 280 } 281 282 @Override 283 public void leaveToken(DetailAST ast) { 284 switch (ast.getType()) { 285 case TokenTypes.CTOR_DEF: 286 case TokenTypes.METHOD_DEF: 287 leaveMethodDef(); 288 break; 289 case TokenTypes.EXPR: 290 leaveExpr(ast); 291 break; 292 default: 293 // Do nothing 294 } 295 } 296 297 /** 298 * Creates new context for a given method. 299 * 300 * @param ast a method we start to check. 301 */ 302 private void visitMethodDef(DetailAST ast) { 303 contextStack.push(context); 304 final boolean check = !CheckUtil.isEqualsMethod(ast); 305 context = new Context(check); 306 } 307 308 /** Removes old context. */ 309 private void leaveMethodDef() { 310 context = contextStack.pop(); 311 } 312 313 /** Creates and pushes new context. */ 314 private void visitExpr() { 315 contextStack.push(context); 316 context = new Context(context.isChecking()); 317 } 318 319 /** 320 * Restores previous context. 321 * 322 * @param ast expression we leave. 323 */ 324 private void leaveExpr(DetailAST ast) { 325 context.checkCount(ast); 326 context = contextStack.pop(); 327 } 328 329 /** 330 * Represents context (method/expression) in which we check complexity. 331 * 332 */ 333 private class Context { 334 335 /** 336 * Should we perform check in current context or not. 337 * Usually false if we are inside equals() method. 338 */ 339 private final boolean checking; 340 /** Count of boolean operators. */ 341 private int count; 342 343 /** 344 * Creates new instance. 345 * 346 * @param checking should we check in current context or not. 347 */ 348 /* package */ Context(boolean checking) { 349 this.checking = checking; 350 count = 0; 351 } 352 353 /** 354 * Getter for checking property. 355 * 356 * @return should we check in current context or not. 357 */ 358 public boolean isChecking() { 359 return checking; 360 } 361 362 /** Increases operator counter. */ 363 public void visitBooleanOperator() { 364 ++count; 365 } 366 367 /** 368 * Checks if we violates maximum allowed complexity. 369 * 370 * @param ast a node we check now. 371 */ 372 public void checkCount(DetailAST ast) { 373 if (checking && count > max) { 374 final DetailAST parentAST = ast.getParent(); 375 376 log(parentAST, MSG_KEY, count, max); 377 } 378 } 379 380 } 381 382}