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.sizes; 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; 029 030/** 031 * <p> 032 * Restricts the number of executable statements to a specified limit. 033 * </p> 034 * <ul> 035 * <li> 036 * Property {@code max} - Specify the maximum threshold allowed. 037 * Default value is {@code 30}. 038 * </li> 039 * <li> 040 * Property {@code tokens} - tokens to check 041 * Default value is: 042 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CTOR_DEF"> 043 * CTOR_DEF</a>, 044 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#METHOD_DEF"> 045 * METHOD_DEF</a>, 046 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#INSTANCE_INIT"> 047 * INSTANCE_INIT</a>, 048 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#STATIC_INIT"> 049 * STATIC_INIT</a>. 050 * </li> 051 * </ul> 052 * <p> 053 * To configure the check: 054 * </p> 055 * <pre> 056 * <module name="ExecutableStatementCount"/> 057 * </pre> 058 * <p> 059 * To configure the check with a threshold of 20 for constructor and method definitions: 060 * </p> 061 * <pre> 062 * <module name="ExecutableStatementCount"> 063 * <property name="max" value="20"/> 064 * <property name="tokens" value="CTOR_DEF,METHOD_DEF"/> 065 * </module> 066 * </pre> 067 * 068 * @since 3.2 069 */ 070@FileStatefulCheck 071public final class ExecutableStatementCountCheck 072 extends AbstractCheck { 073 074 /** 075 * A key is pointing to the warning message text in "messages.properties" 076 * file. 077 */ 078 public static final String MSG_KEY = "executableStatementCount"; 079 080 /** Default threshold. */ 081 private static final int DEFAULT_MAX = 30; 082 083 /** Stack of method contexts. */ 084 private final Deque<Context> contextStack = new ArrayDeque<>(); 085 086 /** Specify the maximum threshold allowed. */ 087 private int max; 088 089 /** Current method context. */ 090 private Context context; 091 092 /** Constructs a {@code ExecutableStatementCountCheck}. */ 093 public ExecutableStatementCountCheck() { 094 max = DEFAULT_MAX; 095 } 096 097 @Override 098 public int[] getDefaultTokens() { 099 return new int[] { 100 TokenTypes.CTOR_DEF, 101 TokenTypes.METHOD_DEF, 102 TokenTypes.INSTANCE_INIT, 103 TokenTypes.STATIC_INIT, 104 TokenTypes.SLIST, 105 }; 106 } 107 108 @Override 109 public int[] getRequiredTokens() { 110 return new int[] {TokenTypes.SLIST}; 111 } 112 113 @Override 114 public int[] getAcceptableTokens() { 115 return new int[] { 116 TokenTypes.CTOR_DEF, 117 TokenTypes.METHOD_DEF, 118 TokenTypes.INSTANCE_INIT, 119 TokenTypes.STATIC_INIT, 120 TokenTypes.SLIST, 121 }; 122 } 123 124 /** 125 * Setter to specify the maximum threshold allowed. 126 * 127 * @param max the maximum threshold. 128 */ 129 public void setMax(int max) { 130 this.max = max; 131 } 132 133 @Override 134 public void beginTree(DetailAST rootAST) { 135 context = new Context(null); 136 contextStack.clear(); 137 } 138 139 @Override 140 public void visitToken(DetailAST ast) { 141 switch (ast.getType()) { 142 case TokenTypes.CTOR_DEF: 143 case TokenTypes.METHOD_DEF: 144 case TokenTypes.INSTANCE_INIT: 145 case TokenTypes.STATIC_INIT: 146 visitMemberDef(ast); 147 break; 148 case TokenTypes.SLIST: 149 visitSlist(ast); 150 break; 151 default: 152 throw new IllegalStateException(ast.toString()); 153 } 154 } 155 156 @Override 157 public void leaveToken(DetailAST ast) { 158 switch (ast.getType()) { 159 case TokenTypes.CTOR_DEF: 160 case TokenTypes.METHOD_DEF: 161 case TokenTypes.INSTANCE_INIT: 162 case TokenTypes.STATIC_INIT: 163 leaveMemberDef(ast); 164 break; 165 case TokenTypes.SLIST: 166 // Do nothing 167 break; 168 default: 169 throw new IllegalStateException(ast.toString()); 170 } 171 } 172 173 /** 174 * Process the start of the member definition. 175 * 176 * @param ast the token representing the member definition. 177 */ 178 private void visitMemberDef(DetailAST ast) { 179 contextStack.push(context); 180 context = new Context(ast); 181 } 182 183 /** 184 * Process the end of a member definition. 185 * 186 * @param ast the token representing the member definition. 187 */ 188 private void leaveMemberDef(DetailAST ast) { 189 final int count = context.getCount(); 190 if (count > max) { 191 log(ast, MSG_KEY, count, max); 192 } 193 context = contextStack.pop(); 194 } 195 196 /** 197 * Process the end of a statement list. 198 * 199 * @param ast the token representing the statement list. 200 */ 201 private void visitSlist(DetailAST ast) { 202 if (context.getAST() != null) { 203 // find member AST for the statement list 204 final DetailAST contextAST = context.getAST(); 205 DetailAST parent = ast.getParent(); 206 int type = parent.getType(); 207 while (type != TokenTypes.CTOR_DEF 208 && type != TokenTypes.METHOD_DEF 209 && type != TokenTypes.INSTANCE_INIT 210 && type != TokenTypes.STATIC_INIT) { 211 parent = parent.getParent(); 212 type = parent.getType(); 213 } 214 if (parent == contextAST) { 215 context.addCount(ast.getChildCount() / 2); 216 } 217 } 218 } 219 220 /** 221 * Class to encapsulate counting information about one member. 222 */ 223 private static class Context { 224 225 /** Member AST node. */ 226 private final DetailAST ast; 227 228 /** Counter for context elements. */ 229 private int count; 230 231 /** 232 * Creates new member context. 233 * 234 * @param ast member AST node. 235 */ 236 /* package */ Context(DetailAST ast) { 237 this.ast = ast; 238 count = 0; 239 } 240 241 /** 242 * Increase count. 243 * 244 * @param addition the count increment. 245 */ 246 public void addCount(int addition) { 247 count += addition; 248 } 249 250 /** 251 * Gets the member AST node. 252 * 253 * @return the member AST node. 254 */ 255 public DetailAST getAST() { 256 return ast; 257 } 258 259 /** 260 * Gets the count. 261 * 262 * @return the count. 263 */ 264 public int getCount() { 265 return count; 266 } 267 268 } 269 270}