001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2014 Oliver Burn 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//////////////////////////////////////////////////////////////////////////////// 019package com.puppycrawl.tools.checkstyle.checks.metrics; 020 021import com.puppycrawl.tools.checkstyle.api.Check; 022import com.puppycrawl.tools.checkstyle.api.DetailAST; 023import com.puppycrawl.tools.checkstyle.api.FastStack; 024import com.puppycrawl.tools.checkstyle.api.TokenTypes; 025import com.puppycrawl.tools.checkstyle.checks.CheckUtils; 026 027/** 028 * Restricts nested boolean operators (&&, ||, &, | and ^) to 029 * a specified depth (default = 3). 030 * 031 * @author <a href="mailto:simon@redhillconsulting.com.au">Simon Harris</a> 032 * @author o_sukhodolsky 033 */ 034public final class BooleanExpressionComplexityCheck extends Check 035{ 036 /** Default allowed complexity. */ 037 private static final int DEFAULT_MAX = 3; 038 039 /** Stack of contexts. */ 040 private final FastStack<Context> mContextStack = FastStack.newInstance(); 041 /** Maximum allowed complexity. */ 042 private int mMax; 043 /** Current context. */ 044 private Context mContext; 045 046 /** Creates new instance of the check. */ 047 public BooleanExpressionComplexityCheck() 048 { 049 setMax(DEFAULT_MAX); 050 } 051 052 @Override 053 public int[] getDefaultTokens() 054 { 055 return new int[] { 056 TokenTypes.CTOR_DEF, 057 TokenTypes.METHOD_DEF, 058 TokenTypes.EXPR, 059 TokenTypes.LAND, 060 TokenTypes.BAND, 061 TokenTypes.LOR, 062 TokenTypes.BOR, 063 TokenTypes.BXOR, 064 }; 065 } 066 067 @Override 068 public int[] getRequiredTokens() 069 { 070 return new int[] { 071 TokenTypes.CTOR_DEF, 072 TokenTypes.METHOD_DEF, 073 TokenTypes.EXPR, 074 }; 075 } 076 077 /** 078 * Getter for maximum allowed complexity. 079 * @return value of maximum allowed complexity. 080 */ 081 public int getMax() 082 { 083 return mMax; 084 } 085 086 /** 087 * Setter for maximum allowed complexity. 088 * @param aMax new maximum allowed complexity. 089 */ 090 public void setMax(int aMax) 091 { 092 mMax = aMax; 093 } 094 095 @Override 096 public void visitToken(DetailAST aAST) 097 { 098 switch (aAST.getType()) { 099 case TokenTypes.CTOR_DEF: 100 case TokenTypes.METHOD_DEF: 101 visitMethodDef(aAST); 102 break; 103 case TokenTypes.EXPR: 104 visitExpr(); 105 break; 106 case TokenTypes.LAND: 107 case TokenTypes.BAND: 108 case TokenTypes.LOR: 109 case TokenTypes.BOR: 110 case TokenTypes.BXOR: 111 mContext.visitBooleanOperator(); 112 break; 113 default: 114 throw new IllegalStateException(aAST.toString()); 115 } 116 } 117 118 @Override 119 public void leaveToken(DetailAST aAST) 120 { 121 switch (aAST.getType()) { 122 case TokenTypes.CTOR_DEF: 123 case TokenTypes.METHOD_DEF: 124 leaveMethodDef(); 125 break; 126 case TokenTypes.EXPR: 127 leaveExpr(aAST); 128 break; 129 default: 130 // Do nothing 131 } 132 } 133 134 /** 135 * Creates new context for a given method. 136 * @param aAST a method we start to check. 137 */ 138 private void visitMethodDef(DetailAST aAST) 139 { 140 mContextStack.push(mContext); 141 mContext = new Context(!CheckUtils.isEqualsMethod(aAST)); 142 } 143 144 /** Removes old context. */ 145 private void leaveMethodDef() 146 { 147 mContext = mContextStack.pop(); 148 } 149 150 /** Creates and pushes new context. */ 151 private void visitExpr() 152 { 153 mContextStack.push(mContext); 154 mContext = new Context((mContext == null) || mContext.isChecking()); 155 } 156 157 /** 158 * Restores previous context. 159 * @param aAST expression we leave. 160 */ 161 private void leaveExpr(DetailAST aAST) 162 { 163 mContext.checkCount(aAST); 164 mContext = mContextStack.pop(); 165 } 166 167 /** 168 * Represents context (method/expression) in which we check complexity. 169 * 170 * @author <a href="mailto:simon@redhillconsulting.com.au">Simon Harris</a> 171 * @author o_sukhodolsky 172 */ 173 private class Context 174 { 175 /** 176 * Should we perform check in current context or not. 177 * Usually false if we are inside equals() method. 178 */ 179 private final boolean mChecking; 180 /** Count of boolean operators. */ 181 private int mCount; 182 183 /** 184 * Creates new instance. 185 * @param aChecking should we check in current context or not. 186 */ 187 public Context(boolean aChecking) 188 { 189 mChecking = aChecking; 190 mCount = 0; 191 } 192 193 /** 194 * Getter for checking property. 195 * @return should we check in current context or not. 196 */ 197 public boolean isChecking() 198 { 199 return mChecking; 200 } 201 202 /** Increases operator counter. */ 203 public void visitBooleanOperator() 204 { 205 ++mCount; 206 } 207 208 /** 209 * Checks if we violates maximum allowed complexity. 210 * @param aAST a node we check now. 211 */ 212 public void checkCount(DetailAST aAST) 213 { 214 if (mChecking && (mCount > getMax())) { 215 final DetailAST parentAST = aAST.getParent(); 216 217 log(parentAST.getLineNo(), parentAST.getColumnNo(), 218 "booleanExpressionComplexity", mCount, getMax()); 219 } 220 } 221 } 222}