001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2018 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 * Restricts nested boolean operators (&&, ||, &, | and ^) to 033 * a specified depth (default = 3). 034 * Note: &, | and ^ are not checked if they are part of constructor or 035 * method call because they can be applied to non boolean variables and 036 * Checkstyle does not know types of methods from different classes. 037 * 038 */ 039@FileStatefulCheck 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 * Setter for maximum allowed complexity. 102 * @param max new maximum allowed complexity. 103 */ 104 public void setMax(int max) { 105 this.max = max; 106 } 107 108 @Override 109 public void visitToken(DetailAST ast) { 110 switch (ast.getType()) { 111 case TokenTypes.CTOR_DEF: 112 case TokenTypes.METHOD_DEF: 113 visitMethodDef(ast); 114 break; 115 case TokenTypes.EXPR: 116 visitExpr(); 117 break; 118 case TokenTypes.BOR: 119 if (!isPipeOperator(ast) && !isPassedInParameter(ast)) { 120 context.visitBooleanOperator(); 121 } 122 break; 123 case TokenTypes.BAND: 124 case TokenTypes.BXOR: 125 if (!isPassedInParameter(ast)) { 126 context.visitBooleanOperator(); 127 } 128 break; 129 case TokenTypes.LAND: 130 case TokenTypes.LOR: 131 context.visitBooleanOperator(); 132 break; 133 default: 134 throw new IllegalArgumentException("Unknown type: " + ast); 135 } 136 } 137 138 /** 139 * Checks if logical operator is part of constructor or method call. 140 * @param logicalOperator logical operator 141 * @return true if logical operator is part of constructor or method call 142 */ 143 private static boolean isPassedInParameter(DetailAST logicalOperator) { 144 return logicalOperator.getParent().getType() == TokenTypes.EXPR 145 && logicalOperator.getParent().getParent().getType() == TokenTypes.ELIST; 146 } 147 148 /** 149 * Checks if {@link TokenTypes#BOR binary OR} is applied to exceptions 150 * in 151 * <a href="https://docs.oracle.com/javase/specs/jls/se8/html/jls-14.html#jls-14.20"> 152 * multi-catch</a> (pipe-syntax). 153 * @param binaryOr {@link TokenTypes#BOR binary or} 154 * @return true if binary or is applied to exceptions in multi-catch. 155 */ 156 private static boolean isPipeOperator(DetailAST binaryOr) { 157 return binaryOr.getParent().getType() == TokenTypes.TYPE; 158 } 159 160 @Override 161 public void leaveToken(DetailAST ast) { 162 switch (ast.getType()) { 163 case TokenTypes.CTOR_DEF: 164 case TokenTypes.METHOD_DEF: 165 leaveMethodDef(); 166 break; 167 case TokenTypes.EXPR: 168 leaveExpr(ast); 169 break; 170 default: 171 // Do nothing 172 } 173 } 174 175 /** 176 * Creates new context for a given method. 177 * @param ast a method we start to check. 178 */ 179 private void visitMethodDef(DetailAST ast) { 180 contextStack.push(context); 181 final boolean check = !CheckUtil.isEqualsMethod(ast); 182 context = new Context(check); 183 } 184 185 /** Removes old context. */ 186 private void leaveMethodDef() { 187 context = contextStack.pop(); 188 } 189 190 /** Creates and pushes new context. */ 191 private void visitExpr() { 192 contextStack.push(context); 193 context = new Context(context.isChecking()); 194 } 195 196 /** 197 * Restores previous context. 198 * @param ast expression we leave. 199 */ 200 private void leaveExpr(DetailAST ast) { 201 context.checkCount(ast); 202 context = contextStack.pop(); 203 } 204 205 /** 206 * Represents context (method/expression) in which we check complexity. 207 * 208 */ 209 private class Context { 210 211 /** 212 * Should we perform check in current context or not. 213 * Usually false if we are inside equals() method. 214 */ 215 private final boolean checking; 216 /** Count of boolean operators. */ 217 private int count; 218 219 /** 220 * Creates new instance. 221 * @param checking should we check in current context or not. 222 */ 223 Context(boolean checking) { 224 this.checking = checking; 225 count = 0; 226 } 227 228 /** 229 * Getter for checking property. 230 * @return should we check in current context or not. 231 */ 232 public boolean isChecking() { 233 return checking; 234 } 235 236 /** Increases operator counter. */ 237 public void visitBooleanOperator() { 238 ++count; 239 } 240 241 /** 242 * Checks if we violates maximum allowed complexity. 243 * @param ast a node we check now. 244 */ 245 public void checkCount(DetailAST ast) { 246 if (checking && count > max) { 247 final DetailAST parentAST = ast.getParent(); 248 249 log(parentAST, MSG_KEY, count, max); 250 } 251 } 252 253 } 254 255}