001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2014  Oliver Burn
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////////////////////////////////////////////////////////////////////////////////
019package com.puppycrawl.tools.checkstyle.checks.sizes;
020
021import com.puppycrawl.tools.checkstyle.api.Check;
022import com.puppycrawl.tools.checkstyle.api.DetailAST;
023import com.puppycrawl.tools.checkstyle.api.FastStack;
024import com.puppycrawl.tools.checkstyle.api.Scope;
025import com.puppycrawl.tools.checkstyle.api.ScopeUtils;
026import com.puppycrawl.tools.checkstyle.api.TokenTypes;
027import java.util.EnumMap;
028
029/**
030 * Counts the methods of the type-definition and checks whether this
031 * count is higher than the configured limit.
032 * @author Alexander Jesse
033 * @author Oliver Burn
034 */
035public final class MethodCountCheck extends Check
036{
037    /**
038     * Marker class used to collect data about the number of methods per
039     * class. Objects of this class are used on the Stack to count the
040     * methods for each class and layer.
041     */
042    private static class MethodCounter
043    {
044        /** Maintains the counts. */
045        private final EnumMap<Scope, Integer> counts =
046            new EnumMap<Scope, Integer>(Scope.class);
047        /** indicated is an interface, in which case all methods are public */
048        private final boolean inInterface;
049        /** tracks the total. */
050        private int total;
051
052        /**
053         * Creates an interface.
054         * @param inInterface indicated if counter for an interface. In which
055         *        case, add all counts as public methods.
056         */
057        MethodCounter(boolean inInterface)
058        {
059            this.inInterface = inInterface;
060        }
061
062        /**
063         * Increments to counter by one for the supplied scope.
064         * @param scope the scope counter to increment.
065         */
066        void increment(Scope scope)
067        {
068            total++;
069            if (inInterface) {
070                counts.put(Scope.PUBLIC, 1 + value(Scope.PUBLIC));
071            }
072            else {
073                counts.put(scope, 1 + value(scope));
074            }
075        }
076
077        /**
078         * @return the value of a scope counter
079         * @param scope the scope counter to get the value of
080         */
081        int value(Scope scope)
082        {
083            final Integer value = counts.get(scope);
084            return (null == value) ? 0 : value;
085        }
086
087        /** @return the total number of methods. */
088        int getTotal()
089        {
090            return total;
091        }
092    };
093
094    /** default maximum number of methods */
095    private static final int DEFAULT_MAX_METHODS = 100;
096    /** Maximum private methods. */
097    private int maxPrivate = DEFAULT_MAX_METHODS;
098    /** Maximum package methods. */
099    private int maxPackage = DEFAULT_MAX_METHODS;
100    /** Maximum protected methods. */
101    private int maxProtected = DEFAULT_MAX_METHODS;
102    /** Maximum public methods. */
103    private int maxPublic = DEFAULT_MAX_METHODS;
104    /** Maximum total number of methods. */
105    private int maxTotal = DEFAULT_MAX_METHODS;
106    /** Maintains stack of counters, to support inner types. */
107    private final FastStack<MethodCounter> counters =
108        new FastStack<MethodCounter>();
109
110    @Override
111    public int[] getDefaultTokens()
112    {
113        return new int[] {
114            TokenTypes.CLASS_DEF,
115            TokenTypes.ENUM_CONSTANT_DEF,
116            TokenTypes.ENUM_DEF,
117            TokenTypes.INTERFACE_DEF,
118            TokenTypes.METHOD_DEF,
119        };
120    }
121
122    @Override
123    public void visitToken(DetailAST ast)
124    {
125        if ((TokenTypes.CLASS_DEF == ast.getType())
126            || (TokenTypes.INTERFACE_DEF == ast.getType())
127            || (TokenTypes.ENUM_CONSTANT_DEF == ast.getType())
128            || (TokenTypes.ENUM_DEF == ast.getType()))
129        {
130            counters.push(new MethodCounter(
131                TokenTypes.INTERFACE_DEF == ast.getType()));
132        }
133        else if (TokenTypes.METHOD_DEF == ast.getType()) {
134            raiseCounter(ast);
135        }
136    }
137
138    @Override
139    public void leaveToken(DetailAST ast)
140    {
141        if ((TokenTypes.CLASS_DEF == ast.getType())
142            || (TokenTypes.INTERFACE_DEF == ast.getType())
143            || (TokenTypes.ENUM_CONSTANT_DEF == ast.getType())
144            || (TokenTypes.ENUM_DEF == ast.getType()))
145        {
146            final MethodCounter counter = counters.pop();
147            checkCounters(counter, ast);
148        }
149    }
150
151    /**
152     * Determine the visibility modifier and raise the corresponding counter.
153     * @param method
154     *            The method-subtree from the AbstractSyntaxTree.
155     */
156    private void raiseCounter(DetailAST method)
157    {
158        final MethodCounter actualCounter = counters.peek();
159        final DetailAST temp = method.findFirstToken(TokenTypes.MODIFIERS);
160        final Scope scope = ScopeUtils.getScopeFromMods(temp);
161        actualCounter.increment(scope);
162    }
163
164    /**
165     * Check the counters and report violations.
166     * @param counter the method counters to check
167     * @param ast to report errors against.
168     */
169    private void checkCounters(MethodCounter counter, DetailAST ast)
170    {
171        checkMax(maxPrivate, counter.value(Scope.PRIVATE),
172                 "too.many.privateMethods", ast);
173        checkMax(maxPackage, counter.value(Scope.PACKAGE),
174                 "too.many.packageMethods", ast);
175        checkMax(maxProtected, counter.value(Scope.PROTECTED),
176                 "too.many.protectedMethods", ast);
177        checkMax(maxPublic, counter.value(Scope.PUBLIC),
178                 "too.many.publicMethods", ast);
179        checkMax(maxTotal, counter.getTotal(), "too.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    {
191        if (max < value) {
192            log(ast.getLineNo(), msg, value, max);
193        }
194    }
195
196    /**
197     * Sets the maximum allowed <code>private</code> methods per type.
198     * @param value the maximum allowed.
199     */
200    public void setMaxPrivate(int value)
201    {
202        maxPrivate = value;
203    }
204
205    /**
206     * Sets the maximum allowed <code>package</code> methods per type.
207     * @param value the maximum allowed.
208     */
209    public void setMaxPackage(int value)
210    {
211        maxPackage = value;
212    }
213
214    /**
215     * Sets the maximum allowed <code>protected</code> methods per type.
216     * @param value the maximum allowed.
217     */
218    public void setMaxProtected(int value)
219    {
220        maxProtected = value;
221    }
222
223    /**
224     * Sets the maximum allowed <code>public</code> methods per type.
225     * @param value the maximum allowed.
226     */
227    public void setMaxPublic(int value)
228    {
229        maxPublic = value;
230    }
231
232    /**
233     * Sets the maximum total methods per type.
234     * @param value the maximum allowed.
235     */
236    public void setMaxTotal(int value)
237    {
238        maxTotal = value;
239    }
240}