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