001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2018 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.ScopeUtils;
033
034/**
035 * Checks the number of methods declared in each type declaration by access
036 * modifier or total count.
037 * @author Alexander Jesse
038 * @author Oliver Burn
039 */
040@FileStatefulCheck
041public final class MethodCountCheck extends AbstractCheck {
042
043    /**
044     * A key is pointing to the warning message text in "messages.properties"
045     * file.
046     */
047    public static final String MSG_PRIVATE_METHODS = "too.many.privateMethods";
048
049    /**
050     * A key is pointing to the warning message text in "messages.properties"
051     * file.
052     */
053    public static final String MSG_PACKAGE_METHODS = "too.many.packageMethods";
054
055    /**
056     * A key is pointing to the warning message text in "messages.properties"
057     * file.
058     */
059    public static final String MSG_PROTECTED_METHODS = "too.many.protectedMethods";
060
061    /**
062     * A key is pointing to the warning message text in "messages.properties"
063     * file.
064     */
065    public static final String MSG_PUBLIC_METHODS = "too.many.publicMethods";
066
067    /**
068     * A key is pointing to the warning message text in "messages.properties"
069     * file.
070     */
071    public static final String MSG_MANY_METHODS = "too.many.methods";
072
073    /** Default maximum number of methods. */
074    private static final int DEFAULT_MAX_METHODS = 100;
075
076    /** Maintains stack of counters, to support inner types. */
077    private final Deque<MethodCounter> counters = new ArrayDeque<>();
078
079    /** Maximum private methods. */
080    private int maxPrivate = DEFAULT_MAX_METHODS;
081    /** Maximum package methods. */
082    private int maxPackage = DEFAULT_MAX_METHODS;
083    /** Maximum protected methods. */
084    private int maxProtected = DEFAULT_MAX_METHODS;
085    /** Maximum public methods. */
086    private int maxPublic = DEFAULT_MAX_METHODS;
087    /** Maximum total number of methods. */
088    private int maxTotal = DEFAULT_MAX_METHODS;
089
090    @Override
091    public int[] getDefaultTokens() {
092        return getAcceptableTokens();
093    }
094
095    @Override
096    public int[] getAcceptableTokens() {
097        return new int[] {
098            TokenTypes.CLASS_DEF,
099            TokenTypes.ENUM_CONSTANT_DEF,
100            TokenTypes.ENUM_DEF,
101            TokenTypes.INTERFACE_DEF,
102            TokenTypes.ANNOTATION_DEF,
103            TokenTypes.METHOD_DEF,
104        };
105    }
106
107    @Override
108    public int[] getRequiredTokens() {
109        return new int[] {TokenTypes.METHOD_DEF};
110    }
111
112    @Override
113    public void visitToken(DetailAST ast) {
114        if (ast.getType() == TokenTypes.METHOD_DEF) {
115            if (isInLatestScopeDefinition(ast)) {
116                raiseCounter(ast);
117            }
118        }
119        else {
120            counters.push(new MethodCounter(ast));
121        }
122    }
123
124    @Override
125    public void leaveToken(DetailAST ast) {
126        if (ast.getType() != TokenTypes.METHOD_DEF) {
127            final MethodCounter counter = counters.pop();
128
129            checkCounters(counter, ast);
130        }
131    }
132
133    /**
134     * Checks if there is a scope definition to check and that the method is found inside that scope
135     * (class, enum, etc.).
136     * @param methodDef
137     *        The method to analyze.
138     * @return {@code true} if the method is part of the latest scope definition and should be
139     *         counted.
140     */
141    private boolean isInLatestScopeDefinition(DetailAST methodDef) {
142        boolean result = false;
143
144        if (!counters.isEmpty()) {
145            final DetailAST latestDefinition = counters.peek().getScopeDefinition();
146
147            result = latestDefinition == methodDef.getParent().getParent();
148        }
149
150        return result;
151    }
152
153    /**
154     * Determine the visibility modifier and raise the corresponding counter.
155     * @param method
156     *            The method-subtree from the AbstractSyntaxTree.
157     */
158    private void raiseCounter(DetailAST method) {
159        final MethodCounter actualCounter = counters.peek();
160        final DetailAST temp = method.findFirstToken(TokenTypes.MODIFIERS);
161        final Scope scope = ScopeUtils.getScopeFromMods(temp);
162        actualCounter.increment(scope);
163    }
164
165    /**
166     * Check the counters and report violations.
167     * @param counter the method counters to check
168     * @param ast to report errors against.
169     */
170    private void checkCounters(MethodCounter counter, DetailAST ast) {
171        checkMax(maxPrivate, counter.value(Scope.PRIVATE),
172                 MSG_PRIVATE_METHODS, ast);
173        checkMax(maxPackage, counter.value(Scope.PACKAGE),
174                 MSG_PACKAGE_METHODS, ast);
175        checkMax(maxProtected, counter.value(Scope.PROTECTED),
176                 MSG_PROTECTED_METHODS, ast);
177        checkMax(maxPublic, counter.value(Scope.PUBLIC),
178                 MSG_PUBLIC_METHODS, ast);
179        checkMax(maxTotal, counter.getTotal(), MSG_MANY_METHODS, ast);
180    }
181
182    /**
183     * Utility for reporting if a maximum has been exceeded.
184     * @param max the maximum allowed value
185     * @param value the actual value
186     * @param msg the message to log. Takes two arguments of value and maximum.
187     * @param ast the AST to associate with the message.
188     */
189    private void checkMax(int max, int value, String msg, DetailAST ast) {
190        if (max < value) {
191            log(ast.getLineNo(), msg, value, max);
192        }
193    }
194
195    /**
196     * Sets the maximum allowed {@code private} methods per type.
197     * @param value the maximum allowed.
198     */
199    public void setMaxPrivate(int value) {
200        maxPrivate = value;
201    }
202
203    /**
204     * Sets the maximum allowed {@code package} methods per type.
205     * @param value the maximum allowed.
206     */
207    public void setMaxPackage(int value) {
208        maxPackage = value;
209    }
210
211    /**
212     * Sets the maximum allowed {@code protected} methods per type.
213     * @param value the maximum allowed.
214     */
215    public void setMaxProtected(int value) {
216        maxProtected = value;
217    }
218
219    /**
220     * Sets the maximum allowed {@code public} methods per type.
221     * @param value the maximum allowed.
222     */
223    public void setMaxPublic(int value) {
224        maxPublic = value;
225    }
226
227    /**
228     * Sets the maximum total methods per type.
229     * @param value the maximum allowed.
230     */
231    public void setMaxTotal(int value) {
232        maxTotal = value;
233    }
234
235    /**
236     * Marker class used to collect data about the number of methods per
237     * class. Objects of this class are used on the Stack to count the
238     * methods for each class and layer.
239     */
240    private static class MethodCounter {
241
242        /** Maintains the counts. */
243        private final Map<Scope, Integer> counts = new EnumMap<>(Scope.class);
244        /** Indicated is an interface, in which case all methods are public. */
245        private final boolean inInterface;
246        /**
247         * The surrounding scope definition (class, enum, etc.) which the method counts are connect
248         * to.
249         */
250        private final DetailAST scopeDefinition;
251        /** Tracks the total. */
252        private int total;
253
254        /**
255         * Creates an interface.
256         * @param scopeDefinition
257         *        The surrounding scope definition (class, enum, etc.) which to count all methods
258         *        for.
259         */
260        MethodCounter(DetailAST scopeDefinition) {
261            this.scopeDefinition = scopeDefinition;
262            inInterface = scopeDefinition.getType() == TokenTypes.INTERFACE_DEF;
263        }
264
265        /**
266         * Increments to counter by one for the supplied scope.
267         * @param scope the scope counter to increment.
268         */
269        private void increment(Scope scope) {
270            total++;
271            if (inInterface) {
272                counts.put(Scope.PUBLIC, 1 + value(Scope.PUBLIC));
273            }
274            else {
275                counts.put(scope, 1 + value(scope));
276            }
277        }
278
279        /**
280         * Gets the value of a scope counter.
281         * @param scope the scope counter to get the value of
282         * @return the value of a scope counter
283         */
284        private int value(Scope scope) {
285            Integer value = counts.get(scope);
286            if (value == null) {
287                value = 0;
288            }
289            return value;
290        }
291
292        private DetailAST getScopeDefinition() {
293            return scopeDefinition;
294        }
295
296        /**
297         * Fetches total number of methods.
298         * @return the total number of methods.
299         */
300        private int getTotal() {
301            return total;
302        }
303
304    }
305
306}