001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2017 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.api.AbstractCheck; 028import com.puppycrawl.tools.checkstyle.api.DetailAST; 029import com.puppycrawl.tools.checkstyle.api.Scope; 030import com.puppycrawl.tools.checkstyle.api.TokenTypes; 031import com.puppycrawl.tools.checkstyle.utils.ScopeUtils; 032 033/** 034 * Checks the number of methods declared in each type declaration by access 035 * modifier or total count. 036 * @author Alexander Jesse 037 * @author Oliver Burn 038 */ 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 (isInLastestScopeDefinition(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 analzye. 136 * @return {@code true} if the method is part of the latest scope definition and should be 137 * counted. 138 */ 139 private boolean isInLastestScopeDefinition(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 = ScopeUtils.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 /** Maintains the counts. */ 240 private final Map<Scope, Integer> counts = new EnumMap<>(Scope.class); 241 /** Indicated is an interface, in which case all methods are public. */ 242 private final boolean inInterface; 243 /** 244 * The surrounding scope definition (class, enum, etc.) which the method counts are connect 245 * to. 246 */ 247 private final DetailAST scopeDefinition; 248 /** Tracks the total. */ 249 private int total; 250 251 /** 252 * Creates an interface. 253 * @param scopeDefinition 254 * The surrounding scope definition (class, enum, etc.) which to count all methods 255 * for. 256 */ 257 MethodCounter(DetailAST scopeDefinition) { 258 this.scopeDefinition = scopeDefinition; 259 inInterface = scopeDefinition.getType() == TokenTypes.INTERFACE_DEF; 260 } 261 262 /** 263 * Increments to counter by one for the supplied scope. 264 * @param scope the scope counter to increment. 265 */ 266 private void increment(Scope scope) { 267 total++; 268 if (inInterface) { 269 counts.put(Scope.PUBLIC, 1 + value(Scope.PUBLIC)); 270 } 271 else { 272 counts.put(scope, 1 + value(scope)); 273 } 274 } 275 276 /** 277 * Gets the value of a scope counter. 278 * @param scope the scope counter to get the value of 279 * @return the value of a scope counter 280 */ 281 private int value(Scope scope) { 282 Integer value = counts.get(scope); 283 if (value == null) { 284 value = 0; 285 } 286 return value; 287 } 288 289 private DetailAST getScopeDefinition() { 290 return scopeDefinition; 291 } 292 293 /** 294 * Fetches total number of methods. 295 * @return the total number of methods. 296 */ 297 private int getTotal() { 298 return total; 299 } 300 } 301}