View Javadoc
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   * <div class="wrapper"><pre class="prettyprint"><code class="language-java">
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   * </code></pre></div>
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     @Override
131     public int[] getDefaultTokens() {
132         return getAcceptableTokens();
133     }
134 
135     @Override
136     public int[] getAcceptableTokens() {
137         return new int[] {
138             TokenTypes.CLASS_DEF,
139             TokenTypes.ENUM_CONSTANT_DEF,
140             TokenTypes.ENUM_DEF,
141             TokenTypes.INTERFACE_DEF,
142             TokenTypes.ANNOTATION_DEF,
143             TokenTypes.METHOD_DEF,
144             TokenTypes.RECORD_DEF,
145             TokenTypes.COMPACT_COMPILATION_UNIT,
146         };
147     }
148 
149     @Override
150     public int[] getRequiredTokens() {
151         return new int[] {TokenTypes.METHOD_DEF};
152     }
153 
154     @Override
155     public void visitToken(DetailAST ast) {
156         if (ast.getType() == TokenTypes.METHOD_DEF) {
157             if (isInLatestScopeDefinition(ast)) {
158                 raiseCounter(ast);
159             }
160         }
161         else {
162             counters.push(new MethodCounter(ast));
163         }
164     }
165 
166     @Override
167     public void leaveToken(DetailAST ast) {
168         if (ast.getType() != TokenTypes.METHOD_DEF) {
169             final MethodCounter counter = counters.pop();
170 
171             checkCounters(counter, ast);
172         }
173     }
174 
175     /**
176      * Checks if there is a scope definition to check and that the method is found inside that scope
177      * (class, enum, etc.).
178      *
179      * @param methodDef
180      *        The method to analyze.
181      * @return {@code true} if the method is part of the latest scope definition and should be
182      *         counted.
183      */
184     private boolean isInLatestScopeDefinition(DetailAST methodDef) {
185         boolean result = false;
186 
187         if (!counters.isEmpty()) {
188             final DetailAST latestDefinition = counters.peek().getScopeDefinition();
189             final DetailAST methodParent = methodDef.getParent();
190             final DetailAST scopeDefinition;
191             if (methodParent.getType() == TokenTypes.COMPACT_COMPILATION_UNIT) {
192                 scopeDefinition = methodParent;
193             }
194             else {
195                 scopeDefinition = methodParent.getParent();
196             }
197 
198             result = latestDefinition == scopeDefinition;
199         }
200 
201         return result;
202     }
203 
204     /**
205      * Determine the visibility modifier and raise the corresponding counter.
206      *
207      * @param method
208      *            The method-subtree from the AbstractSyntaxTree.
209      */
210     private void raiseCounter(DetailAST method) {
211         final MethodCounter actualCounter = counters.peek();
212         final Scope scope = ScopeUtil.getScope(method);
213         actualCounter.increment(scope);
214     }
215 
216     /**
217      * Check the counters and report violations.
218      *
219      * @param counter the method counters to check
220      * @param ast to report violations against.
221      */
222     private void checkCounters(MethodCounter counter, DetailAST ast) {
223         checkMax(maxPrivate, counter.value(Scope.PRIVATE),
224                  MSG_PRIVATE_METHODS, ast);
225         checkMax(maxPackage, counter.value(Scope.PACKAGE),
226                  MSG_PACKAGE_METHODS, ast);
227         checkMax(maxProtected, counter.value(Scope.PROTECTED),
228                  MSG_PROTECTED_METHODS, ast);
229         checkMax(maxPublic, counter.value(Scope.PUBLIC),
230                  MSG_PUBLIC_METHODS, ast);
231         checkMax(maxTotal, counter.getTotal(), MSG_MANY_METHODS, ast);
232     }
233 
234     /**
235      * Utility for reporting if a maximum has been exceeded.
236      *
237      * @param max the maximum allowed value
238      * @param value the actual value
239      * @param msg the message to log. Takes two arguments of value and maximum.
240      * @param ast the AST to associate with the message.
241      */
242     private void checkMax(int max, int value, String msg, DetailAST ast) {
243         if (max < value) {
244             log(ast, msg, value, max);
245         }
246     }
247 
248     /**
249      * Setter to specify the maximum number of {@code private} methods allowed.
250      *
251      * @param value the maximum allowed.
252      * @since 5.3
253      */
254     public void setMaxPrivate(int value) {
255         maxPrivate = value;
256     }
257 
258     /**
259      * Setter to specify the maximum number of {@code package} methods allowed.
260      *
261      * @param value the maximum allowed.
262      * @since 5.3
263      */
264     public void setMaxPackage(int value) {
265         maxPackage = value;
266     }
267 
268     /**
269      * Setter to specify the maximum number of {@code protected} methods allowed.
270      *
271      * @param value the maximum allowed.
272      * @since 5.3
273      */
274     public void setMaxProtected(int value) {
275         maxProtected = value;
276     }
277 
278     /**
279      * Setter to specify the maximum number of {@code public} methods allowed.
280      *
281      * @param value the maximum allowed.
282      * @since 5.3
283      */
284     public void setMaxPublic(int value) {
285         maxPublic = value;
286     }
287 
288     /**
289      * Setter to specify the maximum number of methods allowed at all scope levels.
290      *
291      * @param value the maximum allowed.
292      * @since 5.3
293      */
294     public void setMaxTotal(int value) {
295         maxTotal = value;
296     }
297 
298     /**
299      * Marker class used to collect data about the number of methods per
300      * class. Objects of this class are used on the Stack to count the
301      * methods for each class and layer.
302      */
303     private static final class MethodCounter {
304 
305         /** Maintains the counts. */
306         private final Map<Scope, Integer> counts = new EnumMap<>(Scope.class);
307         /**
308          * The surrounding scope definition (class, enum, etc.) which the method counts are
309          * connected to.
310          */
311         private final DetailAST scopeDefinition;
312         /** Tracks the total. */
313         private int total;
314 
315         /**
316          * Creates an interface.
317          *
318          * @param scopeDefinition
319          *        The surrounding scope definition (class, enum, etc.) which to count all methods
320          *        for.
321          */
322         private MethodCounter(DetailAST scopeDefinition) {
323             this.scopeDefinition = scopeDefinition;
324         }
325 
326         /**
327          * Increments to counter by one for the supplied scope.
328          *
329          * @param scope the scope counter to increment.
330          */
331         private void increment(Scope scope) {
332             total++;
333             counts.put(scope, 1 + value(scope));
334         }
335 
336         /**
337          * Gets the value of a scope counter.
338          *
339          * @param scope the scope counter to get the value of
340          * @return the value of a scope counter
341          */
342         private int value(Scope scope) {
343             Integer value = counts.get(scope);
344             if (value == null) {
345                 value = 0;
346             }
347             return value;
348         }
349 
350         /**
351          * Returns the surrounding scope definition (class, enum, etc.) which the method counts
352          * are connected to.
353          *
354          * @return the surrounding scope definition
355          */
356         private DetailAST getScopeDefinition() {
357             return scopeDefinition;
358         }
359 
360         /**
361          * Fetches total number of methods.
362          *
363          * @return the total number of methods.
364          */
365         private int getTotal() {
366             return total;
367         }
368 
369     }
370 
371 }