001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2019 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 * <p> 036 * Checks the number of methods declared in each type declaration by access modifier 037 * or total count. 038 * </p> 039 * <p> 040 * This check can be configured to flag classes that define too many methods 041 * to prevent the class from getting too complex. Counting can be customized 042 * to prevent too many total methods in a type definition ({@code maxTotal}), 043 * or to prevent too many methods of a specific access modifier ({@code private}, 044 * {@code package}, {@code protected} or {@code public}). Each count is completely 045 * separated to customize how many methods of each you want to allow. For example, 046 * specifying a {@code maxTotal} of 10, still means you can prevent more than 0 047 * {@code maxPackage} methods. A violation won't appear for 8 public methods, 048 * but one will appear if there is also 3 private methods or any package-private methods. 049 * </p> 050 * <p> 051 * Methods defined in anonymous classes are not counted towards any totals. 052 * Counts only go towards the main type declaration parent, and are kept separate 053 * from it's children's inner types. 054 * </p> 055 * <pre> 056 * public class ExampleClass { 057 * public enum Colors { 058 * RED, GREEN, YELLOW; 059 * 060 * public String getRGB() { ... } // NOT counted towards ExampleClass 061 * } 062 * 063 * public void example() { // counted towards ExampleClass 064 * Runnable r = (new Runnable() { 065 * public void run() { ... } // NOT counted towards ExampleClass, won't produce any violations 066 * }); 067 * } 068 * 069 * public static class InnerExampleClass { 070 * protected void example2() { ... } // NOT counted towards ExampleClass, 071 * // but counted towards InnerExampleClass 072 * } 073 * } 074 * </pre> 075 * <ul> 076 * <li> 077 * Property {@code maxTotal} - Specify the maximum number of methods allowed at all scope levels. 078 * Default value is {@code 100}. 079 * </li> 080 * <li> 081 * Property {@code maxPrivate} - Specify the maximum number of {@code private} methods allowed. 082 * Default value is {@code 100}. 083 * </li> 084 * <li> 085 * Property {@code maxPackage} - Specify the maximum number of {@code package} methods allowed. 086 * Default value is {@code 100}. 087 * </li> 088 * <li> 089 * Property {@code maxProtected} - Specify the maximum number of {@code protected} methods allowed. 090 * Default value is 100. 091 * </li> 092 * <li> 093 * Property {@code maxPublic} - Specify the maximum number of {@code public} methods allowed. 094 * Default value is {@code 100}. 095 * </li> 096 * <li> 097 * Property {@code tokens} - tokens to check Default value is: 098 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CLASS_DEF"> 099 * CLASS_DEF</a>, 100 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ENUM_CONSTANT_DEF"> 101 * ENUM_CONSTANT_DEF</a>, 102 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ENUM_DEF"> 103 * ENUM_DEF</a>, 104 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#INTERFACE_DEF"> 105 * INTERFACE_DEF</a>, 106 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#INTERFACE_DEF"> 107 * ANNOTATION_DEF</a>. 108 * </li> 109 * </ul> 110 * <p> 111 * To configure the check with defaults: 112 * </p> 113 * <pre> 114 * <module name="MethodCount"/> 115 * </pre> 116 * <p> 117 * To configure the check to allow no more than 30 methods per type declaration: 118 * </p> 119 * <pre> 120 * <module name="MethodCount"> 121 * <property name="maxTotal" value="30"/> 122 * </module> 123 * </pre> 124 * <p> 125 * To configure the check to allow no more than 10 public methods per type declaration, 126 * and 40 methods in total: 127 * </p> 128 * <pre> 129 * <module name="MethodCount"> 130 * <property name="maxPublic" value="10"/> 131 * <property name="maxTotal" value="40"/> 132 * </module> 133 * </pre> 134 * 135 * @since 5.3 136 */ 137@FileStatefulCheck 138public final class MethodCountCheck extends AbstractCheck { 139 140 /** 141 * A key is pointing to the warning message text in "messages.properties" 142 * file. 143 */ 144 public static final String MSG_PRIVATE_METHODS = "too.many.privateMethods"; 145 146 /** 147 * A key is pointing to the warning message text in "messages.properties" 148 * file. 149 */ 150 public static final String MSG_PACKAGE_METHODS = "too.many.packageMethods"; 151 152 /** 153 * A key is pointing to the warning message text in "messages.properties" 154 * file. 155 */ 156 public static final String MSG_PROTECTED_METHODS = "too.many.protectedMethods"; 157 158 /** 159 * A key is pointing to the warning message text in "messages.properties" 160 * file. 161 */ 162 public static final String MSG_PUBLIC_METHODS = "too.many.publicMethods"; 163 164 /** 165 * A key is pointing to the warning message text in "messages.properties" 166 * file. 167 */ 168 public static final String MSG_MANY_METHODS = "too.many.methods"; 169 170 /** Default maximum number of methods. */ 171 private static final int DEFAULT_MAX_METHODS = 100; 172 173 /** Maintains stack of counters, to support inner types. */ 174 private final Deque<MethodCounter> counters = new ArrayDeque<>(); 175 176 /** Specify the maximum number of {@code private} methods allowed. */ 177 private int maxPrivate = DEFAULT_MAX_METHODS; 178 /** Specify the maximum number of {@code package} methods allowed. */ 179 private int maxPackage = DEFAULT_MAX_METHODS; 180 /** Specify the maximum number of {@code protected} methods allowed. */ 181 private int maxProtected = DEFAULT_MAX_METHODS; 182 /** Specify the maximum number of {@code public} methods allowed. */ 183 private int maxPublic = DEFAULT_MAX_METHODS; 184 /** Specify the maximum number of methods allowed at all scope levels. */ 185 private int maxTotal = DEFAULT_MAX_METHODS; 186 187 @Override 188 public int[] getDefaultTokens() { 189 return getAcceptableTokens(); 190 } 191 192 @Override 193 public int[] getAcceptableTokens() { 194 return new int[] { 195 TokenTypes.CLASS_DEF, 196 TokenTypes.ENUM_CONSTANT_DEF, 197 TokenTypes.ENUM_DEF, 198 TokenTypes.INTERFACE_DEF, 199 TokenTypes.ANNOTATION_DEF, 200 TokenTypes.METHOD_DEF, 201 }; 202 } 203 204 @Override 205 public int[] getRequiredTokens() { 206 return new int[] {TokenTypes.METHOD_DEF}; 207 } 208 209 @Override 210 public void visitToken(DetailAST ast) { 211 if (ast.getType() == TokenTypes.METHOD_DEF) { 212 if (isInLatestScopeDefinition(ast)) { 213 raiseCounter(ast); 214 } 215 } 216 else { 217 counters.push(new MethodCounter(ast)); 218 } 219 } 220 221 @Override 222 public void leaveToken(DetailAST ast) { 223 if (ast.getType() != TokenTypes.METHOD_DEF) { 224 final MethodCounter counter = counters.pop(); 225 226 checkCounters(counter, ast); 227 } 228 } 229 230 /** 231 * Checks if there is a scope definition to check and that the method is found inside that scope 232 * (class, enum, etc.). 233 * @param methodDef 234 * The method to analyze. 235 * @return {@code true} if the method is part of the latest scope definition and should be 236 * counted. 237 */ 238 private boolean isInLatestScopeDefinition(DetailAST methodDef) { 239 boolean result = false; 240 241 if (!counters.isEmpty()) { 242 final DetailAST latestDefinition = counters.peek().getScopeDefinition(); 243 244 result = latestDefinition == methodDef.getParent().getParent(); 245 } 246 247 return result; 248 } 249 250 /** 251 * Determine the visibility modifier and raise the corresponding counter. 252 * @param method 253 * The method-subtree from the AbstractSyntaxTree. 254 */ 255 private void raiseCounter(DetailAST method) { 256 final MethodCounter actualCounter = counters.peek(); 257 final DetailAST temp = method.findFirstToken(TokenTypes.MODIFIERS); 258 final Scope scope = ScopeUtil.getScopeFromMods(temp); 259 actualCounter.increment(scope); 260 } 261 262 /** 263 * Check the counters and report violations. 264 * @param counter the method counters to check 265 * @param ast to report violations against. 266 */ 267 private void checkCounters(MethodCounter counter, DetailAST ast) { 268 checkMax(maxPrivate, counter.value(Scope.PRIVATE), 269 MSG_PRIVATE_METHODS, ast); 270 checkMax(maxPackage, counter.value(Scope.PACKAGE), 271 MSG_PACKAGE_METHODS, ast); 272 checkMax(maxProtected, counter.value(Scope.PROTECTED), 273 MSG_PROTECTED_METHODS, ast); 274 checkMax(maxPublic, counter.value(Scope.PUBLIC), 275 MSG_PUBLIC_METHODS, ast); 276 checkMax(maxTotal, counter.getTotal(), MSG_MANY_METHODS, ast); 277 } 278 279 /** 280 * Utility for reporting if a maximum has been exceeded. 281 * @param max the maximum allowed value 282 * @param value the actual value 283 * @param msg the message to log. Takes two arguments of value and maximum. 284 * @param ast the AST to associate with the message. 285 */ 286 private void checkMax(int max, int value, String msg, DetailAST ast) { 287 if (max < value) { 288 log(ast.getLineNo(), msg, value, max); 289 } 290 } 291 292 /** 293 * Setter to specify the maximum number of {@code private} methods allowed. 294 * 295 * @param value the maximum allowed. 296 */ 297 public void setMaxPrivate(int value) { 298 maxPrivate = value; 299 } 300 301 /** 302 * Setter to specify the maximum number of {@code package} methods allowed. 303 * 304 * @param value the maximum allowed. 305 */ 306 public void setMaxPackage(int value) { 307 maxPackage = value; 308 } 309 310 /** 311 * Setter to specify the maximum number of {@code protected} methods allowed. 312 * 313 * @param value the maximum allowed. 314 */ 315 public void setMaxProtected(int value) { 316 maxProtected = value; 317 } 318 319 /** 320 * Setter to specify the maximum number of {@code public} methods allowed. 321 * 322 * @param value the maximum allowed. 323 */ 324 public void setMaxPublic(int value) { 325 maxPublic = value; 326 } 327 328 /** 329 * Setter to specify the maximum number of methods allowed at all scope levels. 330 * 331 * @param value the maximum allowed. 332 */ 333 public void setMaxTotal(int value) { 334 maxTotal = value; 335 } 336 337 /** 338 * Marker class used to collect data about the number of methods per 339 * class. Objects of this class are used on the Stack to count the 340 * methods for each class and layer. 341 */ 342 private static class MethodCounter { 343 344 /** Maintains the counts. */ 345 private final Map<Scope, Integer> counts = new EnumMap<>(Scope.class); 346 /** Indicated is an interface, in which case all methods are public. */ 347 private final boolean inInterface; 348 /** 349 * The surrounding scope definition (class, enum, etc.) which the method counts are connect 350 * to. 351 */ 352 private final DetailAST scopeDefinition; 353 /** Tracks the total. */ 354 private int total; 355 356 /** 357 * Creates an interface. 358 * @param scopeDefinition 359 * The surrounding scope definition (class, enum, etc.) which to count all methods 360 * for. 361 */ 362 /* package */ MethodCounter(DetailAST scopeDefinition) { 363 this.scopeDefinition = scopeDefinition; 364 inInterface = scopeDefinition.getType() == TokenTypes.INTERFACE_DEF; 365 } 366 367 /** 368 * Increments to counter by one for the supplied scope. 369 * @param scope the scope counter to increment. 370 */ 371 private void increment(Scope scope) { 372 total++; 373 if (inInterface) { 374 counts.put(Scope.PUBLIC, 1 + value(Scope.PUBLIC)); 375 } 376 else { 377 counts.put(scope, 1 + value(scope)); 378 } 379 } 380 381 /** 382 * Gets the value of a scope counter. 383 * @param scope the scope counter to get the value of 384 * @return the value of a scope counter 385 */ 386 private int value(Scope scope) { 387 Integer value = counts.get(scope); 388 if (value == null) { 389 value = 0; 390 } 391 return value; 392 } 393 394 private DetailAST getScopeDefinition() { 395 return scopeDefinition; 396 } 397 398 /** 399 * Fetches total number of methods. 400 * @return the total number of methods. 401 */ 402 private int getTotal() { 403 return total; 404 } 405 406 } 407 408}