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