001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2018 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.getLineNo(), ast.getColumnNo(),
154                    MSG_KEY, count, max);
155        }
156        context = contextStack.pop();
157    }
158
159    /**
160     * Process the end of a statement list.
161     *
162     * @param ast the token representing the statement list.
163     */
164    private void visitSlist(DetailAST ast) {
165        if (context.getAST() != null) {
166            // find member AST for the statement list
167            final DetailAST contextAST = context.getAST();
168            DetailAST parent = ast.getParent();
169            int type = parent.getType();
170            while (type != TokenTypes.CTOR_DEF
171                && type != TokenTypes.METHOD_DEF
172                && type != TokenTypes.INSTANCE_INIT
173                && type != TokenTypes.STATIC_INIT) {
174                parent = parent.getParent();
175                type = parent.getType();
176            }
177            if (parent == contextAST) {
178                context.addCount(ast.getChildCount() / 2);
179            }
180        }
181    }
182
183    /**
184     * Class to encapsulate counting information about one member.
185     */
186    private static class Context {
187
188        /** Member AST node. */
189        private final DetailAST ast;
190
191        /** Counter for context elements. */
192        private int count;
193
194        /**
195         * Creates new member context.
196         * @param ast member AST node.
197         */
198        Context(DetailAST ast) {
199            this.ast = ast;
200            count = 0;
201        }
202
203        /**
204         * Increase count.
205         * @param addition the count increment.
206         */
207        public void addCount(int addition) {
208            count += addition;
209        }
210
211        /**
212         * Gets the member AST node.
213         * @return the member AST node.
214         */
215        public DetailAST getAST() {
216            return ast;
217        }
218
219        /**
220         * Gets the count.
221         * @return the count.
222         */
223        public int getCount() {
224            return count;
225        }
226
227    }
228
229}