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