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