1 ///////////////////////////////////////////////////////////////////////////////////////////////
2 // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3 // Copyright (C) 2001-2025 the original author or authors.
4 //
5 // This library is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU Lesser General Public
7 // License as published by the Free Software Foundation; either
8 // version 2.1 of the License, or (at your option) any later version.
9 //
10 // This library is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 // Lesser General Public License for more details.
14 //
15 // You should have received a copy of the GNU Lesser General Public
16 // License along with this library; if not, write to the Free Software
17 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 ///////////////////////////////////////////////////////////////////////////////////////////////
19
20 package com.puppycrawl.tools.checkstyle.checks.sizes;
21
22 import java.util.ArrayDeque;
23 import java.util.Deque;
24
25 import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
26 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
27 import com.puppycrawl.tools.checkstyle.api.DetailAST;
28 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
29 import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
30
31 /**
32 * <div>
33 * Restricts the number of executable statements to a specified limit.
34 * </div>
35 *
36 * @since 3.2
37 */
38 @FileStatefulCheck
39 public final class ExecutableStatementCountCheck
40 extends AbstractCheck {
41
42 /**
43 * A key is pointing to the warning message text in "messages.properties"
44 * file.
45 */
46 public static final String MSG_KEY = "executableStatementCount";
47
48 /** Default threshold. */
49 private static final int DEFAULT_MAX = 30;
50
51 /** Stack of method contexts. */
52 private final Deque<Context> contextStack = new ArrayDeque<>();
53
54 /** Specify the maximum threshold allowed. */
55 private int max;
56
57 /** Current method context. */
58 private Context context;
59
60 /** Constructs a {@code ExecutableStatementCountCheck}. */
61 public ExecutableStatementCountCheck() {
62 max = DEFAULT_MAX;
63 }
64
65 @Override
66 public int[] getDefaultTokens() {
67 return new int[] {
68 TokenTypes.CTOR_DEF,
69 TokenTypes.METHOD_DEF,
70 TokenTypes.INSTANCE_INIT,
71 TokenTypes.STATIC_INIT,
72 TokenTypes.SLIST,
73 TokenTypes.COMPACT_CTOR_DEF,
74 TokenTypes.LAMBDA,
75 };
76 }
77
78 @Override
79 public int[] getRequiredTokens() {
80 return new int[] {TokenTypes.SLIST};
81 }
82
83 @Override
84 public int[] getAcceptableTokens() {
85 return new int[] {
86 TokenTypes.CTOR_DEF,
87 TokenTypes.METHOD_DEF,
88 TokenTypes.INSTANCE_INIT,
89 TokenTypes.STATIC_INIT,
90 TokenTypes.SLIST,
91 TokenTypes.COMPACT_CTOR_DEF,
92 TokenTypes.LAMBDA,
93 };
94 }
95
96 /**
97 * Setter to specify the maximum threshold allowed.
98 *
99 * @param max the maximum threshold.
100 * @since 3.2
101 */
102 public void setMax(int max) {
103 this.max = max;
104 }
105
106 @Override
107 public void beginTree(DetailAST rootAST) {
108 context = new Context(null);
109 contextStack.clear();
110 }
111
112 @Override
113 public void visitToken(DetailAST ast) {
114 if (isContainerNode(ast)) {
115 visitContainerNode(ast);
116 }
117 else if (TokenUtil.isOfType(ast, TokenTypes.SLIST)) {
118 visitSlist(ast);
119 }
120 else {
121 throw new IllegalStateException(ast.toString());
122 }
123 }
124
125 @Override
126 public void leaveToken(DetailAST ast) {
127 if (isContainerNode(ast)) {
128 leaveContainerNode(ast);
129 }
130 else if (!TokenUtil.isOfType(ast, TokenTypes.SLIST)) {
131 throw new IllegalStateException(ast.toString());
132 }
133 }
134
135 /**
136 * Process the start of the container node.
137 *
138 * @param ast the token representing the container node.
139 */
140 private void visitContainerNode(DetailAST ast) {
141 contextStack.push(context);
142 context = new Context(ast);
143 }
144
145 /**
146 * Process the end of a container node.
147 *
148 * @param ast the token representing the container node.
149 */
150 private void leaveContainerNode(DetailAST ast) {
151 final int count = context.getCount();
152 if (count > max) {
153 log(ast, MSG_KEY, count, max);
154 }
155 context = contextStack.pop();
156 }
157
158 /**
159 * Process the end of a statement list.
160 *
161 * @param ast the token representing the statement list.
162 */
163 private void visitSlist(DetailAST ast) {
164 final DetailAST contextAST = context.getAST();
165 DetailAST parent = ast;
166 while (parent != null && !isContainerNode(parent)) {
167 parent = parent.getParent();
168 }
169 if (parent == contextAST) {
170 context.addCount(ast.getChildCount() / 2);
171 }
172 }
173
174 /**
175 * Check if the node is of type ctor (compact or canonical),
176 * instance/ static initializer, method definition or lambda.
177 *
178 * @param node AST node we are checking
179 * @return true if node is of the given types
180 */
181 private static boolean isContainerNode(DetailAST node) {
182 return TokenUtil.isOfType(node, TokenTypes.METHOD_DEF,
183 TokenTypes.LAMBDA, TokenTypes.CTOR_DEF, TokenTypes.INSTANCE_INIT,
184 TokenTypes.STATIC_INIT, TokenTypes.COMPACT_CTOR_DEF);
185 }
186
187 /**
188 * Class to encapsulate counting information about one member.
189 */
190 private static final class Context {
191
192 /** Member AST node. */
193 private final DetailAST ast;
194
195 /** Counter for context elements. */
196 private int count;
197
198 /**
199 * Creates new member context.
200 *
201 * @param ast member AST node.
202 */
203 private Context(DetailAST ast) {
204 this.ast = ast;
205 }
206
207 /**
208 * Increase count.
209 *
210 * @param addition the count increment.
211 */
212 public void addCount(int addition) {
213 count += addition;
214 }
215
216 /**
217 * Gets the member AST node.
218 *
219 * @return the member AST node.
220 */
221 public DetailAST getAST() {
222 return ast;
223 }
224
225 /**
226 * Gets the count.
227 *
228 * @return the count.
229 */
230 public int getCount() {
231 return count;
232 }
233
234 }
235
236 }