1 ///////////////////////////////////////////////////////////////////////////////////////////////
2 // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3 // Copyright (C) 2001-2024 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 * <ul>
36 * <li>
37 * Property {@code max} - Specify the maximum threshold allowed.
38 * Type is {@code int}.
39 * Default value is {@code 30}.
40 * </li>
41 * <li>
42 * Property {@code tokens} - tokens to check
43 * Type is {@code java.lang.String[]}.
44 * Validation type is {@code tokenSet}.
45 * Default value is:
46 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CTOR_DEF">
47 * CTOR_DEF</a>,
48 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#METHOD_DEF">
49 * METHOD_DEF</a>,
50 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#INSTANCE_INIT">
51 * INSTANCE_INIT</a>,
52 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#STATIC_INIT">
53 * STATIC_INIT</a>,
54 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#COMPACT_CTOR_DEF">
55 * COMPACT_CTOR_DEF</a>,
56 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LAMBDA">
57 * LAMBDA</a>.
58 * </li>
59 * </ul>
60 *
61 * <p>
62 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
63 * </p>
64 *
65 * <p>
66 * Violation Message Keys:
67 * </p>
68 * <ul>
69 * <li>
70 * {@code executableStatementCount}
71 * </li>
72 * </ul>
73 *
74 * @since 3.2
75 */
76 @FileStatefulCheck
77 public final class ExecutableStatementCountCheck
78 extends AbstractCheck {
79
80 /**
81 * A key is pointing to the warning message text in "messages.properties"
82 * file.
83 */
84 public static final String MSG_KEY = "executableStatementCount";
85
86 /** Default threshold. */
87 private static final int DEFAULT_MAX = 30;
88
89 /** Stack of method contexts. */
90 private final Deque<Context> contextStack = new ArrayDeque<>();
91
92 /** Specify the maximum threshold allowed. */
93 private int max;
94
95 /** Current method context. */
96 private Context context;
97
98 /** Constructs a {@code ExecutableStatementCountCheck}. */
99 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 }