1 /////////////////////////////////////////////////////////////////////////////////////////////// 2 // checkstyle: Checks Java source code and other text files for adherence to a set of rules. 3 // Copyright (C) 2001-2024 the original author or authors. 4 // 5 // This library is free software; you can redistribute it and/or 6 // modify it under the terms of the GNU Lesser General Public 7 // License as published by the Free Software Foundation; either 8 // version 2.1 of the License, or (at your option) any later version. 9 // 10 // This library is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 // Lesser General Public License for more details. 14 // 15 // You should have received a copy of the GNU Lesser General Public 16 // License along with this library; if not, write to the Free Software 17 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 /////////////////////////////////////////////////////////////////////////////////////////////// 19 20 package com.puppycrawl.tools.checkstyle.checks.sizes; 21 22 import java.util.ArrayDeque; 23 import java.util.Deque; 24 import java.util.EnumMap; 25 import java.util.Map; 26 27 import com.puppycrawl.tools.checkstyle.FileStatefulCheck; 28 import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 29 import com.puppycrawl.tools.checkstyle.api.DetailAST; 30 import com.puppycrawl.tools.checkstyle.api.Scope; 31 import com.puppycrawl.tools.checkstyle.api.TokenTypes; 32 import com.puppycrawl.tools.checkstyle.utils.ScopeUtil; 33 34 /** 35 * <div> 36 * Checks the number of methods declared in each type declaration by access modifier 37 * or total count. 38 * </div> 39 * 40 * <p> 41 * This check can be configured to flag classes that define too many methods 42 * to prevent the class from getting too complex. Counting can be customized 43 * to prevent too many total methods in a type definition ({@code maxTotal}), 44 * or to prevent too many methods of a specific access modifier ({@code private}, 45 * {@code package}, {@code protected} or {@code public}). Each count is completely 46 * separated to customize how many methods of each you want to allow. For example, 47 * specifying a {@code maxTotal} of 10, still means you can prevent more than 0 48 * {@code maxPackage} methods. A violation won't appear for 8 public methods, 49 * but one will appear if there is also 3 private methods or any package-private methods. 50 * </p> 51 * 52 * <p> 53 * Methods defined in anonymous classes are not counted towards any totals. 54 * Counts only go towards the main type declaration parent, and are kept separate 55 * from it's children's inner types. 56 * </p> 57 * <pre> 58 * public class ExampleClass { 59 * public enum Colors { 60 * RED, GREEN, YELLOW; 61 * 62 * public String getRGB() { ... } // NOT counted towards ExampleClass 63 * } 64 * 65 * public void example() { // counted towards ExampleClass 66 * Runnable r = (new Runnable() { 67 * public void run() { ... } // NOT counted towards ExampleClass, won't produce any violations 68 * }); 69 * } 70 * 71 * public static class InnerExampleClass { 72 * protected void example2() { ... } // NOT counted towards ExampleClass, 73 * // but counted towards InnerExampleClass 74 * } 75 * } 76 * </pre> 77 * <ul> 78 * <li> 79 * Property {@code maxPackage} - Specify the maximum number of {@code package} methods allowed. 80 * Type is {@code int}. 81 * Default value is {@code 100}. 82 * </li> 83 * <li> 84 * Property {@code maxPrivate} - Specify the maximum number of {@code private} methods allowed. 85 * Type is {@code int}. 86 * Default value is {@code 100}. 87 * </li> 88 * <li> 89 * Property {@code maxProtected} - Specify the maximum number of {@code protected} methods allowed. 90 * Type is {@code int}. 91 * Default value is {@code 100}. 92 * </li> 93 * <li> 94 * Property {@code maxPublic} - Specify the maximum number of {@code public} methods allowed. 95 * Type is {@code int}. 96 * Default value is {@code 100}. 97 * </li> 98 * <li> 99 * Property {@code maxTotal} - Specify the maximum number of methods allowed at all scope levels. 100 * Type is {@code int}. 101 * Default value is {@code 100}. 102 * </li> 103 * <li> 104 * Property {@code tokens} - tokens to check 105 * Type is {@code java.lang.String[]}. 106 * Validation type is {@code tokenSet}. 107 * Default value is: 108 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CLASS_DEF"> 109 * CLASS_DEF</a>, 110 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ENUM_CONSTANT_DEF"> 111 * ENUM_CONSTANT_DEF</a>, 112 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ENUM_DEF"> 113 * ENUM_DEF</a>, 114 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#INTERFACE_DEF"> 115 * INTERFACE_DEF</a>, 116 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ANNOTATION_DEF"> 117 * ANNOTATION_DEF</a>, 118 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#RECORD_DEF"> 119 * RECORD_DEF</a>. 120 * </li> 121 * </ul> 122 * 123 * <p> 124 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 125 * </p> 126 * 127 * <p> 128 * Violation Message Keys: 129 * </p> 130 * <ul> 131 * <li> 132 * {@code too.many.methods} 133 * </li> 134 * <li> 135 * {@code too.many.packageMethods} 136 * </li> 137 * <li> 138 * {@code too.many.privateMethods} 139 * </li> 140 * <li> 141 * {@code too.many.protectedMethods} 142 * </li> 143 * <li> 144 * {@code too.many.publicMethods} 145 * </li> 146 * </ul> 147 * 148 * @since 5.3 149 */ 150 @FileStatefulCheck 151 public final class MethodCountCheck extends AbstractCheck { 152 153 /** 154 * A key is pointing to the warning message text in "messages.properties" 155 * file. 156 */ 157 public static final String MSG_PRIVATE_METHODS = "too.many.privateMethods"; 158 159 /** 160 * A key is pointing to the warning message text in "messages.properties" 161 * file. 162 */ 163 public static final String MSG_PACKAGE_METHODS = "too.many.packageMethods"; 164 165 /** 166 * A key is pointing to the warning message text in "messages.properties" 167 * file. 168 */ 169 public static final String MSG_PROTECTED_METHODS = "too.many.protectedMethods"; 170 171 /** 172 * A key is pointing to the warning message text in "messages.properties" 173 * file. 174 */ 175 public static final String MSG_PUBLIC_METHODS = "too.many.publicMethods"; 176 177 /** 178 * A key is pointing to the warning message text in "messages.properties" 179 * file. 180 */ 181 public static final String MSG_MANY_METHODS = "too.many.methods"; 182 183 /** Default maximum number of methods. */ 184 private static final int DEFAULT_MAX_METHODS = 100; 185 186 /** Maintains stack of counters, to support inner types. */ 187 private final Deque<MethodCounter> counters = new ArrayDeque<>(); 188 189 /** Specify the maximum number of {@code private} methods allowed. */ 190 private int maxPrivate = DEFAULT_MAX_METHODS; 191 /** Specify the maximum number of {@code package} methods allowed. */ 192 private int maxPackage = DEFAULT_MAX_METHODS; 193 /** Specify the maximum number of {@code protected} methods allowed. */ 194 private int maxProtected = DEFAULT_MAX_METHODS; 195 /** Specify the maximum number of {@code public} methods allowed. */ 196 private int maxPublic = DEFAULT_MAX_METHODS; 197 /** Specify the maximum number of methods allowed at all scope levels. */ 198 private int maxTotal = DEFAULT_MAX_METHODS; 199 200 @Override 201 public int[] getDefaultTokens() { 202 return getAcceptableTokens(); 203 } 204 205 @Override 206 public int[] getAcceptableTokens() { 207 return new int[] { 208 TokenTypes.CLASS_DEF, 209 TokenTypes.ENUM_CONSTANT_DEF, 210 TokenTypes.ENUM_DEF, 211 TokenTypes.INTERFACE_DEF, 212 TokenTypes.ANNOTATION_DEF, 213 TokenTypes.METHOD_DEF, 214 TokenTypes.RECORD_DEF, 215 }; 216 } 217 218 @Override 219 public int[] getRequiredTokens() { 220 return new int[] {TokenTypes.METHOD_DEF}; 221 } 222 223 @Override 224 public void visitToken(DetailAST ast) { 225 if (ast.getType() == TokenTypes.METHOD_DEF) { 226 if (isInLatestScopeDefinition(ast)) { 227 raiseCounter(ast); 228 } 229 } 230 else { 231 counters.push(new MethodCounter(ast)); 232 } 233 } 234 235 @Override 236 public void leaveToken(DetailAST ast) { 237 if (ast.getType() != TokenTypes.METHOD_DEF) { 238 final MethodCounter counter = counters.pop(); 239 240 checkCounters(counter, ast); 241 } 242 } 243 244 /** 245 * Checks if there is a scope definition to check and that the method is found inside that scope 246 * (class, enum, etc.). 247 * 248 * @param methodDef 249 * The method to analyze. 250 * @return {@code true} if the method is part of the latest scope definition and should be 251 * counted. 252 */ 253 private boolean isInLatestScopeDefinition(DetailAST methodDef) { 254 boolean result = false; 255 256 if (!counters.isEmpty()) { 257 final DetailAST latestDefinition = counters.peek().getScopeDefinition(); 258 259 result = latestDefinition == methodDef.getParent().getParent(); 260 } 261 262 return result; 263 } 264 265 /** 266 * Determine the visibility modifier and raise the corresponding counter. 267 * 268 * @param method 269 * The method-subtree from the AbstractSyntaxTree. 270 */ 271 private void raiseCounter(DetailAST method) { 272 final MethodCounter actualCounter = counters.peek(); 273 final Scope scope = ScopeUtil.getScope(method); 274 actualCounter.increment(scope); 275 } 276 277 /** 278 * Check the counters and report violations. 279 * 280 * @param counter the method counters to check 281 * @param ast to report violations against. 282 */ 283 private void checkCounters(MethodCounter counter, DetailAST ast) { 284 checkMax(maxPrivate, counter.value(Scope.PRIVATE), 285 MSG_PRIVATE_METHODS, ast); 286 checkMax(maxPackage, counter.value(Scope.PACKAGE), 287 MSG_PACKAGE_METHODS, ast); 288 checkMax(maxProtected, counter.value(Scope.PROTECTED), 289 MSG_PROTECTED_METHODS, ast); 290 checkMax(maxPublic, counter.value(Scope.PUBLIC), 291 MSG_PUBLIC_METHODS, ast); 292 checkMax(maxTotal, counter.getTotal(), MSG_MANY_METHODS, ast); 293 } 294 295 /** 296 * Utility for reporting if a maximum has been exceeded. 297 * 298 * @param max the maximum allowed value 299 * @param value the actual value 300 * @param msg the message to log. Takes two arguments of value and maximum. 301 * @param ast the AST to associate with the message. 302 */ 303 private void checkMax(int max, int value, String msg, DetailAST ast) { 304 if (max < value) { 305 log(ast, msg, value, max); 306 } 307 } 308 309 /** 310 * Setter to specify the maximum number of {@code private} methods allowed. 311 * 312 * @param value the maximum allowed. 313 * @since 5.3 314 */ 315 public void setMaxPrivate(int value) { 316 maxPrivate = value; 317 } 318 319 /** 320 * Setter to specify the maximum number of {@code package} methods allowed. 321 * 322 * @param value the maximum allowed. 323 * @since 5.3 324 */ 325 public void setMaxPackage(int value) { 326 maxPackage = value; 327 } 328 329 /** 330 * Setter to specify the maximum number of {@code protected} methods allowed. 331 * 332 * @param value the maximum allowed. 333 * @since 5.3 334 */ 335 public void setMaxProtected(int value) { 336 maxProtected = value; 337 } 338 339 /** 340 * Setter to specify the maximum number of {@code public} methods allowed. 341 * 342 * @param value the maximum allowed. 343 * @since 5.3 344 */ 345 public void setMaxPublic(int value) { 346 maxPublic = value; 347 } 348 349 /** 350 * Setter to specify the maximum number of methods allowed at all scope levels. 351 * 352 * @param value the maximum allowed. 353 * @since 5.3 354 */ 355 public void setMaxTotal(int value) { 356 maxTotal = value; 357 } 358 359 /** 360 * Marker class used to collect data about the number of methods per 361 * class. Objects of this class are used on the Stack to count the 362 * methods for each class and layer. 363 */ 364 private static final class MethodCounter { 365 366 /** Maintains the counts. */ 367 private final Map<Scope, Integer> counts = new EnumMap<>(Scope.class); 368 /** 369 * The surrounding scope definition (class, enum, etc.) which the method counts are 370 * connected to. 371 */ 372 private final DetailAST scopeDefinition; 373 /** Tracks the total. */ 374 private int total; 375 376 /** 377 * Creates an interface. 378 * 379 * @param scopeDefinition 380 * The surrounding scope definition (class, enum, etc.) which to count all methods 381 * for. 382 */ 383 private MethodCounter(DetailAST scopeDefinition) { 384 this.scopeDefinition = scopeDefinition; 385 } 386 387 /** 388 * Increments to counter by one for the supplied scope. 389 * 390 * @param scope the scope counter to increment. 391 */ 392 private void increment(Scope scope) { 393 total++; 394 counts.put(scope, 1 + value(scope)); 395 } 396 397 /** 398 * Gets the value of a scope counter. 399 * 400 * @param scope the scope counter to get the value of 401 * @return the value of a scope counter 402 */ 403 private int value(Scope scope) { 404 Integer value = counts.get(scope); 405 if (value == null) { 406 value = 0; 407 } 408 return value; 409 } 410 411 /** 412 * Returns the surrounding scope definition (class, enum, etc.) which the method counts 413 * are connected to. 414 * 415 * @return the surrounding scope definition 416 */ 417 private DetailAST getScopeDefinition() { 418 return scopeDefinition; 419 } 420 421 /** 422 * Fetches total number of methods. 423 * 424 * @return the total number of methods. 425 */ 426 private int getTotal() { 427 return total; 428 } 429 430 } 431 432 }