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