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