1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2019 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ////////////////////////////////////////////////////////////////////////////////
19  
20  package com.puppycrawl.tools.checkstyle.checks.sizes;
21  
22  import java.util.ArrayDeque;
23  import java.util.Deque;
24  import java.util.EnumMap;
25  import java.util.Map;
26  
27  import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
28  import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
29  import com.puppycrawl.tools.checkstyle.api.DetailAST;
30  import com.puppycrawl.tools.checkstyle.api.Scope;
31  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
32  import com.puppycrawl.tools.checkstyle.utils.ScopeUtil;
33  
34  /**
35   * Checks the number of methods declared in each type declaration by access
36   * modifier or total count.
37   */
38  @FileStatefulCheck
39  public final class MethodCountCheck extends AbstractCheck {
40  
41      /**
42       * A key is pointing to the warning message text in "messages.properties"
43       * file.
44       */
45      public static final String MSG_PRIVATE_METHODS = "too.many.privateMethods";
46  
47      /**
48       * A key is pointing to the warning message text in "messages.properties"
49       * file.
50       */
51      public static final String MSG_PACKAGE_METHODS = "too.many.packageMethods";
52  
53      /**
54       * A key is pointing to the warning message text in "messages.properties"
55       * file.
56       */
57      public static final String MSG_PROTECTED_METHODS = "too.many.protectedMethods";
58  
59      /**
60       * A key is pointing to the warning message text in "messages.properties"
61       * file.
62       */
63      public static final String MSG_PUBLIC_METHODS = "too.many.publicMethods";
64  
65      /**
66       * A key is pointing to the warning message text in "messages.properties"
67       * file.
68       */
69      public static final String MSG_MANY_METHODS = "too.many.methods";
70  
71      /** Default maximum number of methods. */
72      private static final int DEFAULT_MAX_METHODS = 100;
73  
74      /** Maintains stack of counters, to support inner types. */
75      private final Deque<MethodCounter> counters = new ArrayDeque<>();
76  
77      /** Maximum private methods. */
78      private int maxPrivate = DEFAULT_MAX_METHODS;
79      /** Maximum package methods. */
80      private int maxPackage = DEFAULT_MAX_METHODS;
81      /** Maximum protected methods. */
82      private int maxProtected = DEFAULT_MAX_METHODS;
83      /** Maximum public methods. */
84      private int maxPublic = DEFAULT_MAX_METHODS;
85      /** Maximum total number of methods. */
86      private int maxTotal = DEFAULT_MAX_METHODS;
87  
88      @Override
89      public int[] getDefaultTokens() {
90          return getAcceptableTokens();
91      }
92  
93      @Override
94      public int[] getAcceptableTokens() {
95          return new int[] {
96              TokenTypes.CLASS_DEF,
97              TokenTypes.ENUM_CONSTANT_DEF,
98              TokenTypes.ENUM_DEF,
99              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         /* package */ 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 }