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 * 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="http://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 = !CheckUtils.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 * @author <a href="mailto:simon@redhillconsulting.com.au">Simon Harris</a> 209 * @author o_sukhodolsky 210 */ 211 private class Context { 212 /** 213 * Should we perform check in current context or not. 214 * Usually false if we are inside equals() method. 215 */ 216 private final boolean checking; 217 /** Count of boolean operators. */ 218 private int count; 219 220 /** 221 * Creates new instance. 222 * @param checking should we check in current context or not. 223 */ 224 Context(boolean checking) { 225 this.checking = checking; 226 count = 0; 227 } 228 229 /** 230 * Getter for checking property. 231 * @return should we check in current context or not. 232 */ 233 public boolean isChecking() { 234 return checking; 235 } 236 237 /** Increases operator counter. */ 238 public void visitBooleanOperator() { 239 ++count; 240 } 241 242 /** 243 * Checks if we violates maximum allowed complexity. 244 * @param ast a node we check now. 245 */ 246 public void checkCount(DetailAST ast) { 247 if (checking && count > max) { 248 final DetailAST parentAST = ast.getParent(); 249 250 log(parentAST.getLineNo(), parentAST.getColumnNo(), 251 MSG_KEY, count, max); 252 } 253 } 254 } 255}