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 * <p>
033 * Restricts the number of executable statements to a specified limit.
034 * </p>
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 * <p>
061 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
062 * </p>
063 * <p>
064 * Violation Message Keys:
065 * </p>
066 * <ul>
067 * <li>
068 * {@code executableStatementCount}
069 * </li>
070 * </ul>
071 *
072 * @since 3.2
073 */
074@FileStatefulCheck
075public final class ExecutableStatementCountCheck
076    extends AbstractCheck {
077
078    /**
079     * A key is pointing to the warning message text in "messages.properties"
080     * file.
081     */
082    public static final String MSG_KEY = "executableStatementCount";
083
084    /** Default threshold. */
085    private static final int DEFAULT_MAX = 30;
086
087    /** Stack of method contexts. */
088    private final Deque<Context> contextStack = new ArrayDeque<>();
089
090    /** Specify the maximum threshold allowed. */
091    private int max;
092
093    /** Current method context. */
094    private Context context;
095
096    /** Constructs a {@code ExecutableStatementCountCheck}. */
097    public ExecutableStatementCountCheck() {
098        max = DEFAULT_MAX;
099    }
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}