View Javadoc
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  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   * <pre>
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   * </pre>
77   * <ul>
78   * <li>
79   * Property {@code maxPackage} - Specify the maximum number of {@code package} methods allowed.
80   * Type is {@code int}.
81   * Default value is {@code 100}.
82   * </li>
83   * <li>
84   * Property {@code maxPrivate} - Specify the maximum number of {@code private} methods allowed.
85   * Type is {@code int}.
86   * Default value is {@code 100}.
87   * </li>
88   * <li>
89   * Property {@code maxProtected} - Specify the maximum number of {@code protected} methods allowed.
90   * Type is {@code int}.
91   * Default value is {@code 100}.
92   * </li>
93   * <li>
94   * Property {@code maxPublic} - Specify the maximum number of {@code public} methods allowed.
95   * Type is {@code int}.
96   * Default value is {@code 100}.
97   * </li>
98   * <li>
99   * Property {@code maxTotal} - Specify the maximum number of methods allowed at all scope levels.
100  * Type is {@code int}.
101  * Default value is {@code 100}.
102  * </li>
103  * <li>
104  * Property {@code tokens} - tokens to check
105  * Type is {@code java.lang.String[]}.
106  * Validation type is {@code tokenSet}.
107  * Default value is:
108  * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CLASS_DEF">
109  * CLASS_DEF</a>,
110  * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ENUM_CONSTANT_DEF">
111  * ENUM_CONSTANT_DEF</a>,
112  * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ENUM_DEF">
113  * ENUM_DEF</a>,
114  * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#INTERFACE_DEF">
115  * INTERFACE_DEF</a>,
116  * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ANNOTATION_DEF">
117  * ANNOTATION_DEF</a>,
118  * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#RECORD_DEF">
119  * RECORD_DEF</a>.
120  * </li>
121  * </ul>
122  *
123  * <p>
124  * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
125  * </p>
126  *
127  * <p>
128  * Violation Message Keys:
129  * </p>
130  * <ul>
131  * <li>
132  * {@code too.many.methods}
133  * </li>
134  * <li>
135  * {@code too.many.packageMethods}
136  * </li>
137  * <li>
138  * {@code too.many.privateMethods}
139  * </li>
140  * <li>
141  * {@code too.many.protectedMethods}
142  * </li>
143  * <li>
144  * {@code too.many.publicMethods}
145  * </li>
146  * </ul>
147  *
148  * @since 5.3
149  */
150 @FileStatefulCheck
151 public final class MethodCountCheck extends AbstractCheck {
152 
153     /**
154      * A key is pointing to the warning message text in "messages.properties"
155      * file.
156      */
157     public static final String MSG_PRIVATE_METHODS = "too.many.privateMethods";
158 
159     /**
160      * A key is pointing to the warning message text in "messages.properties"
161      * file.
162      */
163     public static final String MSG_PACKAGE_METHODS = "too.many.packageMethods";
164 
165     /**
166      * A key is pointing to the warning message text in "messages.properties"
167      * file.
168      */
169     public static final String MSG_PROTECTED_METHODS = "too.many.protectedMethods";
170 
171     /**
172      * A key is pointing to the warning message text in "messages.properties"
173      * file.
174      */
175     public static final String MSG_PUBLIC_METHODS = "too.many.publicMethods";
176 
177     /**
178      * A key is pointing to the warning message text in "messages.properties"
179      * file.
180      */
181     public static final String MSG_MANY_METHODS = "too.many.methods";
182 
183     /** Default maximum number of methods. */
184     private static final int DEFAULT_MAX_METHODS = 100;
185 
186     /** Maintains stack of counters, to support inner types. */
187     private final Deque<MethodCounter> counters = new ArrayDeque<>();
188 
189     /** Specify the maximum number of {@code private} methods allowed. */
190     private int maxPrivate = DEFAULT_MAX_METHODS;
191     /** Specify the maximum number of {@code package} methods allowed. */
192     private int maxPackage = DEFAULT_MAX_METHODS;
193     /** Specify the maximum number of {@code protected} methods allowed. */
194     private int maxProtected = DEFAULT_MAX_METHODS;
195     /** Specify the maximum number of {@code public} methods allowed. */
196     private int maxPublic = DEFAULT_MAX_METHODS;
197     /** Specify the maximum number of methods allowed at all scope levels. */
198     private int maxTotal = DEFAULT_MAX_METHODS;
199 
200     @Override
201     public int[] getDefaultTokens() {
202         return getAcceptableTokens();
203     }
204 
205     @Override
206     public int[] getAcceptableTokens() {
207         return new int[] {
208             TokenTypes.CLASS_DEF,
209             TokenTypes.ENUM_CONSTANT_DEF,
210             TokenTypes.ENUM_DEF,
211             TokenTypes.INTERFACE_DEF,
212             TokenTypes.ANNOTATION_DEF,
213             TokenTypes.METHOD_DEF,
214             TokenTypes.RECORD_DEF,
215         };
216     }
217 
218     @Override
219     public int[] getRequiredTokens() {
220         return new int[] {TokenTypes.METHOD_DEF};
221     }
222 
223     @Override
224     public void visitToken(DetailAST ast) {
225         if (ast.getType() == TokenTypes.METHOD_DEF) {
226             if (isInLatestScopeDefinition(ast)) {
227                 raiseCounter(ast);
228             }
229         }
230         else {
231             counters.push(new MethodCounter(ast));
232         }
233     }
234 
235     @Override
236     public void leaveToken(DetailAST ast) {
237         if (ast.getType() != TokenTypes.METHOD_DEF) {
238             final MethodCounter counter = counters.pop();
239 
240             checkCounters(counter, ast);
241         }
242     }
243 
244     /**
245      * Checks if there is a scope definition to check and that the method is found inside that scope
246      * (class, enum, etc.).
247      *
248      * @param methodDef
249      *        The method to analyze.
250      * @return {@code true} if the method is part of the latest scope definition and should be
251      *         counted.
252      */
253     private boolean isInLatestScopeDefinition(DetailAST methodDef) {
254         boolean result = false;
255 
256         if (!counters.isEmpty()) {
257             final DetailAST latestDefinition = counters.peek().getScopeDefinition();
258 
259             result = latestDefinition == methodDef.getParent().getParent();
260         }
261 
262         return result;
263     }
264 
265     /**
266      * Determine the visibility modifier and raise the corresponding counter.
267      *
268      * @param method
269      *            The method-subtree from the AbstractSyntaxTree.
270      */
271     private void raiseCounter(DetailAST method) {
272         final MethodCounter actualCounter = counters.peek();
273         final Scope scope = ScopeUtil.getScope(method);
274         actualCounter.increment(scope);
275     }
276 
277     /**
278      * Check the counters and report violations.
279      *
280      * @param counter the method counters to check
281      * @param ast to report violations against.
282      */
283     private void checkCounters(MethodCounter counter, DetailAST ast) {
284         checkMax(maxPrivate, counter.value(Scope.PRIVATE),
285                  MSG_PRIVATE_METHODS, ast);
286         checkMax(maxPackage, counter.value(Scope.PACKAGE),
287                  MSG_PACKAGE_METHODS, ast);
288         checkMax(maxProtected, counter.value(Scope.PROTECTED),
289                  MSG_PROTECTED_METHODS, ast);
290         checkMax(maxPublic, counter.value(Scope.PUBLIC),
291                  MSG_PUBLIC_METHODS, ast);
292         checkMax(maxTotal, counter.getTotal(), MSG_MANY_METHODS, ast);
293     }
294 
295     /**
296      * Utility for reporting if a maximum has been exceeded.
297      *
298      * @param max the maximum allowed value
299      * @param value the actual value
300      * @param msg the message to log. Takes two arguments of value and maximum.
301      * @param ast the AST to associate with the message.
302      */
303     private void checkMax(int max, int value, String msg, DetailAST ast) {
304         if (max < value) {
305             log(ast, msg, value, max);
306         }
307     }
308 
309     /**
310      * Setter to specify the maximum number of {@code private} methods allowed.
311      *
312      * @param value the maximum allowed.
313      * @since 5.3
314      */
315     public void setMaxPrivate(int value) {
316         maxPrivate = value;
317     }
318 
319     /**
320      * Setter to specify the maximum number of {@code package} methods allowed.
321      *
322      * @param value the maximum allowed.
323      * @since 5.3
324      */
325     public void setMaxPackage(int value) {
326         maxPackage = value;
327     }
328 
329     /**
330      * Setter to specify the maximum number of {@code protected} methods allowed.
331      *
332      * @param value the maximum allowed.
333      * @since 5.3
334      */
335     public void setMaxProtected(int value) {
336         maxProtected = value;
337     }
338 
339     /**
340      * Setter to specify the maximum number of {@code public} methods allowed.
341      *
342      * @param value the maximum allowed.
343      * @since 5.3
344      */
345     public void setMaxPublic(int value) {
346         maxPublic = value;
347     }
348 
349     /**
350      * Setter to specify the maximum number of methods allowed at all scope levels.
351      *
352      * @param value the maximum allowed.
353      * @since 5.3
354      */
355     public void setMaxTotal(int value) {
356         maxTotal = value;
357     }
358 
359     /**
360      * Marker class used to collect data about the number of methods per
361      * class. Objects of this class are used on the Stack to count the
362      * methods for each class and layer.
363      */
364     private static final class MethodCounter {
365 
366         /** Maintains the counts. */
367         private final Map<Scope, Integer> counts = new EnumMap<>(Scope.class);
368         /**
369          * The surrounding scope definition (class, enum, etc.) which the method counts are
370          * connected to.
371          */
372         private final DetailAST scopeDefinition;
373         /** Tracks the total. */
374         private int total;
375 
376         /**
377          * Creates an interface.
378          *
379          * @param scopeDefinition
380          *        The surrounding scope definition (class, enum, etc.) which to count all methods
381          *        for.
382          */
383         private MethodCounter(DetailAST scopeDefinition) {
384             this.scopeDefinition = scopeDefinition;
385         }
386 
387         /**
388          * Increments to counter by one for the supplied scope.
389          *
390          * @param scope the scope counter to increment.
391          */
392         private void increment(Scope scope) {
393             total++;
394             counts.put(scope, 1 + value(scope));
395         }
396 
397         /**
398          * Gets the value of a scope counter.
399          *
400          * @param scope the scope counter to get the value of
401          * @return the value of a scope counter
402          */
403         private int value(Scope scope) {
404             Integer value = counts.get(scope);
405             if (value == null) {
406                 value = 0;
407             }
408             return value;
409         }
410 
411         /**
412          * Returns the surrounding scope definition (class, enum, etc.) which the method counts
413          * are connected to.
414          *
415          * @return the surrounding scope definition
416          */
417         private DetailAST getScopeDefinition() {
418             return scopeDefinition;
419         }
420 
421         /**
422          * Fetches total number of methods.
423          *
424          * @return the total number of methods.
425          */
426         private int getTotal() {
427             return total;
428         }
429 
430     }
431 
432 }