001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2024 the original author or authors.
004//
005// This library is free software; you can redistribute it and/or
006// modify it under the terms of the GNU Lesser General Public
007// License as published by the Free Software Foundation; either
008// version 2.1 of the License, or (at your option) any later version.
009//
010// This library is distributed in the hope that it will be useful,
011// but WITHOUT ANY WARRANTY; without even the implied warranty of
012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013// Lesser General Public License for more details.
014//
015// You should have received a copy of the GNU Lesser General Public
016// License along with this library; if not, write to the Free Software
017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
018///////////////////////////////////////////////////////////////////////////////////////////////
019
020package com.puppycrawl.tools.checkstyle.checks.sizes;
021
022import java.util.ArrayDeque;
023import java.util.Deque;
024
025import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
026import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
027import com.puppycrawl.tools.checkstyle.api.DetailAST;
028import com.puppycrawl.tools.checkstyle.api.TokenTypes;
029import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
030
031/**
032 * <div>
033 * Restricts the number of executable statements to a specified limit.
034 * </div>
035 * <ul>
036 * <li>
037 * Property {@code max} - Specify the maximum threshold allowed.
038 * Type is {@code int}.
039 * Default value is {@code 30}.
040 * </li>
041 * <li>
042 * Property {@code tokens} - tokens to check
043 * Type is {@code java.lang.String[]}.
044 * Validation type is {@code tokenSet}.
045 * Default value is:
046 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CTOR_DEF">
047 * CTOR_DEF</a>,
048 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#METHOD_DEF">
049 * METHOD_DEF</a>,
050 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#INSTANCE_INIT">
051 * INSTANCE_INIT</a>,
052 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#STATIC_INIT">
053 * STATIC_INIT</a>,
054 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#COMPACT_CTOR_DEF">
055 * COMPACT_CTOR_DEF</a>,
056 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LAMBDA">
057 * LAMBDA</a>.
058 * </li>
059 * </ul>
060 *
061 * <p>
062 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
063 * </p>
064 *
065 * <p>
066 * Violation Message Keys:
067 * </p>
068 * <ul>
069 * <li>
070 * {@code executableStatementCount}
071 * </li>
072 * </ul>
073 *
074 * @since 3.2
075 */
076@FileStatefulCheck
077public final class ExecutableStatementCountCheck
078    extends AbstractCheck {
079
080    /**
081     * A key is pointing to the warning message text in "messages.properties"
082     * file.
083     */
084    public static final String MSG_KEY = "executableStatementCount";
085
086    /** Default threshold. */
087    private static final int DEFAULT_MAX = 30;
088
089    /** Stack of method contexts. */
090    private final Deque<Context> contextStack = new ArrayDeque<>();
091
092    /** Specify the maximum threshold allowed. */
093    private int max;
094
095    /** Current method context. */
096    private Context context;
097
098    /** Constructs a {@code ExecutableStatementCountCheck}. */
099    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}