001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2023 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 * Type is {@code int}. 079 * Default value is {@code 100}. 080 * </li> 081 * <li> 082 * Property {@code maxPrivate} - Specify the maximum number of {@code private} methods allowed. 083 * Type is {@code int}. 084 * Default value is {@code 100}. 085 * </li> 086 * <li> 087 * Property {@code maxPackage} - Specify the maximum number of {@code package} methods allowed. 088 * Type is {@code int}. 089 * Default value is {@code 100}. 090 * </li> 091 * <li> 092 * Property {@code maxProtected} - Specify the maximum number of {@code protected} methods allowed. 093 * Type is {@code int}. 094 * Default value is {@code 100}. 095 * </li> 096 * <li> 097 * Property {@code maxPublic} - Specify the maximum number of {@code public} methods allowed. 098 * Type is {@code int}. 099 * Default value is {@code 100}. 100 * </li> 101 * <li> 102 * Property {@code tokens} - tokens to check 103 * Type is {@code java.lang.String[]}. 104 * Validation type is {@code tokenSet}. 105 * Default value is: 106 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CLASS_DEF"> 107 * CLASS_DEF</a>, 108 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ENUM_CONSTANT_DEF"> 109 * ENUM_CONSTANT_DEF</a>, 110 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ENUM_DEF"> 111 * ENUM_DEF</a>, 112 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#INTERFACE_DEF"> 113 * INTERFACE_DEF</a>, 114 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#INTERFACE_DEF"> 115 * ANNOTATION_DEF</a>, 116 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#RECORD_DEF"> 117 * RECORD_DEF</a>. 118 * </li> 119 * </ul> 120 * <p> 121 * To configure the default check: 122 * </p> 123 * <pre> 124 * <module name="MethodCount"/> 125 * </pre> 126 * <p> 127 * To configure the check to allow no more than 30 methods per type declaration: 128 * </p> 129 * <pre> 130 * <module name="MethodCount"> 131 * <property name="maxTotal" value="30"/> 132 * </module> 133 * </pre> 134 * <p> 135 * To configure the check to allow no more than 10 public methods per type declaration, 136 * and 40 methods in total: 137 * </p> 138 * <pre> 139 * <module name="MethodCount"> 140 * <property name="maxPublic" value="10"/> 141 * <property name="maxTotal" value="40"/> 142 * </module> 143 * </pre> 144 * <p> 145 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 146 * </p> 147 * <p> 148 * Violation Message Keys: 149 * </p> 150 * <ul> 151 * <li> 152 * {@code too.many.methods} 153 * </li> 154 * <li> 155 * {@code too.many.packageMethods} 156 * </li> 157 * <li> 158 * {@code too.many.privateMethods} 159 * </li> 160 * <li> 161 * {@code too.many.protectedMethods} 162 * </li> 163 * <li> 164 * {@code too.many.publicMethods} 165 * </li> 166 * </ul> 167 * 168 * @since 5.3 169 */ 170@FileStatefulCheck 171public final class MethodCountCheck extends AbstractCheck { 172 173 /** 174 * A key is pointing to the warning message text in "messages.properties" 175 * file. 176 */ 177 public static final String MSG_PRIVATE_METHODS = "too.many.privateMethods"; 178 179 /** 180 * A key is pointing to the warning message text in "messages.properties" 181 * file. 182 */ 183 public static final String MSG_PACKAGE_METHODS = "too.many.packageMethods"; 184 185 /** 186 * A key is pointing to the warning message text in "messages.properties" 187 * file. 188 */ 189 public static final String MSG_PROTECTED_METHODS = "too.many.protectedMethods"; 190 191 /** 192 * A key is pointing to the warning message text in "messages.properties" 193 * file. 194 */ 195 public static final String MSG_PUBLIC_METHODS = "too.many.publicMethods"; 196 197 /** 198 * A key is pointing to the warning message text in "messages.properties" 199 * file. 200 */ 201 public static final String MSG_MANY_METHODS = "too.many.methods"; 202 203 /** Default maximum number of methods. */ 204 private static final int DEFAULT_MAX_METHODS = 100; 205 206 /** Maintains stack of counters, to support inner types. */ 207 private final Deque<MethodCounter> counters = new ArrayDeque<>(); 208 209 /** Specify the maximum number of {@code private} methods allowed. */ 210 private int maxPrivate = DEFAULT_MAX_METHODS; 211 /** Specify the maximum number of {@code package} methods allowed. */ 212 private int maxPackage = DEFAULT_MAX_METHODS; 213 /** Specify the maximum number of {@code protected} methods allowed. */ 214 private int maxProtected = DEFAULT_MAX_METHODS; 215 /** Specify the maximum number of {@code public} methods allowed. */ 216 private int maxPublic = DEFAULT_MAX_METHODS; 217 /** Specify the maximum number of methods allowed at all scope levels. */ 218 private int maxTotal = DEFAULT_MAX_METHODS; 219 220 @Override 221 public int[] getDefaultTokens() { 222 return getAcceptableTokens(); 223 } 224 225 @Override 226 public int[] getAcceptableTokens() { 227 return new int[] { 228 TokenTypes.CLASS_DEF, 229 TokenTypes.ENUM_CONSTANT_DEF, 230 TokenTypes.ENUM_DEF, 231 TokenTypes.INTERFACE_DEF, 232 TokenTypes.ANNOTATION_DEF, 233 TokenTypes.METHOD_DEF, 234 TokenTypes.RECORD_DEF, 235 }; 236 } 237 238 @Override 239 public int[] getRequiredTokens() { 240 return new int[] {TokenTypes.METHOD_DEF}; 241 } 242 243 @Override 244 public void visitToken(DetailAST ast) { 245 if (ast.getType() == TokenTypes.METHOD_DEF) { 246 if (isInLatestScopeDefinition(ast)) { 247 raiseCounter(ast); 248 } 249 } 250 else { 251 counters.push(new MethodCounter(ast)); 252 } 253 } 254 255 @Override 256 public void leaveToken(DetailAST ast) { 257 if (ast.getType() != TokenTypes.METHOD_DEF) { 258 final MethodCounter counter = counters.pop(); 259 260 checkCounters(counter, ast); 261 } 262 } 263 264 /** 265 * Checks if there is a scope definition to check and that the method is found inside that scope 266 * (class, enum, etc.). 267 * 268 * @param methodDef 269 * The method to analyze. 270 * @return {@code true} if the method is part of the latest scope definition and should be 271 * counted. 272 */ 273 private boolean isInLatestScopeDefinition(DetailAST methodDef) { 274 boolean result = false; 275 276 if (!counters.isEmpty()) { 277 final DetailAST latestDefinition = counters.peek().getScopeDefinition(); 278 279 result = latestDefinition == methodDef.getParent().getParent(); 280 } 281 282 return result; 283 } 284 285 /** 286 * Determine the visibility modifier and raise the corresponding counter. 287 * 288 * @param method 289 * The method-subtree from the AbstractSyntaxTree. 290 */ 291 private void raiseCounter(DetailAST method) { 292 final MethodCounter actualCounter = counters.peek(); 293 final Scope scope = ScopeUtil.getScope(method); 294 actualCounter.increment(scope); 295 } 296 297 /** 298 * Check the counters and report violations. 299 * 300 * @param counter the method counters to check 301 * @param ast to report violations against. 302 */ 303 private void checkCounters(MethodCounter counter, DetailAST ast) { 304 checkMax(maxPrivate, counter.value(Scope.PRIVATE), 305 MSG_PRIVATE_METHODS, ast); 306 checkMax(maxPackage, counter.value(Scope.PACKAGE), 307 MSG_PACKAGE_METHODS, ast); 308 checkMax(maxProtected, counter.value(Scope.PROTECTED), 309 MSG_PROTECTED_METHODS, ast); 310 checkMax(maxPublic, counter.value(Scope.PUBLIC), 311 MSG_PUBLIC_METHODS, ast); 312 checkMax(maxTotal, counter.getTotal(), MSG_MANY_METHODS, ast); 313 } 314 315 /** 316 * Utility for reporting if a maximum has been exceeded. 317 * 318 * @param max the maximum allowed value 319 * @param value the actual value 320 * @param msg the message to log. Takes two arguments of value and maximum. 321 * @param ast the AST to associate with the message. 322 */ 323 private void checkMax(int max, int value, String msg, DetailAST ast) { 324 if (max < value) { 325 log(ast, msg, value, max); 326 } 327 } 328 329 /** 330 * Setter to specify the maximum number of {@code private} methods allowed. 331 * 332 * @param value the maximum allowed. 333 */ 334 public void setMaxPrivate(int value) { 335 maxPrivate = value; 336 } 337 338 /** 339 * Setter to specify the maximum number of {@code package} methods allowed. 340 * 341 * @param value the maximum allowed. 342 */ 343 public void setMaxPackage(int value) { 344 maxPackage = value; 345 } 346 347 /** 348 * Setter to specify the maximum number of {@code protected} methods allowed. 349 * 350 * @param value the maximum allowed. 351 */ 352 public void setMaxProtected(int value) { 353 maxProtected = value; 354 } 355 356 /** 357 * Setter to specify the maximum number of {@code public} methods allowed. 358 * 359 * @param value the maximum allowed. 360 */ 361 public void setMaxPublic(int value) { 362 maxPublic = value; 363 } 364 365 /** 366 * Setter to specify the maximum number of methods allowed at all scope levels. 367 * 368 * @param value the maximum allowed. 369 */ 370 public void setMaxTotal(int value) { 371 maxTotal = value; 372 } 373 374 /** 375 * Marker class used to collect data about the number of methods per 376 * class. Objects of this class are used on the Stack to count the 377 * methods for each class and layer. 378 */ 379 private static final class MethodCounter { 380 381 /** Maintains the counts. */ 382 private final Map<Scope, Integer> counts = new EnumMap<>(Scope.class); 383 /** 384 * The surrounding scope definition (class, enum, etc.) which the method counts are 385 * connected to. 386 */ 387 private final DetailAST scopeDefinition; 388 /** Tracks the total. */ 389 private int total; 390 391 /** 392 * Creates an interface. 393 * 394 * @param scopeDefinition 395 * The surrounding scope definition (class, enum, etc.) which to count all methods 396 * for. 397 */ 398 private MethodCounter(DetailAST scopeDefinition) { 399 this.scopeDefinition = scopeDefinition; 400 } 401 402 /** 403 * Increments to counter by one for the supplied scope. 404 * 405 * @param scope the scope counter to increment. 406 */ 407 private void increment(Scope scope) { 408 total++; 409 counts.put(scope, 1 + value(scope)); 410 } 411 412 /** 413 * Gets the value of a scope counter. 414 * 415 * @param scope the scope counter to get the value of 416 * @return the value of a scope counter 417 */ 418 private int value(Scope scope) { 419 Integer value = counts.get(scope); 420 if (value == null) { 421 value = 0; 422 } 423 return value; 424 } 425 426 /** 427 * Returns the surrounding scope definition (class, enum, etc.) which the method counts 428 * are connected to. 429 * 430 * @return the surrounding scope definition 431 */ 432 private DetailAST getScopeDefinition() { 433 return scopeDefinition; 434 } 435 436 /** 437 * Fetches total number of methods. 438 * 439 * @return the total number of methods. 440 */ 441 private int getTotal() { 442 return total; 443 } 444 445 } 446 447}