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