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