001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2025 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 @Override 131 public int[] getDefaultTokens() { 132 return getAcceptableTokens(); 133 } 134 135 @Override 136 public int[] getAcceptableTokens() { 137 return new int[] { 138 TokenTypes.CLASS_DEF, 139 TokenTypes.ENUM_CONSTANT_DEF, 140 TokenTypes.ENUM_DEF, 141 TokenTypes.INTERFACE_DEF, 142 TokenTypes.ANNOTATION_DEF, 143 TokenTypes.METHOD_DEF, 144 TokenTypes.RECORD_DEF, 145 }; 146 } 147 148 @Override 149 public int[] getRequiredTokens() { 150 return new int[] {TokenTypes.METHOD_DEF}; 151 } 152 153 @Override 154 public void visitToken(DetailAST ast) { 155 if (ast.getType() == TokenTypes.METHOD_DEF) { 156 if (isInLatestScopeDefinition(ast)) { 157 raiseCounter(ast); 158 } 159 } 160 else { 161 counters.push(new MethodCounter(ast)); 162 } 163 } 164 165 @Override 166 public void leaveToken(DetailAST ast) { 167 if (ast.getType() != TokenTypes.METHOD_DEF) { 168 final MethodCounter counter = counters.pop(); 169 170 checkCounters(counter, ast); 171 } 172 } 173 174 /** 175 * Checks if there is a scope definition to check and that the method is found inside that scope 176 * (class, enum, etc.). 177 * 178 * @param methodDef 179 * The method to analyze. 180 * @return {@code true} if the method is part of the latest scope definition and should be 181 * counted. 182 */ 183 private boolean isInLatestScopeDefinition(DetailAST methodDef) { 184 boolean result = false; 185 186 if (!counters.isEmpty()) { 187 final DetailAST latestDefinition = counters.peek().getScopeDefinition(); 188 189 result = latestDefinition == methodDef.getParent().getParent(); 190 } 191 192 return result; 193 } 194 195 /** 196 * Determine the visibility modifier and raise the corresponding counter. 197 * 198 * @param method 199 * The method-subtree from the AbstractSyntaxTree. 200 */ 201 private void raiseCounter(DetailAST method) { 202 final MethodCounter actualCounter = counters.peek(); 203 final Scope scope = ScopeUtil.getScope(method); 204 actualCounter.increment(scope); 205 } 206 207 /** 208 * Check the counters and report violations. 209 * 210 * @param counter the method counters to check 211 * @param ast to report violations against. 212 */ 213 private void checkCounters(MethodCounter counter, DetailAST ast) { 214 checkMax(maxPrivate, counter.value(Scope.PRIVATE), 215 MSG_PRIVATE_METHODS, ast); 216 checkMax(maxPackage, counter.value(Scope.PACKAGE), 217 MSG_PACKAGE_METHODS, ast); 218 checkMax(maxProtected, counter.value(Scope.PROTECTED), 219 MSG_PROTECTED_METHODS, ast); 220 checkMax(maxPublic, counter.value(Scope.PUBLIC), 221 MSG_PUBLIC_METHODS, ast); 222 checkMax(maxTotal, counter.getTotal(), MSG_MANY_METHODS, ast); 223 } 224 225 /** 226 * Utility for reporting if a maximum has been exceeded. 227 * 228 * @param max the maximum allowed value 229 * @param value the actual value 230 * @param msg the message to log. Takes two arguments of value and maximum. 231 * @param ast the AST to associate with the message. 232 */ 233 private void checkMax(int max, int value, String msg, DetailAST ast) { 234 if (max < value) { 235 log(ast, msg, value, max); 236 } 237 } 238 239 /** 240 * Setter to specify the maximum number of {@code private} methods allowed. 241 * 242 * @param value the maximum allowed. 243 * @since 5.3 244 */ 245 public void setMaxPrivate(int value) { 246 maxPrivate = value; 247 } 248 249 /** 250 * Setter to specify the maximum number of {@code package} methods allowed. 251 * 252 * @param value the maximum allowed. 253 * @since 5.3 254 */ 255 public void setMaxPackage(int value) { 256 maxPackage = value; 257 } 258 259 /** 260 * Setter to specify the maximum number of {@code protected} methods allowed. 261 * 262 * @param value the maximum allowed. 263 * @since 5.3 264 */ 265 public void setMaxProtected(int value) { 266 maxProtected = value; 267 } 268 269 /** 270 * Setter to specify the maximum number of {@code public} methods allowed. 271 * 272 * @param value the maximum allowed. 273 * @since 5.3 274 */ 275 public void setMaxPublic(int value) { 276 maxPublic = value; 277 } 278 279 /** 280 * Setter to specify the maximum number of methods allowed at all scope levels. 281 * 282 * @param value the maximum allowed. 283 * @since 5.3 284 */ 285 public void setMaxTotal(int value) { 286 maxTotal = value; 287 } 288 289 /** 290 * Marker class used to collect data about the number of methods per 291 * class. Objects of this class are used on the Stack to count the 292 * methods for each class and layer. 293 */ 294 private static final class MethodCounter { 295 296 /** Maintains the counts. */ 297 private final Map<Scope, Integer> counts = new EnumMap<>(Scope.class); 298 /** 299 * The surrounding scope definition (class, enum, etc.) which the method counts are 300 * connected to. 301 */ 302 private final DetailAST scopeDefinition; 303 /** Tracks the total. */ 304 private int total; 305 306 /** 307 * Creates an interface. 308 * 309 * @param scopeDefinition 310 * The surrounding scope definition (class, enum, etc.) which to count all methods 311 * for. 312 */ 313 private MethodCounter(DetailAST scopeDefinition) { 314 this.scopeDefinition = scopeDefinition; 315 } 316 317 /** 318 * Increments to counter by one for the supplied scope. 319 * 320 * @param scope the scope counter to increment. 321 */ 322 private void increment(Scope scope) { 323 total++; 324 counts.put(scope, 1 + value(scope)); 325 } 326 327 /** 328 * Gets the value of a scope counter. 329 * 330 * @param scope the scope counter to get the value of 331 * @return the value of a scope counter 332 */ 333 private int value(Scope scope) { 334 Integer value = counts.get(scope); 335 if (value == null) { 336 value = 0; 337 } 338 return value; 339 } 340 341 /** 342 * Returns the surrounding scope definition (class, enum, etc.) which the method counts 343 * are connected to. 344 * 345 * @return the surrounding scope definition 346 */ 347 private DetailAST getScopeDefinition() { 348 return scopeDefinition; 349 } 350 351 /** 352 * Fetches total number of methods. 353 * 354 * @return the total number of methods. 355 */ 356 private int getTotal() { 357 return total; 358 } 359 360 } 361 362}