001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2019 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;
029
030/**
031 * Restricts the number of executable statements to a specified limit
032 * (default = 30).
033 */
034@FileStatefulCheck
035public final class ExecutableStatementCountCheck
036    extends AbstractCheck {
037
038    /**
039     * A key is pointing to the warning message text in "messages.properties"
040     * file.
041     */
042    public static final String MSG_KEY = "executableStatementCount";
043
044    /** Default threshold. */
045    private static final int DEFAULT_MAX = 30;
046
047    /** Stack of method contexts. */
048    private final Deque<Context> contextStack = new ArrayDeque<>();
049
050    /** Threshold to report error for. */
051    private int max;
052
053    /** Current method context. */
054    private Context context;
055
056    /** Constructs a {@code ExecutableStatementCountCheck}. */
057    public ExecutableStatementCountCheck() {
058        max = DEFAULT_MAX;
059    }
060
061    @Override
062    public int[] getDefaultTokens() {
063        return new int[] {
064            TokenTypes.CTOR_DEF,
065            TokenTypes.METHOD_DEF,
066            TokenTypes.INSTANCE_INIT,
067            TokenTypes.STATIC_INIT,
068            TokenTypes.SLIST,
069        };
070    }
071
072    @Override
073    public int[] getRequiredTokens() {
074        return new int[] {TokenTypes.SLIST};
075    }
076
077    @Override
078    public int[] getAcceptableTokens() {
079        return new int[] {
080            TokenTypes.CTOR_DEF,
081            TokenTypes.METHOD_DEF,
082            TokenTypes.INSTANCE_INIT,
083            TokenTypes.STATIC_INIT,
084            TokenTypes.SLIST,
085        };
086    }
087
088    /**
089     * Sets the maximum threshold.
090     * @param max the maximum threshold.
091     */
092    public void setMax(int max) {
093        this.max = max;
094    }
095
096    @Override
097    public void beginTree(DetailAST rootAST) {
098        context = new Context(null);
099        contextStack.clear();
100    }
101
102    @Override
103    public void visitToken(DetailAST ast) {
104        switch (ast.getType()) {
105            case TokenTypes.CTOR_DEF:
106            case TokenTypes.METHOD_DEF:
107            case TokenTypes.INSTANCE_INIT:
108            case TokenTypes.STATIC_INIT:
109                visitMemberDef(ast);
110                break;
111            case TokenTypes.SLIST:
112                visitSlist(ast);
113                break;
114            default:
115                throw new IllegalStateException(ast.toString());
116        }
117    }
118
119    @Override
120    public void leaveToken(DetailAST ast) {
121        switch (ast.getType()) {
122            case TokenTypes.CTOR_DEF:
123            case TokenTypes.METHOD_DEF:
124            case TokenTypes.INSTANCE_INIT:
125            case TokenTypes.STATIC_INIT:
126                leaveMemberDef(ast);
127                break;
128            case TokenTypes.SLIST:
129                // Do nothing
130                break;
131            default:
132                throw new IllegalStateException(ast.toString());
133        }
134    }
135
136    /**
137     * Process the start of the member definition.
138     * @param ast the token representing the member definition.
139     */
140    private void visitMemberDef(DetailAST ast) {
141        contextStack.push(context);
142        context = new Context(ast);
143    }
144
145    /**
146     * Process the end of a member definition.
147     *
148     * @param ast the token representing the member definition.
149     */
150    private void leaveMemberDef(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        if (context.getAST() != null) {
165            // find member AST for the statement list
166            final DetailAST contextAST = context.getAST();
167            DetailAST parent = ast.getParent();
168            int type = parent.getType();
169            while (type != TokenTypes.CTOR_DEF
170                && type != TokenTypes.METHOD_DEF
171                && type != TokenTypes.INSTANCE_INIT
172                && type != TokenTypes.STATIC_INIT) {
173                parent = parent.getParent();
174                type = parent.getType();
175            }
176            if (parent == contextAST) {
177                context.addCount(ast.getChildCount() / 2);
178            }
179        }
180    }
181
182    /**
183     * Class to encapsulate counting information about one member.
184     */
185    private static class Context {
186
187        /** Member AST node. */
188        private final DetailAST ast;
189
190        /** Counter for context elements. */
191        private int count;
192
193        /**
194         * Creates new member context.
195         * @param ast member AST node.
196         */
197        Context(DetailAST ast) {
198            this.ast = ast;
199            count = 0;
200        }
201
202        /**
203         * Increase count.
204         * @param addition the count increment.
205         */
206        public void addCount(int addition) {
207            count += addition;
208        }
209
210        /**
211         * Gets the member AST node.
212         * @return the member AST node.
213         */
214        public DetailAST getAST() {
215            return ast;
216        }
217
218        /**
219         * Gets the count.
220         * @return the count.
221         */
222        public int getCount() {
223            return count;
224        }
225
226    }
227
228}