001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2024 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 * <pre>
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 * </pre>
077 * <ul>
078 * <li>
079 * Property {@code maxPackage} - Specify the maximum number of {@code package} methods allowed.
080 * Type is {@code int}.
081 * Default value is {@code 100}.
082 * </li>
083 * <li>
084 * Property {@code maxPrivate} - Specify the maximum number of {@code private} methods allowed.
085 * Type is {@code int}.
086 * Default value is {@code 100}.
087 * </li>
088 * <li>
089 * Property {@code maxProtected} - Specify the maximum number of {@code protected} methods allowed.
090 * Type is {@code int}.
091 * Default value is {@code 100}.
092 * </li>
093 * <li>
094 * Property {@code maxPublic} - Specify the maximum number of {@code public} methods allowed.
095 * Type is {@code int}.
096 * Default value is {@code 100}.
097 * </li>
098 * <li>
099 * 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
151public 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}