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