1 ///////////////////////////////////////////////////////////////////////////////////////////////
2 // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3 // Copyright (C) 2001-2026 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 import java.util.EnumMap;
25 import java.util.Map;
26
27 import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
28 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
29 import com.puppycrawl.tools.checkstyle.api.DetailAST;
30 import com.puppycrawl.tools.checkstyle.api.Scope;
31 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
32 import com.puppycrawl.tools.checkstyle.utils.ScopeUtil;
33
34 /**
35 * <div>
36 * Checks the number of methods declared in each type declaration by access modifier
37 * or total count.
38 * </div>
39 *
40 * <p>
41 * This check can be configured to flag classes that define too many methods
42 * to prevent the class from getting too complex. Counting can be customized
43 * to prevent too many total methods in a type definition ({@code maxTotal}),
44 * or to prevent too many methods of a specific access modifier ({@code private},
45 * {@code package}, {@code protected} or {@code public}). Each count is completely
46 * separated to customize how many methods of each you want to allow. For example,
47 * specifying a {@code maxTotal} of 10, still means you can prevent more than 0
48 * {@code maxPackage} methods. A violation won't appear for 8 public methods,
49 * but one will appear if there is also 3 private methods or any package-private methods.
50 * </p>
51 *
52 * <p>
53 * Methods defined in anonymous classes are not counted towards any totals.
54 * Counts only go towards the main type declaration parent, and are kept separate
55 * from it's children's inner types.
56 * </p>
57 * {@snippet lang="text" :
58 * public class ExampleClass {
59 * public enum Colors {
60 * RED, GREEN, YELLOW;
61 *
62 * public String getRGB() { } // NOT counted towards ExampleClass
63 * }
64 *
65 * public void example() { // counted towards ExampleClass
66 * Runnable r = (new Runnable() {
67 * public void run() { } // NOT counted towards ExampleClass, won't produce any violations
68 * });
69 * }
70 *
71 * public static class InnerExampleClass {
72 * protected void example2() { } // NOT counted towards ExampleClass,
73 * // but counted towards InnerExampleClass
74 * }
75 * }
76 * }
77 *
78 * @since 5.3
79 */
80 @FileStatefulCheck
81 public final class MethodCountCheck extends AbstractCheck {
82
83 /**
84 * A key is pointing to the warning message text in "messages.properties"
85 * file.
86 */
87 public static final String MSG_PRIVATE_METHODS = "too.many.privateMethods";
88
89 /**
90 * A key is pointing to the warning message text in "messages.properties"
91 * file.
92 */
93 public static final String MSG_PACKAGE_METHODS = "too.many.packageMethods";
94
95 /**
96 * A key is pointing to the warning message text in "messages.properties"
97 * file.
98 */
99 public static final String MSG_PROTECTED_METHODS = "too.many.protectedMethods";
100
101 /**
102 * A key is pointing to the warning message text in "messages.properties"
103 * file.
104 */
105 public static final String MSG_PUBLIC_METHODS = "too.many.publicMethods";
106
107 /**
108 * A key is pointing to the warning message text in "messages.properties"
109 * file.
110 */
111 public static final String MSG_MANY_METHODS = "too.many.methods";
112
113 /** Default maximum number of methods. */
114 private static final int DEFAULT_MAX_METHODS = 100;
115
116 /** Maintains stack of counters, to support inner types. */
117 private final Deque<MethodCounter> counters = new ArrayDeque<>();
118
119 /** Specify the maximum number of {@code private} methods allowed. */
120 private int maxPrivate = DEFAULT_MAX_METHODS;
121 /** Specify the maximum number of {@code package} methods allowed. */
122 private int maxPackage = DEFAULT_MAX_METHODS;
123 /** Specify the maximum number of {@code protected} methods allowed. */
124 private int maxProtected = DEFAULT_MAX_METHODS;
125 /** Specify the maximum number of {@code public} methods allowed. */
126 private int maxPublic = DEFAULT_MAX_METHODS;
127 /** Specify the maximum number of methods allowed at all scope levels. */
128 private int maxTotal = DEFAULT_MAX_METHODS;
129
130 /**
131 * Creates a new {@code MethodCountCheck} instance.
132 */
133 public MethodCountCheck() {
134 // no code by default
135 }
136
137 @Override
138 public int[] getDefaultTokens() {
139 return getAcceptableTokens();
140 }
141
142 @Override
143 public int[] getAcceptableTokens() {
144 return new int[] {
145 TokenTypes.CLASS_DEF,
146 TokenTypes.ENUM_CONSTANT_DEF,
147 TokenTypes.ENUM_DEF,
148 TokenTypes.INTERFACE_DEF,
149 TokenTypes.ANNOTATION_DEF,
150 TokenTypes.METHOD_DEF,
151 TokenTypes.RECORD_DEF,
152 TokenTypes.COMPACT_COMPILATION_UNIT,
153 };
154 }
155
156 @Override
157 public int[] getRequiredTokens() {
158 return new int[] {TokenTypes.METHOD_DEF};
159 }
160
161 @Override
162 public void visitToken(DetailAST ast) {
163 if (ast.getType() == TokenTypes.METHOD_DEF) {
164 if (isInLatestScopeDefinition(ast)) {
165 raiseCounter(ast);
166 }
167 }
168 else {
169 counters.push(new MethodCounter(ast));
170 }
171 }
172
173 @Override
174 public void leaveToken(DetailAST ast) {
175 if (ast.getType() != TokenTypes.METHOD_DEF) {
176 final MethodCounter counter = counters.pop();
177
178 checkCounters(counter, ast);
179 }
180 }
181
182 /**
183 * Checks if there is a scope definition to check and that the method is found inside that scope
184 * (class, enum, etc.).
185 *
186 * @param methodDef
187 * The method to analyze.
188 * @return {@code true} if the method is part of the latest scope definition and should be
189 * counted.
190 */
191 private boolean isInLatestScopeDefinition(DetailAST methodDef) {
192 boolean result = false;
193
194 if (!counters.isEmpty()) {
195 final DetailAST latestDefinition = counters.peek().getScopeDefinition();
196 final DetailAST methodParent = methodDef.getParent();
197 final DetailAST scopeDefinition;
198 if (methodParent.getType() == TokenTypes.COMPACT_COMPILATION_UNIT) {
199 scopeDefinition = methodParent;
200 }
201 else {
202 scopeDefinition = methodParent.getParent();
203 }
204
205 result = latestDefinition == scopeDefinition;
206 }
207
208 return result;
209 }
210
211 /**
212 * Determine the visibility modifier and raise the corresponding counter.
213 *
214 * @param method
215 * The method-subtree from the AbstractSyntaxTree.
216 */
217 private void raiseCounter(DetailAST method) {
218 final MethodCounter actualCounter = counters.peek();
219 final Scope scope = ScopeUtil.getScope(method);
220 actualCounter.increment(scope);
221 }
222
223 /**
224 * Check the counters and report violations.
225 *
226 * @param counter the method counters to check
227 * @param ast to report violations against.
228 */
229 private void checkCounters(MethodCounter counter, DetailAST ast) {
230 checkMax(maxPrivate, counter.value(Scope.PRIVATE),
231 MSG_PRIVATE_METHODS, ast);
232 checkMax(maxPackage, counter.value(Scope.PACKAGE),
233 MSG_PACKAGE_METHODS, ast);
234 checkMax(maxProtected, counter.value(Scope.PROTECTED),
235 MSG_PROTECTED_METHODS, ast);
236 checkMax(maxPublic, counter.value(Scope.PUBLIC),
237 MSG_PUBLIC_METHODS, ast);
238 checkMax(maxTotal, counter.getTotal(), MSG_MANY_METHODS, ast);
239 }
240
241 /**
242 * Utility for reporting if a maximum has been exceeded.
243 *
244 * @param max the maximum allowed value
245 * @param value the actual value
246 * @param msg the message to log. Takes two arguments of value and maximum.
247 * @param ast the AST to associate with the message.
248 */
249 private void checkMax(int max, int value, String msg, DetailAST ast) {
250 if (max < value) {
251 log(ast, msg, value, max);
252 }
253 }
254
255 /**
256 * Setter to specify the maximum number of {@code private} methods allowed.
257 *
258 * @param value the maximum allowed.
259 * @since 5.3
260 */
261 public void setMaxPrivate(int value) {
262 maxPrivate = value;
263 }
264
265 /**
266 * Setter to specify the maximum number of {@code package} methods allowed.
267 *
268 * @param value the maximum allowed.
269 * @since 5.3
270 */
271 public void setMaxPackage(int value) {
272 maxPackage = value;
273 }
274
275 /**
276 * Setter to specify the maximum number of {@code protected} methods allowed.
277 *
278 * @param value the maximum allowed.
279 * @since 5.3
280 */
281 public void setMaxProtected(int value) {
282 maxProtected = value;
283 }
284
285 /**
286 * Setter to specify the maximum number of {@code public} methods allowed.
287 *
288 * @param value the maximum allowed.
289 * @since 5.3
290 */
291 public void setMaxPublic(int value) {
292 maxPublic = value;
293 }
294
295 /**
296 * Setter to specify the maximum number of methods allowed at all scope levels.
297 *
298 * @param value the maximum allowed.
299 * @since 5.3
300 */
301 public void setMaxTotal(int value) {
302 maxTotal = value;
303 }
304
305 /**
306 * Marker class used to collect data about the number of methods per
307 * class. Objects of this class are used on the Stack to count the
308 * methods for each class and layer.
309 */
310 private static final class MethodCounter {
311
312 /** Maintains the counts. */
313 private final Map<Scope, Integer> counts = new EnumMap<>(Scope.class);
314 /**
315 * The surrounding scope definition (class, enum, etc.) which the method counts are
316 * connected to.
317 */
318 private final DetailAST scopeDefinition;
319 /** Tracks the total. */
320 private int total;
321
322 /**
323 * Creates an interface.
324 *
325 * @param scopeDefinition
326 * The surrounding scope definition (class, enum, etc.) which to count all methods
327 * for.
328 */
329 private MethodCounter(DetailAST scopeDefinition) {
330 this.scopeDefinition = scopeDefinition;
331 }
332
333 /**
334 * Increments to counter by one for the supplied scope.
335 *
336 * @param scope the scope counter to increment.
337 */
338 private void increment(Scope scope) {
339 total++;
340 counts.put(scope, 1 + value(scope));
341 }
342
343 /**
344 * Gets the value of a scope counter.
345 *
346 * @param scope the scope counter to get the value of
347 * @return the value of a scope counter
348 */
349 private int value(Scope scope) {
350 Integer value = counts.get(scope);
351 if (value == null) {
352 value = 0;
353 }
354 return value;
355 }
356
357 /**
358 * Returns the surrounding scope definition (class, enum, etc.) which the method counts
359 * are connected to.
360 *
361 * @return the surrounding scope definition
362 */
363 private DetailAST getScopeDefinition() {
364 return scopeDefinition;
365 }
366
367 /**
368 * Fetches total number of methods.
369 *
370 * @return the total number of methods.
371 */
372 private int getTotal() {
373 return total;
374 }
375
376 }
377
378 }