001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2015 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.coding;
021
022import java.util.ArrayDeque;
023import java.util.Deque;
024
025import com.puppycrawl.tools.checkstyle.api.DetailAST;
026import com.puppycrawl.tools.checkstyle.api.TokenTypes;
027import com.puppycrawl.tools.checkstyle.checks.AbstractFormatCheck;
028
029/**
030 * <p>
031 * Restricts the number of return statements in methods, constructors and lambda expressions
032 * (2 by default). Ignores specified methods ({@code equals()} by default).
033 * </p>
034 * <p>
035 * Rationale: Too many return points can be indication that code is
036 * attempting to do too much or may be difficult to understand.
037 * </p>
038 *
039 * @author <a href="mailto:simon@redhillconsulting.com.au">Simon Harris</a>
040 */
041public final class ReturnCountCheck extends AbstractFormatCheck {
042
043    /**
044     * A key is pointing to the warning message text in "messages.properties"
045     * file.
046     */
047    public static final String MSG_KEY = "return.count";
048
049    /** Default allowed number of return statements. */
050    private static final int DEFAULT_MAX = 2;
051
052    /** Stack of method contexts. */
053    private final Deque<Context> contextStack = new ArrayDeque<>();
054    /** Maximum allowed number of return stmts. */
055    private int max;
056    /** Current method context. */
057    private Context context;
058
059    /** Creates new instance of the checks. */
060    public ReturnCountCheck() {
061        super("^equals$");
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.LAMBDA,
071            TokenTypes.LITERAL_RETURN,
072        };
073    }
074
075    @Override
076    public int[] getRequiredTokens() {
077        return new int[]{
078            TokenTypes.LITERAL_RETURN,
079        };
080    }
081
082    @Override
083    public int[] getAcceptableTokens() {
084        return new int[] {
085            TokenTypes.CTOR_DEF,
086            TokenTypes.METHOD_DEF,
087            TokenTypes.LAMBDA,
088            TokenTypes.LITERAL_RETURN,
089        };
090    }
091
092    /**
093     * Getter for max property.
094     * @return maximum allowed number of return statements.
095     */
096    public int getMax() {
097        return max;
098    }
099
100    /**
101     * Setter for max property.
102     * @param max maximum allowed number of return statements.
103     */
104    public void setMax(int max) {
105        this.max = max;
106    }
107
108    @Override
109    public void beginTree(DetailAST rootAST) {
110        context = new Context(false);
111        contextStack.clear();
112    }
113
114    @Override
115    public void visitToken(DetailAST ast) {
116        switch (ast.getType()) {
117            case TokenTypes.CTOR_DEF:
118            case TokenTypes.METHOD_DEF:
119                visitMethodDef(ast);
120                break;
121            case TokenTypes.LAMBDA:
122                visitLambda();
123                break;
124            case TokenTypes.LITERAL_RETURN:
125                context.visitLiteralReturn();
126                break;
127            default:
128                throw new IllegalStateException(ast.toString());
129        }
130    }
131
132    @Override
133    public void leaveToken(DetailAST ast) {
134        switch (ast.getType()) {
135            case TokenTypes.CTOR_DEF:
136            case TokenTypes.METHOD_DEF:
137            case TokenTypes.LAMBDA:
138                leave(ast);
139                break;
140            case TokenTypes.LITERAL_RETURN:
141                // Do nothing
142                break;
143            default:
144                throw new IllegalStateException(ast.toString());
145        }
146    }
147
148    /**
149     * Creates new method context and places old one on the stack.
150     * @param ast method definition for check.
151     */
152    private void visitMethodDef(DetailAST ast) {
153        contextStack.push(context);
154        final DetailAST methodNameAST = ast.findFirstToken(TokenTypes.IDENT);
155        final boolean check = !getRegexp().matcher(methodNameAST.getText()).find();
156        context = new Context(check);
157    }
158
159    /**
160     * Checks number of return statements and restore previous context.
161     * @param ast node to leave.
162     */
163    private void leave(DetailAST ast) {
164        context.checkCount(ast);
165        context = contextStack.pop();
166    }
167
168    /**
169     * Creates new lambda context and places old one on the stack.
170     */
171    private void visitLambda() {
172        contextStack.push(context);
173        context = new Context(true);
174    }
175
176    /**
177     * Class to encapsulate information about one method.
178     * @author <a href="mailto:simon@redhillconsulting.com.au">Simon Harris</a>
179     */
180    private class Context {
181        /** Whether we should check this method or not. */
182        private final boolean checking;
183        /** Counter for return statements. */
184        private int count;
185
186        /**
187         * Creates new method context.
188         * @param checking should we check this method or not.
189         */
190        Context(boolean checking) {
191            this.checking = checking;
192            count = 0;
193        }
194
195        /** Increase number of return statements. */
196        public void visitLiteralReturn() {
197            ++count;
198        }
199
200        /**
201         * Checks if number of return statements in method more
202         * than allowed.
203         * @param ast method def associated with this context.
204         */
205        public void checkCount(DetailAST ast) {
206            if (checking && count > getMax()) {
207                log(ast.getLineNo(), ast.getColumnNo(), MSG_KEY,
208                    count, getMax());
209            }
210        }
211    }
212}