001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2019 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 * Checks the number of methods declared in each type declaration by access 036 * modifier or total count. 037 */ 038@FileStatefulCheck 039public final class MethodCountCheck extends AbstractCheck { 040 041 /** 042 * A key is pointing to the warning message text in "messages.properties" 043 * file. 044 */ 045 public static final String MSG_PRIVATE_METHODS = "too.many.privateMethods"; 046 047 /** 048 * A key is pointing to the warning message text in "messages.properties" 049 * file. 050 */ 051 public static final String MSG_PACKAGE_METHODS = "too.many.packageMethods"; 052 053 /** 054 * A key is pointing to the warning message text in "messages.properties" 055 * file. 056 */ 057 public static final String MSG_PROTECTED_METHODS = "too.many.protectedMethods"; 058 059 /** 060 * A key is pointing to the warning message text in "messages.properties" 061 * file. 062 */ 063 public static final String MSG_PUBLIC_METHODS = "too.many.publicMethods"; 064 065 /** 066 * A key is pointing to the warning message text in "messages.properties" 067 * file. 068 */ 069 public static final String MSG_MANY_METHODS = "too.many.methods"; 070 071 /** Default maximum number of methods. */ 072 private static final int DEFAULT_MAX_METHODS = 100; 073 074 /** Maintains stack of counters, to support inner types. */ 075 private final Deque<MethodCounter> counters = new ArrayDeque<>(); 076 077 /** Maximum private methods. */ 078 private int maxPrivate = DEFAULT_MAX_METHODS; 079 /** Maximum package methods. */ 080 private int maxPackage = DEFAULT_MAX_METHODS; 081 /** Maximum protected methods. */ 082 private int maxProtected = DEFAULT_MAX_METHODS; 083 /** Maximum public methods. */ 084 private int maxPublic = DEFAULT_MAX_METHODS; 085 /** Maximum total number of methods. */ 086 private int maxTotal = DEFAULT_MAX_METHODS; 087 088 @Override 089 public int[] getDefaultTokens() { 090 return getAcceptableTokens(); 091 } 092 093 @Override 094 public int[] getAcceptableTokens() { 095 return new int[] { 096 TokenTypes.CLASS_DEF, 097 TokenTypes.ENUM_CONSTANT_DEF, 098 TokenTypes.ENUM_DEF, 099 TokenTypes.INTERFACE_DEF, 100 TokenTypes.ANNOTATION_DEF, 101 TokenTypes.METHOD_DEF, 102 }; 103 } 104 105 @Override 106 public int[] getRequiredTokens() { 107 return new int[] {TokenTypes.METHOD_DEF}; 108 } 109 110 @Override 111 public void visitToken(DetailAST ast) { 112 if (ast.getType() == TokenTypes.METHOD_DEF) { 113 if (isInLatestScopeDefinition(ast)) { 114 raiseCounter(ast); 115 } 116 } 117 else { 118 counters.push(new MethodCounter(ast)); 119 } 120 } 121 122 @Override 123 public void leaveToken(DetailAST ast) { 124 if (ast.getType() != TokenTypes.METHOD_DEF) { 125 final MethodCounter counter = counters.pop(); 126 127 checkCounters(counter, ast); 128 } 129 } 130 131 /** 132 * Checks if there is a scope definition to check and that the method is found inside that scope 133 * (class, enum, etc.). 134 * @param methodDef 135 * The method to analyze. 136 * @return {@code true} if the method is part of the latest scope definition and should be 137 * counted. 138 */ 139 private boolean isInLatestScopeDefinition(DetailAST methodDef) { 140 boolean result = false; 141 142 if (!counters.isEmpty()) { 143 final DetailAST latestDefinition = counters.peek().getScopeDefinition(); 144 145 result = latestDefinition == methodDef.getParent().getParent(); 146 } 147 148 return result; 149 } 150 151 /** 152 * Determine the visibility modifier and raise the corresponding counter. 153 * @param method 154 * The method-subtree from the AbstractSyntaxTree. 155 */ 156 private void raiseCounter(DetailAST method) { 157 final MethodCounter actualCounter = counters.peek(); 158 final DetailAST temp = method.findFirstToken(TokenTypes.MODIFIERS); 159 final Scope scope = ScopeUtil.getScopeFromMods(temp); 160 actualCounter.increment(scope); 161 } 162 163 /** 164 * Check the counters and report violations. 165 * @param counter the method counters to check 166 * @param ast to report errors against. 167 */ 168 private void checkCounters(MethodCounter counter, DetailAST ast) { 169 checkMax(maxPrivate, counter.value(Scope.PRIVATE), 170 MSG_PRIVATE_METHODS, ast); 171 checkMax(maxPackage, counter.value(Scope.PACKAGE), 172 MSG_PACKAGE_METHODS, ast); 173 checkMax(maxProtected, counter.value(Scope.PROTECTED), 174 MSG_PROTECTED_METHODS, ast); 175 checkMax(maxPublic, counter.value(Scope.PUBLIC), 176 MSG_PUBLIC_METHODS, ast); 177 checkMax(maxTotal, counter.getTotal(), MSG_MANY_METHODS, ast); 178 } 179 180 /** 181 * Utility for reporting if a maximum has been exceeded. 182 * @param max the maximum allowed value 183 * @param value the actual value 184 * @param msg the message to log. Takes two arguments of value and maximum. 185 * @param ast the AST to associate with the message. 186 */ 187 private void checkMax(int max, int value, String msg, DetailAST ast) { 188 if (max < value) { 189 log(ast.getLineNo(), msg, value, max); 190 } 191 } 192 193 /** 194 * Sets the maximum allowed {@code private} methods per type. 195 * @param value the maximum allowed. 196 */ 197 public void setMaxPrivate(int value) { 198 maxPrivate = value; 199 } 200 201 /** 202 * Sets the maximum allowed {@code package} methods per type. 203 * @param value the maximum allowed. 204 */ 205 public void setMaxPackage(int value) { 206 maxPackage = value; 207 } 208 209 /** 210 * Sets the maximum allowed {@code protected} methods per type. 211 * @param value the maximum allowed. 212 */ 213 public void setMaxProtected(int value) { 214 maxProtected = value; 215 } 216 217 /** 218 * Sets the maximum allowed {@code public} methods per type. 219 * @param value the maximum allowed. 220 */ 221 public void setMaxPublic(int value) { 222 maxPublic = value; 223 } 224 225 /** 226 * Sets the maximum total methods per type. 227 * @param value the maximum allowed. 228 */ 229 public void setMaxTotal(int value) { 230 maxTotal = value; 231 } 232 233 /** 234 * Marker class used to collect data about the number of methods per 235 * class. Objects of this class are used on the Stack to count the 236 * methods for each class and layer. 237 */ 238 private static class MethodCounter { 239 240 /** Maintains the counts. */ 241 private final Map<Scope, Integer> counts = new EnumMap<>(Scope.class); 242 /** Indicated is an interface, in which case all methods are public. */ 243 private final boolean inInterface; 244 /** 245 * The surrounding scope definition (class, enum, etc.) which the method counts are connect 246 * to. 247 */ 248 private final DetailAST scopeDefinition; 249 /** Tracks the total. */ 250 private int total; 251 252 /** 253 * Creates an interface. 254 * @param scopeDefinition 255 * The surrounding scope definition (class, enum, etc.) which to count all methods 256 * for. 257 */ 258 MethodCounter(DetailAST scopeDefinition) { 259 this.scopeDefinition = scopeDefinition; 260 inInterface = scopeDefinition.getType() == TokenTypes.INTERFACE_DEF; 261 } 262 263 /** 264 * Increments to counter by one for the supplied scope. 265 * @param scope the scope counter to increment. 266 */ 267 private void increment(Scope scope) { 268 total++; 269 if (inInterface) { 270 counts.put(Scope.PUBLIC, 1 + value(Scope.PUBLIC)); 271 } 272 else { 273 counts.put(scope, 1 + value(scope)); 274 } 275 } 276 277 /** 278 * Gets the value of a scope counter. 279 * @param scope the scope counter to get the value of 280 * @return the value of a scope counter 281 */ 282 private int value(Scope scope) { 283 Integer value = counts.get(scope); 284 if (value == null) { 285 value = 0; 286 } 287 return value; 288 } 289 290 private DetailAST getScopeDefinition() { 291 return scopeDefinition; 292 } 293 294 /** 295 * Fetches total number of methods. 296 * @return the total number of methods. 297 */ 298 private int getTotal() { 299 return total; 300 } 301 302 } 303 304}