001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2020 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 DetailAST temp = method.findFirstToken(TokenTypes.MODIFIERS); 294 final Scope scope = ScopeUtil.getScopeFromMods(temp); 295 actualCounter.increment(scope); 296 } 297 298 /** 299 * Check the counters and report violations. 300 * 301 * @param counter the method counters to check 302 * @param ast to report violations against. 303 */ 304 private void checkCounters(MethodCounter counter, DetailAST ast) { 305 checkMax(maxPrivate, counter.value(Scope.PRIVATE), 306 MSG_PRIVATE_METHODS, ast); 307 checkMax(maxPackage, counter.value(Scope.PACKAGE), 308 MSG_PACKAGE_METHODS, ast); 309 checkMax(maxProtected, counter.value(Scope.PROTECTED), 310 MSG_PROTECTED_METHODS, ast); 311 checkMax(maxPublic, counter.value(Scope.PUBLIC), 312 MSG_PUBLIC_METHODS, ast); 313 checkMax(maxTotal, counter.getTotal(), MSG_MANY_METHODS, ast); 314 } 315 316 /** 317 * Utility for reporting if a maximum has been exceeded. 318 * 319 * @param max the maximum allowed value 320 * @param value the actual value 321 * @param msg the message to log. Takes two arguments of value and maximum. 322 * @param ast the AST to associate with the message. 323 */ 324 private void checkMax(int max, int value, String msg, DetailAST ast) { 325 if (max < value) { 326 log(ast, msg, value, max); 327 } 328 } 329 330 /** 331 * Setter to specify the maximum number of {@code private} methods allowed. 332 * 333 * @param value the maximum allowed. 334 */ 335 public void setMaxPrivate(int value) { 336 maxPrivate = value; 337 } 338 339 /** 340 * Setter to specify the maximum number of {@code package} methods allowed. 341 * 342 * @param value the maximum allowed. 343 */ 344 public void setMaxPackage(int value) { 345 maxPackage = value; 346 } 347 348 /** 349 * Setter to specify the maximum number of {@code protected} methods allowed. 350 * 351 * @param value the maximum allowed. 352 */ 353 public void setMaxProtected(int value) { 354 maxProtected = value; 355 } 356 357 /** 358 * Setter to specify the maximum number of {@code public} methods allowed. 359 * 360 * @param value the maximum allowed. 361 */ 362 public void setMaxPublic(int value) { 363 maxPublic = value; 364 } 365 366 /** 367 * Setter to specify the maximum number of methods allowed at all scope levels. 368 * 369 * @param value the maximum allowed. 370 */ 371 public void setMaxTotal(int value) { 372 maxTotal = value; 373 } 374 375 /** 376 * Marker class used to collect data about the number of methods per 377 * class. Objects of this class are used on the Stack to count the 378 * methods for each class and layer. 379 */ 380 private static class MethodCounter { 381 382 /** Maintains the counts. */ 383 private final Map<Scope, Integer> counts = new EnumMap<>(Scope.class); 384 /** Indicated is an interface, in which case all methods are public. */ 385 private final boolean inInterface; 386 /** 387 * The surrounding scope definition (class, enum, etc.) which the method counts are connect 388 * to. 389 */ 390 private final DetailAST scopeDefinition; 391 /** Tracks the total. */ 392 private int total; 393 394 /** 395 * Creates an interface. 396 * 397 * @param scopeDefinition 398 * The surrounding scope definition (class, enum, etc.) which to count all methods 399 * for. 400 */ 401 /* package */ MethodCounter(DetailAST scopeDefinition) { 402 this.scopeDefinition = scopeDefinition; 403 inInterface = scopeDefinition.getType() == TokenTypes.INTERFACE_DEF; 404 } 405 406 /** 407 * Increments to counter by one for the supplied scope. 408 * 409 * @param scope the scope counter to increment. 410 */ 411 private void increment(Scope scope) { 412 total++; 413 if (inInterface) { 414 counts.put(Scope.PUBLIC, 1 + value(Scope.PUBLIC)); 415 } 416 else { 417 counts.put(scope, 1 + value(scope)); 418 } 419 } 420 421 /** 422 * Gets the value of a scope counter. 423 * 424 * @param scope the scope counter to get the value of 425 * @return the value of a scope counter 426 */ 427 private int value(Scope scope) { 428 Integer value = counts.get(scope); 429 if (value == null) { 430 value = 0; 431 } 432 return value; 433 } 434 435 private DetailAST getScopeDefinition() { 436 return scopeDefinition; 437 } 438 439 /** 440 * Fetches total number of methods. 441 * 442 * @return the total number of methods. 443 */ 444 private int getTotal() { 445 return total; 446 } 447 448 } 449 450}