001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2026 the original author or authors.
004//
005// This library is free software; you can redistribute it and/or
006// modify it under the terms of the GNU Lesser General Public
007// License as published by the Free Software Foundation; either
008// version 2.1 of the License, or (at your option) any later version.
009//
010// This library is distributed in the hope that it will be useful,
011// but WITHOUT ANY WARRANTY; without even the implied warranty of
012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013// Lesser General Public License for more details.
014//
015// You should have received a copy of the GNU Lesser General Public
016// License along with this library; if not, write to the Free Software
017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
018///////////////////////////////////////////////////////////////////////////////////////////////
019
020package com.puppycrawl.tools.checkstyle.checks.sizes;
021
022import java.util.ArrayDeque;
023import java.util.Deque;
024import java.util.EnumMap;
025import java.util.Map;
026
027import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
028import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
029import com.puppycrawl.tools.checkstyle.api.DetailAST;
030import com.puppycrawl.tools.checkstyle.api.Scope;
031import com.puppycrawl.tools.checkstyle.api.TokenTypes;
032import com.puppycrawl.tools.checkstyle.utils.ScopeUtil;
033
034/**
035 * <div>
036 * Checks the number of methods declared in each type declaration by access modifier
037 * or total count.
038 * </div>
039 *
040 * <p>
041 * This check can be configured to flag classes that define too many methods
042 * to prevent the class from getting too complex. Counting can be customized
043 * to prevent too many total methods in a type definition ({@code maxTotal}),
044 * or to prevent too many methods of a specific access modifier ({@code private},
045 * {@code package}, {@code protected} or {@code public}). Each count is completely
046 * separated to customize how many methods of each you want to allow. For example,
047 * specifying a {@code maxTotal} of 10, still means you can prevent more than 0
048 * {@code maxPackage} methods. A violation won't appear for 8 public methods,
049 * but one will appear if there is also 3 private methods or any package-private methods.
050 * </p>
051 *
052 * <p>
053 * Methods defined in anonymous classes are not counted towards any totals.
054 * Counts only go towards the main type declaration parent, and are kept separate
055 * from it's children's inner types.
056 * </p>
057 * <div class="wrapper"><pre class="prettyprint"><code class="language-java">
058 * public class ExampleClass {
059 *   public enum Colors {
060 *     RED, GREEN, YELLOW;
061 *
062 *     public String getRGB() { ... } // NOT counted towards ExampleClass
063 *   }
064 *
065 *   public void example() { // counted towards ExampleClass
066 *     Runnable r = (new Runnable() {
067 *       public void run() { ... } // NOT counted towards ExampleClass, won't produce any violations
068 *     });
069 *   }
070 *
071 *   public static class InnerExampleClass {
072 *     protected void example2() { ... } // NOT counted towards ExampleClass,
073 *                                    // but counted towards InnerExampleClass
074 *   }
075 * }
076 * </code></pre></div>
077 *
078 * @since 5.3
079 */
080@FileStatefulCheck
081public final class MethodCountCheck extends AbstractCheck {
082
083    /**
084     * A key is pointing to the warning message text in "messages.properties"
085     * file.
086     */
087    public static final String MSG_PRIVATE_METHODS = "too.many.privateMethods";
088
089    /**
090     * A key is pointing to the warning message text in "messages.properties"
091     * file.
092     */
093    public static final String MSG_PACKAGE_METHODS = "too.many.packageMethods";
094
095    /**
096     * A key is pointing to the warning message text in "messages.properties"
097     * file.
098     */
099    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}