com.puppycrawl.tools.checkstyle.checks.sizes.MethodCountCheck Maven / Gradle / Ivy
Show all versions of checkstyle Show documentation
////////////////////////////////////////////////////////////////////////////////
// checkstyle: Checks Java source code for adherence to a set of rules.
// Copyright (C) 2001-2019 the original author or authors.
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
////////////////////////////////////////////////////////////////////////////////
package com.puppycrawl.tools.checkstyle.checks.sizes;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.EnumMap;
import java.util.Map;
import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
import com.puppycrawl.tools.checkstyle.api.DetailAST;
import com.puppycrawl.tools.checkstyle.api.Scope;
import com.puppycrawl.tools.checkstyle.api.TokenTypes;
import com.puppycrawl.tools.checkstyle.utils.ScopeUtil;
/**
*
* Checks the number of methods declared in each type declaration by access modifier
* or total count.
*
*
* This check can be configured to flag classes that define too many methods
* to prevent the class from getting too complex. Counting can be customized
* to prevent too many total methods in a type definition ({@code maxTotal}),
* or to prevent too many methods of a specific access modifier ({@code private},
* {@code package}, {@code protected} or {@code public}). Each count is completely
* separated to customize how many methods of each you want to allow. For example,
* specifying a {@code maxTotal} of 10, still means you can prevent more than 0
* {@code maxPackage} methods. A violation won't appear for 8 public methods,
* but one will appear if there is also 3 private methods or any package-private methods.
*
*
* Methods defined in anonymous classes are not counted towards any totals.
* Counts only go towards the main type declaration parent, and are kept separate
* from it's children's inner types.
*
*
* public class ExampleClass {
* public enum Colors {
* RED, GREEN, YELLOW;
*
* public String getRGB() { ... } // NOT counted towards ExampleClass
* }
*
* public void example() { // counted towards ExampleClass
* Runnable r = (new Runnable() {
* public void run() { ... } // NOT counted towards ExampleClass, won't produce any violations
* });
* }
*
* public static class InnerExampleClass {
* protected void example2() { ... } // NOT counted towards ExampleClass,
* // but counted towards InnerExampleClass
* }
* }
*
*
* -
* Property {@code maxTotal} - Specify the maximum number of methods allowed at all scope levels.
* Default value is {@code 100}.
*
* -
* Property {@code maxPrivate} - Specify the maximum number of {@code private} methods allowed.
* Default value is {@code 100}.
*
* -
* Property {@code maxPackage} - Specify the maximum number of {@code package} methods allowed.
* Default value is {@code 100}.
*
* -
* Property {@code maxProtected} - Specify the maximum number of {@code protected} methods allowed.
* Default value is 100.
*
* -
* Property {@code maxPublic} - Specify the maximum number of {@code public} methods allowed.
* Default value is {@code 100}.
*
* -
* Property {@code tokens} - tokens to check Default value is:
*
* CLASS_DEF,
*
* ENUM_CONSTANT_DEF,
*
* ENUM_DEF,
*
* INTERFACE_DEF,
*
* ANNOTATION_DEF.
*
*
*
* To configure the check with defaults:
*
*
* <module name="MethodCount"/>
*
*
* To configure the check to allow no more than 30 methods per type declaration:
*
*
* <module name="MethodCount">
* <property name="maxTotal" value="30"/>
* </module>
*
*
* To configure the check to allow no more than 10 public methods per type declaration,
* and 40 methods in total:
*
*
* <module name="MethodCount">
* <property name="maxPublic" value="10"/>
* <property name="maxTotal" value="40"/>
* </module>
*
*
* @since 5.3
*/
@FileStatefulCheck
public final class MethodCountCheck extends AbstractCheck {
/**
* A key is pointing to the warning message text in "messages.properties"
* file.
*/
public static final String MSG_PRIVATE_METHODS = "too.many.privateMethods";
/**
* A key is pointing to the warning message text in "messages.properties"
* file.
*/
public static final String MSG_PACKAGE_METHODS = "too.many.packageMethods";
/**
* A key is pointing to the warning message text in "messages.properties"
* file.
*/
public static final String MSG_PROTECTED_METHODS = "too.many.protectedMethods";
/**
* A key is pointing to the warning message text in "messages.properties"
* file.
*/
public static final String MSG_PUBLIC_METHODS = "too.many.publicMethods";
/**
* A key is pointing to the warning message text in "messages.properties"
* file.
*/
public static final String MSG_MANY_METHODS = "too.many.methods";
/** Default maximum number of methods. */
private static final int DEFAULT_MAX_METHODS = 100;
/** Maintains stack of counters, to support inner types. */
private final Deque counters = new ArrayDeque<>();
/** Specify the maximum number of {@code private} methods allowed. */
private int maxPrivate = DEFAULT_MAX_METHODS;
/** Specify the maximum number of {@code package} methods allowed. */
private int maxPackage = DEFAULT_MAX_METHODS;
/** Specify the maximum number of {@code protected} methods allowed. */
private int maxProtected = DEFAULT_MAX_METHODS;
/** Specify the maximum number of {@code public} methods allowed. */
private int maxPublic = DEFAULT_MAX_METHODS;
/** Specify the maximum number of methods allowed at all scope levels. */
private int maxTotal = DEFAULT_MAX_METHODS;
@Override
public int[] getDefaultTokens() {
return getAcceptableTokens();
}
@Override
public int[] getAcceptableTokens() {
return new int[] {
TokenTypes.CLASS_DEF,
TokenTypes.ENUM_CONSTANT_DEF,
TokenTypes.ENUM_DEF,
TokenTypes.INTERFACE_DEF,
TokenTypes.ANNOTATION_DEF,
TokenTypes.METHOD_DEF,
};
}
@Override
public int[] getRequiredTokens() {
return new int[] {TokenTypes.METHOD_DEF};
}
@Override
public void visitToken(DetailAST ast) {
if (ast.getType() == TokenTypes.METHOD_DEF) {
if (isInLatestScopeDefinition(ast)) {
raiseCounter(ast);
}
}
else {
counters.push(new MethodCounter(ast));
}
}
@Override
public void leaveToken(DetailAST ast) {
if (ast.getType() != TokenTypes.METHOD_DEF) {
final MethodCounter counter = counters.pop();
checkCounters(counter, ast);
}
}
/**
* Checks if there is a scope definition to check and that the method is found inside that scope
* (class, enum, etc.).
* @param methodDef
* The method to analyze.
* @return {@code true} if the method is part of the latest scope definition and should be
* counted.
*/
private boolean isInLatestScopeDefinition(DetailAST methodDef) {
boolean result = false;
if (!counters.isEmpty()) {
final DetailAST latestDefinition = counters.peek().getScopeDefinition();
result = latestDefinition == methodDef.getParent().getParent();
}
return result;
}
/**
* Determine the visibility modifier and raise the corresponding counter.
* @param method
* The method-subtree from the AbstractSyntaxTree.
*/
private void raiseCounter(DetailAST method) {
final MethodCounter actualCounter = counters.peek();
final DetailAST temp = method.findFirstToken(TokenTypes.MODIFIERS);
final Scope scope = ScopeUtil.getScopeFromMods(temp);
actualCounter.increment(scope);
}
/**
* Check the counters and report violations.
* @param counter the method counters to check
* @param ast to report violations against.
*/
private void checkCounters(MethodCounter counter, DetailAST ast) {
checkMax(maxPrivate, counter.value(Scope.PRIVATE),
MSG_PRIVATE_METHODS, ast);
checkMax(maxPackage, counter.value(Scope.PACKAGE),
MSG_PACKAGE_METHODS, ast);
checkMax(maxProtected, counter.value(Scope.PROTECTED),
MSG_PROTECTED_METHODS, ast);
checkMax(maxPublic, counter.value(Scope.PUBLIC),
MSG_PUBLIC_METHODS, ast);
checkMax(maxTotal, counter.getTotal(), MSG_MANY_METHODS, ast);
}
/**
* Utility for reporting if a maximum has been exceeded.
* @param max the maximum allowed value
* @param value the actual value
* @param msg the message to log. Takes two arguments of value and maximum.
* @param ast the AST to associate with the message.
*/
private void checkMax(int max, int value, String msg, DetailAST ast) {
if (max < value) {
log(ast.getLineNo(), msg, value, max);
}
}
/**
* Setter to specify the maximum number of {@code private} methods allowed.
*
* @param value the maximum allowed.
*/
public void setMaxPrivate(int value) {
maxPrivate = value;
}
/**
* Setter to specify the maximum number of {@code package} methods allowed.
*
* @param value the maximum allowed.
*/
public void setMaxPackage(int value) {
maxPackage = value;
}
/**
* Setter to specify the maximum number of {@code protected} methods allowed.
*
* @param value the maximum allowed.
*/
public void setMaxProtected(int value) {
maxProtected = value;
}
/**
* Setter to specify the maximum number of {@code public} methods allowed.
*
* @param value the maximum allowed.
*/
public void setMaxPublic(int value) {
maxPublic = value;
}
/**
* Setter to specify the maximum number of methods allowed at all scope levels.
*
* @param value the maximum allowed.
*/
public void setMaxTotal(int value) {
maxTotal = value;
}
/**
* Marker class used to collect data about the number of methods per
* class. Objects of this class are used on the Stack to count the
* methods for each class and layer.
*/
private static class MethodCounter {
/** Maintains the counts. */
private final Map counts = new EnumMap<>(Scope.class);
/** Indicated is an interface, in which case all methods are public. */
private final boolean inInterface;
/**
* The surrounding scope definition (class, enum, etc.) which the method counts are connect
* to.
*/
private final DetailAST scopeDefinition;
/** Tracks the total. */
private int total;
/**
* Creates an interface.
* @param scopeDefinition
* The surrounding scope definition (class, enum, etc.) which to count all methods
* for.
*/
/* package */ MethodCounter(DetailAST scopeDefinition) {
this.scopeDefinition = scopeDefinition;
inInterface = scopeDefinition.getType() == TokenTypes.INTERFACE_DEF;
}
/**
* Increments to counter by one for the supplied scope.
* @param scope the scope counter to increment.
*/
private void increment(Scope scope) {
total++;
if (inInterface) {
counts.put(Scope.PUBLIC, 1 + value(Scope.PUBLIC));
}
else {
counts.put(scope, 1 + value(scope));
}
}
/**
* Gets the value of a scope counter.
* @param scope the scope counter to get the value of
* @return the value of a scope counter
*/
private int value(Scope scope) {
Integer value = counts.get(scope);
if (value == null) {
value = 0;
}
return value;
}
private DetailAST getScopeDefinition() {
return scopeDefinition;
}
/**
* Fetches total number of methods.
* @return the total number of methods.
*/
private int getTotal() {
return total;
}
}
}