View Javadoc
1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2017 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.ScopeUtils;
33  
34  /**
35   * Checks the number of methods declared in each type declaration by access
36   * modifier or total count.
37   * @author Alexander Jesse
38   * @author Oliver Burn
39   */
40  @FileStatefulCheck
41  public final class MethodCountCheck extends AbstractCheck {
42  
43      /**
44       * A key is pointing to the warning message text in "messages.properties"
45       * file.
46       */
47      public static final String MSG_PRIVATE_METHODS = "too.many.privateMethods";
48  
49      /**
50       * A key is pointing to the warning message text in "messages.properties"
51       * file.
52       */
53      public static final String MSG_PACKAGE_METHODS = "too.many.packageMethods";
54  
55      /**
56       * A key is pointing to the warning message text in "messages.properties"
57       * file.
58       */
59      public static final String MSG_PROTECTED_METHODS = "too.many.protectedMethods";
60  
61      /**
62       * A key is pointing to the warning message text in "messages.properties"
63       * file.
64       */
65      public static final String MSG_PUBLIC_METHODS = "too.many.publicMethods";
66  
67      /**
68       * A key is pointing to the warning message text in "messages.properties"
69       * file.
70       */
71      public static final String MSG_MANY_METHODS = "too.many.methods";
72  
73      /** Default maximum number of methods. */
74      private static final int DEFAULT_MAX_METHODS = 100;
75  
76      /** Maintains stack of counters, to support inner types. */
77      private final Deque<MethodCounter> counters = new ArrayDeque<>();
78  
79      /** Maximum private methods. */
80      private int maxPrivate = DEFAULT_MAX_METHODS;
81      /** Maximum package methods. */
82      private int maxPackage = DEFAULT_MAX_METHODS;
83      /** Maximum protected methods. */
84      private int maxProtected = DEFAULT_MAX_METHODS;
85      /** Maximum public methods. */
86      private int maxPublic = DEFAULT_MAX_METHODS;
87      /** Maximum total number of methods. */
88      private int maxTotal = DEFAULT_MAX_METHODS;
89  
90      @Override
91      public int[] getDefaultTokens() {
92          return getAcceptableTokens();
93      }
94  
95      @Override
96      public int[] getAcceptableTokens() {
97          return new int[] {
98              TokenTypes.CLASS_DEF,
99              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         /** Maintains the counts. */
242         private final Map<Scope, Integer> counts = new EnumMap<>(Scope.class);
243         /** Indicated is an interface, in which case all methods are public. */
244         private final boolean inInterface;
245         /**
246          * The surrounding scope definition (class, enum, etc.) which the method counts are connect
247          * to.
248          */
249         private final DetailAST scopeDefinition;
250         /** Tracks the total. */
251         private int total;
252 
253         /**
254          * Creates an interface.
255          * @param scopeDefinition
256          *        The surrounding scope definition (class, enum, etc.) which to count all methods
257          *        for.
258          */
259         MethodCounter(DetailAST scopeDefinition) {
260             this.scopeDefinition = scopeDefinition;
261             inInterface = scopeDefinition.getType() == TokenTypes.INTERFACE_DEF;
262         }
263 
264         /**
265          * Increments to counter by one for the supplied scope.
266          * @param scope the scope counter to increment.
267          */
268         private void increment(Scope scope) {
269             total++;
270             if (inInterface) {
271                 counts.put(Scope.PUBLIC, 1 + value(Scope.PUBLIC));
272             }
273             else {
274                 counts.put(scope, 1 + value(scope));
275             }
276         }
277 
278         /**
279          * Gets the value of a scope counter.
280          * @param scope the scope counter to get the value of
281          * @return the value of a scope counter
282          */
283         private int value(Scope scope) {
284             Integer value = counts.get(scope);
285             if (value == null) {
286                 value = 0;
287             }
288             return value;
289         }
290 
291         private DetailAST getScopeDefinition() {
292             return scopeDefinition;
293         }
294 
295         /**
296          * Fetches total number of methods.
297          * @return the total number of methods.
298          */
299         private int getTotal() {
300             return total;
301         }
302     }
303 }