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.CheckUtils; 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 * @author <a href="mailto:simon@redhillconsulting.com.au">Simon Harris</a> 039 * @author o_sukhodolsky 040 */ 041@FileStatefulCheck 042public final class BooleanExpressionComplexityCheck extends AbstractCheck { 043 044 /** 045 * A key is pointing to the warning message text in "messages.properties" 046 * file. 047 */ 048 public static final String MSG_KEY = "booleanExpressionComplexity"; 049 050 /** Default allowed complexity. */ 051 private static final int DEFAULT_MAX = 3; 052 053 /** Stack of contexts. */ 054 private final Deque<Context> contextStack = new ArrayDeque<>(); 055 /** Maximum allowed complexity. */ 056 private int max; 057 /** Current context. */ 058 private Context context = new Context(false); 059 060 /** Creates new instance of the check. */ 061 public BooleanExpressionComplexityCheck() { 062 max = DEFAULT_MAX; 063 } 064 065 @Override 066 public int[] getDefaultTokens() { 067 return new int[] { 068 TokenTypes.CTOR_DEF, 069 TokenTypes.METHOD_DEF, 070 TokenTypes.EXPR, 071 TokenTypes.LAND, 072 TokenTypes.BAND, 073 TokenTypes.LOR, 074 TokenTypes.BOR, 075 TokenTypes.BXOR, 076 }; 077 } 078 079 @Override 080 public int[] getRequiredTokens() { 081 return new int[] { 082 TokenTypes.CTOR_DEF, 083 TokenTypes.METHOD_DEF, 084 TokenTypes.EXPR, 085 }; 086 } 087 088 @Override 089 public int[] getAcceptableTokens() { 090 return new int[] { 091 TokenTypes.CTOR_DEF, 092 TokenTypes.METHOD_DEF, 093 TokenTypes.EXPR, 094 TokenTypes.LAND, 095 TokenTypes.BAND, 096 TokenTypes.LOR, 097 TokenTypes.BOR, 098 TokenTypes.BXOR, 099 }; 100 } 101 102 /** 103 * Setter for maximum allowed complexity. 104 * @param max new maximum allowed complexity. 105 */ 106 public void setMax(int max) { 107 this.max = max; 108 } 109 110 @Override 111 public void visitToken(DetailAST ast) { 112 switch (ast.getType()) { 113 case TokenTypes.CTOR_DEF: 114 case TokenTypes.METHOD_DEF: 115 visitMethodDef(ast); 116 break; 117 case TokenTypes.EXPR: 118 visitExpr(); 119 break; 120 case TokenTypes.BOR: 121 if (!isPipeOperator(ast) && !isPassedInParameter(ast)) { 122 context.visitBooleanOperator(); 123 } 124 break; 125 case TokenTypes.BAND: 126 case TokenTypes.BXOR: 127 if (!isPassedInParameter(ast)) { 128 context.visitBooleanOperator(); 129 } 130 break; 131 case TokenTypes.LAND: 132 case TokenTypes.LOR: 133 context.visitBooleanOperator(); 134 break; 135 default: 136 throw new IllegalArgumentException("Unknown type: " + ast); 137 } 138 } 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 return logicalOperator.getParent().getType() == TokenTypes.EXPR 147 && 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 return binaryOr.getParent().getType() == TokenTypes.TYPE; 160 } 161 162 @Override 163 public void leaveToken(DetailAST ast) { 164 switch (ast.getType()) { 165 case TokenTypes.CTOR_DEF: 166 case TokenTypes.METHOD_DEF: 167 leaveMethodDef(); 168 break; 169 case TokenTypes.EXPR: 170 leaveExpr(ast); 171 break; 172 default: 173 // Do nothing 174 } 175 } 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 contextStack.push(context); 183 final boolean check = !CheckUtils.isEqualsMethod(ast); 184 context = new Context(check); 185 } 186 187 /** Removes old context. */ 188 private void leaveMethodDef() { 189 context = contextStack.pop(); 190 } 191 192 /** Creates and pushes new context. */ 193 private void visitExpr() { 194 contextStack.push(context); 195 context = new Context(context.isChecking()); 196 } 197 198 /** 199 * Restores previous context. 200 * @param ast expression we leave. 201 */ 202 private void leaveExpr(DetailAST ast) { 203 context.checkCount(ast); 204 context = contextStack.pop(); 205 } 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 /** 216 * Should we perform check in current context or not. 217 * Usually false if we are inside equals() method. 218 */ 219 private final boolean checking; 220 /** Count of boolean operators. */ 221 private int count; 222 223 /** 224 * Creates new instance. 225 * @param checking should we check in current context or not. 226 */ 227 Context(boolean checking) { 228 this.checking = checking; 229 count = 0; 230 } 231 232 /** 233 * Getter for checking property. 234 * @return should we check in current context or not. 235 */ 236 public boolean isChecking() { 237 return checking; 238 } 239 240 /** Increases operator counter. */ 241 public void visitBooleanOperator() { 242 ++count; 243 } 244 245 /** 246 * Checks if we violates maximum allowed complexity. 247 * @param ast a node we check now. 248 */ 249 public void checkCount(DetailAST ast) { 250 if (checking && count > max) { 251 final DetailAST parentAST = ast.getParent(); 252 253 log(parentAST.getLineNo(), parentAST.getColumnNo(), 254 MSG_KEY, count, max); 255 } 256 } 257 258 } 259 260}