View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2024 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  
25  import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
26  import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
27  import com.puppycrawl.tools.checkstyle.api.DetailAST;
28  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
29  import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
30  
31  /**
32   * <div>
33   * Restricts the number of executable statements to a specified limit.
34   * </div>
35   * <ul>
36   * <li>
37   * Property {@code max} - Specify the maximum threshold allowed.
38   * Type is {@code int}.
39   * Default value is {@code 30}.
40   * </li>
41   * <li>
42   * Property {@code tokens} - tokens to check
43   * Type is {@code java.lang.String[]}.
44   * Validation type is {@code tokenSet}.
45   * Default value is:
46   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CTOR_DEF">
47   * CTOR_DEF</a>,
48   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#METHOD_DEF">
49   * METHOD_DEF</a>,
50   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#INSTANCE_INIT">
51   * INSTANCE_INIT</a>,
52   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#STATIC_INIT">
53   * STATIC_INIT</a>,
54   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#COMPACT_CTOR_DEF">
55   * COMPACT_CTOR_DEF</a>,
56   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LAMBDA">
57   * LAMBDA</a>.
58   * </li>
59   * </ul>
60   *
61   * <p>
62   * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
63   * </p>
64   *
65   * <p>
66   * Violation Message Keys:
67   * </p>
68   * <ul>
69   * <li>
70   * {@code executableStatementCount}
71   * </li>
72   * </ul>
73   *
74   * @since 3.2
75   */
76  @FileStatefulCheck
77  public final class ExecutableStatementCountCheck
78      extends AbstractCheck {
79  
80      /**
81       * A key is pointing to the warning message text in "messages.properties"
82       * file.
83       */
84      public static final String MSG_KEY = "executableStatementCount";
85  
86      /** Default threshold. */
87      private static final int DEFAULT_MAX = 30;
88  
89      /** Stack of method contexts. */
90      private final Deque<Context> contextStack = new ArrayDeque<>();
91  
92      /** Specify the maximum threshold allowed. */
93      private int max;
94  
95      /** Current method context. */
96      private Context context;
97  
98      /** Constructs a {@code ExecutableStatementCountCheck}. */
99      public ExecutableStatementCountCheck() {
100         max = DEFAULT_MAX;
101     }
102 
103     @Override
104     public int[] getDefaultTokens() {
105         return new int[] {
106             TokenTypes.CTOR_DEF,
107             TokenTypes.METHOD_DEF,
108             TokenTypes.INSTANCE_INIT,
109             TokenTypes.STATIC_INIT,
110             TokenTypes.SLIST,
111             TokenTypes.COMPACT_CTOR_DEF,
112             TokenTypes.LAMBDA,
113         };
114     }
115 
116     @Override
117     public int[] getRequiredTokens() {
118         return new int[] {TokenTypes.SLIST};
119     }
120 
121     @Override
122     public int[] getAcceptableTokens() {
123         return new int[] {
124             TokenTypes.CTOR_DEF,
125             TokenTypes.METHOD_DEF,
126             TokenTypes.INSTANCE_INIT,
127             TokenTypes.STATIC_INIT,
128             TokenTypes.SLIST,
129             TokenTypes.COMPACT_CTOR_DEF,
130             TokenTypes.LAMBDA,
131         };
132     }
133 
134     /**
135      * Setter to specify the maximum threshold allowed.
136      *
137      * @param max the maximum threshold.
138      * @since 3.2
139      */
140     public void setMax(int max) {
141         this.max = max;
142     }
143 
144     @Override
145     public void beginTree(DetailAST rootAST) {
146         context = new Context(null);
147         contextStack.clear();
148     }
149 
150     @Override
151     public void visitToken(DetailAST ast) {
152         if (isContainerNode(ast)) {
153             visitContainerNode(ast);
154         }
155         else if (TokenUtil.isOfType(ast, TokenTypes.SLIST)) {
156             visitSlist(ast);
157         }
158         else {
159             throw new IllegalStateException(ast.toString());
160         }
161     }
162 
163     @Override
164     public void leaveToken(DetailAST ast) {
165         if (isContainerNode(ast)) {
166             leaveContainerNode(ast);
167         }
168         else if (!TokenUtil.isOfType(ast, TokenTypes.SLIST)) {
169             throw new IllegalStateException(ast.toString());
170         }
171     }
172 
173     /**
174      * Process the start of the container node.
175      *
176      * @param ast the token representing the container node.
177      */
178     private void visitContainerNode(DetailAST ast) {
179         contextStack.push(context);
180         context = new Context(ast);
181     }
182 
183     /**
184      * Process the end of a container node.
185      *
186      * @param ast the token representing the container node.
187      */
188     private void leaveContainerNode(DetailAST ast) {
189         final int count = context.getCount();
190         if (count > max) {
191             log(ast, MSG_KEY, count, max);
192         }
193         context = contextStack.pop();
194     }
195 
196     /**
197      * Process the end of a statement list.
198      *
199      * @param ast the token representing the statement list.
200      */
201     private void visitSlist(DetailAST ast) {
202         final DetailAST contextAST = context.getAST();
203         DetailAST parent = ast;
204         while (parent != null && !isContainerNode(parent)) {
205             parent = parent.getParent();
206         }
207         if (parent == contextAST) {
208             context.addCount(ast.getChildCount() / 2);
209         }
210     }
211 
212     /**
213      * Check if the node is of type ctor (compact or canonical),
214      * instance/ static initializer, method definition or lambda.
215      *
216      * @param node AST node we are checking
217      * @return true if node is of the given types
218      */
219     private static boolean isContainerNode(DetailAST node) {
220         return TokenUtil.isOfType(node, TokenTypes.METHOD_DEF,
221                 TokenTypes.LAMBDA, TokenTypes.CTOR_DEF, TokenTypes.INSTANCE_INIT,
222                 TokenTypes.STATIC_INIT, TokenTypes.COMPACT_CTOR_DEF);
223     }
224 
225     /**
226      * Class to encapsulate counting information about one member.
227      */
228     private static final class Context {
229 
230         /** Member AST node. */
231         private final DetailAST ast;
232 
233         /** Counter for context elements. */
234         private int count;
235 
236         /**
237          * Creates new member context.
238          *
239          * @param ast member AST node.
240          */
241         private Context(DetailAST ast) {
242             this.ast = ast;
243         }
244 
245         /**
246          * Increase count.
247          *
248          * @param addition the count increment.
249          */
250         public void addCount(int addition) {
251             count += addition;
252         }
253 
254         /**
255          * Gets the member AST node.
256          *
257          * @return the member AST node.
258          */
259         public DetailAST getAST() {
260             return ast;
261         }
262 
263         /**
264          * Gets the count.
265          *
266          * @return the count.
267          */
268         public int getCount() {
269             return count;
270         }
271 
272     }
273 
274 }