001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2016 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 * Counts the methods of the type-definition and checks whether this 035 * count is higher than the configured limit. 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.METHOD_DEF, 101 }; 102 } 103 104 @Override 105 public int[] getRequiredTokens() { 106 return new int[] {TokenTypes.METHOD_DEF}; 107 } 108 109 @Override 110 public void visitToken(DetailAST ast) { 111 if (ast.getType() == TokenTypes.METHOD_DEF) { 112 raiseCounter(ast); 113 } 114 else { 115 final boolean inInterface = ast.getType() == TokenTypes.INTERFACE_DEF; 116 counters.push(new MethodCounter(inInterface)); 117 } 118 } 119 120 @Override 121 public void leaveToken(DetailAST ast) { 122 if (ast.getType() == TokenTypes.CLASS_DEF 123 || ast.getType() == TokenTypes.INTERFACE_DEF 124 || ast.getType() == TokenTypes.ENUM_CONSTANT_DEF 125 || ast.getType() == TokenTypes.ENUM_DEF) { 126 final MethodCounter counter = counters.pop(); 127 checkCounters(counter, ast); 128 } 129 } 130 131 /** 132 * Determine the visibility modifier and raise the corresponding counter. 133 * @param method 134 * The method-subtree from the AbstractSyntaxTree. 135 */ 136 private void raiseCounter(DetailAST method) { 137 final MethodCounter actualCounter = counters.peek(); 138 final DetailAST temp = method.findFirstToken(TokenTypes.MODIFIERS); 139 final Scope scope = ScopeUtils.getScopeFromMods(temp); 140 actualCounter.increment(scope); 141 } 142 143 /** 144 * Check the counters and report violations. 145 * @param counter the method counters to check 146 * @param ast to report errors against. 147 */ 148 private void checkCounters(MethodCounter counter, DetailAST ast) { 149 checkMax(maxPrivate, counter.value(Scope.PRIVATE), 150 MSG_PRIVATE_METHODS, ast); 151 checkMax(maxPackage, counter.value(Scope.PACKAGE), 152 MSG_PACKAGE_METHODS, ast); 153 checkMax(maxProtected, counter.value(Scope.PROTECTED), 154 MSG_PROTECTED_METHODS, ast); 155 checkMax(maxPublic, counter.value(Scope.PUBLIC), 156 MSG_PUBLIC_METHODS, ast); 157 checkMax(maxTotal, counter.getTotal(), MSG_MANY_METHODS, ast); 158 } 159 160 /** 161 * Utility for reporting if a maximum has been exceeded. 162 * @param max the maximum allowed value 163 * @param value the actual value 164 * @param msg the message to log. Takes two arguments of value and maximum. 165 * @param ast the AST to associate with the message. 166 */ 167 private void checkMax(int max, int value, String msg, DetailAST ast) { 168 if (max < value) { 169 log(ast.getLineNo(), msg, value, max); 170 } 171 } 172 173 /** 174 * Sets the maximum allowed {@code private} methods per type. 175 * @param value the maximum allowed. 176 */ 177 public void setMaxPrivate(int value) { 178 maxPrivate = value; 179 } 180 181 /** 182 * Sets the maximum allowed {@code package} methods per type. 183 * @param value the maximum allowed. 184 */ 185 public void setMaxPackage(int value) { 186 maxPackage = value; 187 } 188 189 /** 190 * Sets the maximum allowed {@code protected} methods per type. 191 * @param value the maximum allowed. 192 */ 193 public void setMaxProtected(int value) { 194 maxProtected = value; 195 } 196 197 /** 198 * Sets the maximum allowed {@code public} methods per type. 199 * @param value the maximum allowed. 200 */ 201 public void setMaxPublic(int value) { 202 maxPublic = value; 203 } 204 205 /** 206 * Sets the maximum total methods per type. 207 * @param value the maximum allowed. 208 */ 209 public void setMaxTotal(int value) { 210 maxTotal = value; 211 } 212 213 /** 214 * Marker class used to collect data about the number of methods per 215 * class. Objects of this class are used on the Stack to count the 216 * methods for each class and layer. 217 */ 218 private static class MethodCounter { 219 /** Maintains the counts. */ 220 private final Map<Scope, Integer> counts = new EnumMap<>(Scope.class); 221 /** Indicated is an interface, in which case all methods are public. */ 222 private final boolean inInterface; 223 /** Tracks the total. */ 224 private int total; 225 226 /** 227 * Creates an interface. 228 * @param inInterface indicated if counter for an interface. In which 229 * case, add all counts as public methods. 230 */ 231 MethodCounter(boolean inInterface) { 232 this.inInterface = inInterface; 233 } 234 235 /** 236 * Increments to counter by one for the supplied scope. 237 * @param scope the scope counter to increment. 238 */ 239 private void increment(Scope scope) { 240 total++; 241 if (inInterface) { 242 counts.put(Scope.PUBLIC, 1 + value(Scope.PUBLIC)); 243 } 244 else { 245 counts.put(scope, 1 + value(scope)); 246 } 247 } 248 249 /** 250 * Gets the value of a scope counter. 251 * @param scope the scope counter to get the value of 252 * @return the value of a scope counter 253 */ 254 private int value(Scope scope) { 255 final Integer value = counts.get(scope); 256 257 if (value == null) { 258 return 0; 259 } 260 else { 261 return value; 262 } 263 } 264 265 /** 266 * @return the total number of methods. 267 */ 268 private int getTotal() { 269 return total; 270 } 271 } 272}