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   * {@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 }