001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2026 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 * <div> 036 * Checks the number of methods declared in each type declaration by access modifier 037 * or total count. 038 * </div> 039 * 040 * <p> 041 * This check can be configured to flag classes that define too many methods 042 * to prevent the class from getting too complex. Counting can be customized 043 * to prevent too many total methods in a type definition ({@code maxTotal}), 044 * or to prevent too many methods of a specific access modifier ({@code private}, 045 * {@code package}, {@code protected} or {@code public}). Each count is completely 046 * separated to customize how many methods of each you want to allow. For example, 047 * specifying a {@code maxTotal} of 10, still means you can prevent more than 0 048 * {@code maxPackage} methods. A violation won't appear for 8 public methods, 049 * but one will appear if there is also 3 private methods or any package-private methods. 050 * </p> 051 * 052 * <p> 053 * Methods defined in anonymous classes are not counted towards any totals. 054 * Counts only go towards the main type declaration parent, and are kept separate 055 * from it's children's inner types. 056 * </p> 057 * <div class="wrapper"><pre class="prettyprint"><code class="language-java"> 058 * public class ExampleClass { 059 * public enum Colors { 060 * RED, GREEN, YELLOW; 061 * 062 * public String getRGB() { ... } // NOT counted towards ExampleClass 063 * } 064 * 065 * public void example() { // counted towards ExampleClass 066 * Runnable r = (new Runnable() { 067 * public void run() { ... } // NOT counted towards ExampleClass, won't produce any violations 068 * }); 069 * } 070 * 071 * public static class InnerExampleClass { 072 * protected void example2() { ... } // NOT counted towards ExampleClass, 073 * // but counted towards InnerExampleClass 074 * } 075 * } 076 * </code></pre></div> 077 * 078 * @since 5.3 079 */ 080@FileStatefulCheck 081public final class MethodCountCheck extends AbstractCheck { 082 083 /** 084 * A key is pointing to the warning message text in "messages.properties" 085 * file. 086 */ 087 public static final String MSG_PRIVATE_METHODS = "too.many.privateMethods"; 088 089 /** 090 * A key is pointing to the warning message text in "messages.properties" 091 * file. 092 */ 093 public static final String MSG_PACKAGE_METHODS = "too.many.packageMethods"; 094 095 /** 096 * A key is pointing to the warning message text in "messages.properties" 097 * file. 098 */ 099 public static final String MSG_PROTECTED_METHODS = "too.many.protectedMethods"; 100 101 /** 102 * A key is pointing to the warning message text in "messages.properties" 103 * file. 104 */ 105 public static final String MSG_PUBLIC_METHODS = "too.many.publicMethods"; 106 107 /** 108 * A key is pointing to the warning message text in "messages.properties" 109 * file. 110 */ 111 public static final String MSG_MANY_METHODS = "too.many.methods"; 112 113 /** Default maximum number of methods. */ 114 private static final int DEFAULT_MAX_METHODS = 100; 115 116 /** Maintains stack of counters, to support inner types. */ 117 private final Deque<MethodCounter> counters = new ArrayDeque<>(); 118 119 /** Specify the maximum number of {@code private} methods allowed. */ 120 private int maxPrivate = DEFAULT_MAX_METHODS; 121 /** Specify the maximum number of {@code package} methods allowed. */ 122 private int maxPackage = DEFAULT_MAX_METHODS; 123 /** Specify the maximum number of {@code protected} methods allowed. */ 124 private int maxProtected = DEFAULT_MAX_METHODS; 125 /** Specify the maximum number of {@code public} methods allowed. */ 126 private int maxPublic = DEFAULT_MAX_METHODS; 127 /** Specify the maximum number of methods allowed at all scope levels. */ 128 private int maxTotal = DEFAULT_MAX_METHODS; 129 130 /** 131 * Creates a new {@code MethodCountCheck} instance. 132 */ 133 public MethodCountCheck() { 134 // no code by default 135 } 136 137 @Override 138 public int[] getDefaultTokens() { 139 return getAcceptableTokens(); 140 } 141 142 @Override 143 public int[] getAcceptableTokens() { 144 return new int[] { 145 TokenTypes.CLASS_DEF, 146 TokenTypes.ENUM_CONSTANT_DEF, 147 TokenTypes.ENUM_DEF, 148 TokenTypes.INTERFACE_DEF, 149 TokenTypes.ANNOTATION_DEF, 150 TokenTypes.METHOD_DEF, 151 TokenTypes.RECORD_DEF, 152 TokenTypes.COMPACT_COMPILATION_UNIT, 153 }; 154 } 155 156 @Override 157 public int[] getRequiredTokens() { 158 return new int[] {TokenTypes.METHOD_DEF}; 159 } 160 161 @Override 162 public void visitToken(DetailAST ast) { 163 if (ast.getType() == TokenTypes.METHOD_DEF) { 164 if (isInLatestScopeDefinition(ast)) { 165 raiseCounter(ast); 166 } 167 } 168 else { 169 counters.push(new MethodCounter(ast)); 170 } 171 } 172 173 @Override 174 public void leaveToken(DetailAST ast) { 175 if (ast.getType() != TokenTypes.METHOD_DEF) { 176 final MethodCounter counter = counters.pop(); 177 178 checkCounters(counter, ast); 179 } 180 } 181 182 /** 183 * Checks if there is a scope definition to check and that the method is found inside that scope 184 * (class, enum, etc.). 185 * 186 * @param methodDef 187 * The method to analyze. 188 * @return {@code true} if the method is part of the latest scope definition and should be 189 * counted. 190 */ 191 private boolean isInLatestScopeDefinition(DetailAST methodDef) { 192 boolean result = false; 193 194 if (!counters.isEmpty()) { 195 final DetailAST latestDefinition = counters.peek().getScopeDefinition(); 196 final DetailAST methodParent = methodDef.getParent(); 197 final DetailAST scopeDefinition; 198 if (methodParent.getType() == TokenTypes.COMPACT_COMPILATION_UNIT) { 199 scopeDefinition = methodParent; 200 } 201 else { 202 scopeDefinition = methodParent.getParent(); 203 } 204 205 result = latestDefinition == scopeDefinition; 206 } 207 208 return result; 209 } 210 211 /** 212 * Determine the visibility modifier and raise the corresponding counter. 213 * 214 * @param method 215 * The method-subtree from the AbstractSyntaxTree. 216 */ 217 private void raiseCounter(DetailAST method) { 218 final MethodCounter actualCounter = counters.peek(); 219 final Scope scope = ScopeUtil.getScope(method); 220 actualCounter.increment(scope); 221 } 222 223 /** 224 * Check the counters and report violations. 225 * 226 * @param counter the method counters to check 227 * @param ast to report violations against. 228 */ 229 private void checkCounters(MethodCounter counter, DetailAST ast) { 230 checkMax(maxPrivate, counter.value(Scope.PRIVATE), 231 MSG_PRIVATE_METHODS, ast); 232 checkMax(maxPackage, counter.value(Scope.PACKAGE), 233 MSG_PACKAGE_METHODS, ast); 234 checkMax(maxProtected, counter.value(Scope.PROTECTED), 235 MSG_PROTECTED_METHODS, ast); 236 checkMax(maxPublic, counter.value(Scope.PUBLIC), 237 MSG_PUBLIC_METHODS, ast); 238 checkMax(maxTotal, counter.getTotal(), MSG_MANY_METHODS, ast); 239 } 240 241 /** 242 * Utility for reporting if a maximum has been exceeded. 243 * 244 * @param max the maximum allowed value 245 * @param value the actual value 246 * @param msg the message to log. Takes two arguments of value and maximum. 247 * @param ast the AST to associate with the message. 248 */ 249 private void checkMax(int max, int value, String msg, DetailAST ast) { 250 if (max < value) { 251 log(ast, msg, value, max); 252 } 253 } 254 255 /** 256 * Setter to specify the maximum number of {@code private} methods allowed. 257 * 258 * @param value the maximum allowed. 259 * @since 5.3 260 */ 261 public void setMaxPrivate(int value) { 262 maxPrivate = value; 263 } 264 265 /** 266 * Setter to specify the maximum number of {@code package} methods allowed. 267 * 268 * @param value the maximum allowed. 269 * @since 5.3 270 */ 271 public void setMaxPackage(int value) { 272 maxPackage = value; 273 } 274 275 /** 276 * Setter to specify the maximum number of {@code protected} methods allowed. 277 * 278 * @param value the maximum allowed. 279 * @since 5.3 280 */ 281 public void setMaxProtected(int value) { 282 maxProtected = value; 283 } 284 285 /** 286 * Setter to specify the maximum number of {@code public} methods allowed. 287 * 288 * @param value the maximum allowed. 289 * @since 5.3 290 */ 291 public void setMaxPublic(int value) { 292 maxPublic = value; 293 } 294 295 /** 296 * Setter to specify the maximum number of methods allowed at all scope levels. 297 * 298 * @param value the maximum allowed. 299 * @since 5.3 300 */ 301 public void setMaxTotal(int value) { 302 maxTotal = value; 303 } 304 305 /** 306 * Marker class used to collect data about the number of methods per 307 * class. Objects of this class are used on the Stack to count the 308 * methods for each class and layer. 309 */ 310 private static final class MethodCounter { 311 312 /** Maintains the counts. */ 313 private final Map<Scope, Integer> counts = new EnumMap<>(Scope.class); 314 /** 315 * The surrounding scope definition (class, enum, etc.) which the method counts are 316 * connected to. 317 */ 318 private final DetailAST scopeDefinition; 319 /** Tracks the total. */ 320 private int total; 321 322 /** 323 * Creates an interface. 324 * 325 * @param scopeDefinition 326 * The surrounding scope definition (class, enum, etc.) which to count all methods 327 * for. 328 */ 329 private MethodCounter(DetailAST scopeDefinition) { 330 this.scopeDefinition = scopeDefinition; 331 } 332 333 /** 334 * Increments to counter by one for the supplied scope. 335 * 336 * @param scope the scope counter to increment. 337 */ 338 private void increment(Scope scope) { 339 total++; 340 counts.put(scope, 1 + value(scope)); 341 } 342 343 /** 344 * Gets the value of a scope counter. 345 * 346 * @param scope the scope counter to get the value of 347 * @return the value of a scope counter 348 */ 349 private int value(Scope scope) { 350 Integer value = counts.get(scope); 351 if (value == null) { 352 value = 0; 353 } 354 return value; 355 } 356 357 /** 358 * Returns the surrounding scope definition (class, enum, etc.) which the method counts 359 * are connected to. 360 * 361 * @return the surrounding scope definition 362 */ 363 private DetailAST getScopeDefinition() { 364 return scopeDefinition; 365 } 366 367 /** 368 * Fetches total number of methods. 369 * 370 * @return the total number of methods. 371 */ 372 private int getTotal() { 373 return total; 374 } 375 376 } 377 378}