001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2025 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 *
036 * @since 3.2
037 */
038@FileStatefulCheck
039public final class ExecutableStatementCountCheck
040    extends AbstractCheck {
041
042    /**
043     * A key is pointing to the warning message text in "messages.properties"
044     * file.
045     */
046    public static final String MSG_KEY = "executableStatementCount";
047
048    /** Default threshold. */
049    private static final int DEFAULT_MAX = 30;
050
051    /** Stack of method contexts. */
052    private final Deque<Context> contextStack = new ArrayDeque<>();
053
054    /** Specify the maximum threshold allowed. */
055    private int max;
056
057    /** Current method context. */
058    private Context context;
059
060    /** Constructs a {@code ExecutableStatementCountCheck}. */
061    public ExecutableStatementCountCheck() {
062        max = DEFAULT_MAX;
063    }
064
065    @Override
066    public int[] getDefaultTokens() {
067        return new int[] {
068            TokenTypes.CTOR_DEF,
069            TokenTypes.METHOD_DEF,
070            TokenTypes.INSTANCE_INIT,
071            TokenTypes.STATIC_INIT,
072            TokenTypes.SLIST,
073            TokenTypes.COMPACT_CTOR_DEF,
074            TokenTypes.LAMBDA,
075        };
076    }
077
078    @Override
079    public int[] getRequiredTokens() {
080        return new int[] {TokenTypes.SLIST};
081    }
082
083    @Override
084    public int[] getAcceptableTokens() {
085        return new int[] {
086            TokenTypes.CTOR_DEF,
087            TokenTypes.METHOD_DEF,
088            TokenTypes.INSTANCE_INIT,
089            TokenTypes.STATIC_INIT,
090            TokenTypes.SLIST,
091            TokenTypes.COMPACT_CTOR_DEF,
092            TokenTypes.LAMBDA,
093        };
094    }
095
096    /**
097     * Setter to specify the maximum threshold allowed.
098     *
099     * @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}