001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2017 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;
024import java.util.regex.Pattern;
025
026import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
027import com.puppycrawl.tools.checkstyle.api.DetailAST;
028import com.puppycrawl.tools.checkstyle.api.TokenTypes;
029
030/**
031 * <p>
032 * Restricts the number of return statements in methods, constructors and lambda expressions
033 * (2 by default). Ignores specified methods ({@code equals()} by default).
034 * </p>
035 * <p>
036 * <b>max</b> property will only check returns in methods and lambdas that
037 * return a specific value (Ex: 'return 1;').
038 * </p>
039 * <p>
040 * <b>maxForVoid</b> property will only check returns in methods, constructors,
041 * and lambdas that have no return type (IE 'return;'). It will only count
042 * visible return statements. Return statements not normally written, but
043 * implied, at the end of the method/constructor definition will not be taken
044 * into account. To disallow "return;" in void return type methods, use a value
045 * of 0.
046 * </p>
047 * <p>
048 * Rationale: Too many return points can be indication that code is
049 * attempting to do too much or may be difficult to understand.
050 * </p>
051 *
052 * @author <a href="mailto:simon@redhillconsulting.com.au">Simon Harris</a>
053 */
054public final class ReturnCountCheck extends AbstractCheck {
055
056    /**
057     * A key is pointing to the warning message text in "messages.properties"
058     * file.
059     */
060    public static final String MSG_KEY = "return.count";
061
062    /** Stack of method contexts. */
063    private final Deque<Context> contextStack = new ArrayDeque<>();
064
065    /** The regexp to match against. */
066    private Pattern format = Pattern.compile("^equals$");
067
068    /** Maximum allowed number of return statements. */
069    private int max = 2;
070    /** Maximum allowed number of return statements for void methods. */
071    private int maxForVoid = 1;
072    /** Current method context. */
073    private Context context;
074
075    @Override
076    public int[] getDefaultTokens() {
077        return new int[] {
078            TokenTypes.CTOR_DEF,
079            TokenTypes.METHOD_DEF,
080            TokenTypes.LAMBDA,
081            TokenTypes.LITERAL_RETURN,
082        };
083    }
084
085    @Override
086    public int[] getRequiredTokens() {
087        return new int[] {TokenTypes.LITERAL_RETURN};
088    }
089
090    @Override
091    public int[] getAcceptableTokens() {
092        return new int[] {
093            TokenTypes.CTOR_DEF,
094            TokenTypes.METHOD_DEF,
095            TokenTypes.LAMBDA,
096            TokenTypes.LITERAL_RETURN,
097        };
098    }
099
100    /**
101     * Set the format for the specified regular expression.
102     * @param pattern a pattern.
103     */
104    public void setFormat(Pattern pattern) {
105        format = pattern;
106    }
107
108    /**
109     * Setter for max property.
110     * @param max maximum allowed number of return statements.
111     */
112    public void setMax(int max) {
113        this.max = max;
114    }
115
116    /**
117     * Setter for maxForVoid property.
118     * @param maxForVoid maximum allowed number of return statements for void methods.
119     */
120    public void setMaxForVoid(int maxForVoid) {
121        this.maxForVoid = maxForVoid;
122    }
123
124    @Override
125    public void beginTree(DetailAST rootAST) {
126        context = new Context(false);
127        contextStack.clear();
128    }
129
130    @Override
131    public void visitToken(DetailAST ast) {
132        switch (ast.getType()) {
133            case TokenTypes.CTOR_DEF:
134            case TokenTypes.METHOD_DEF:
135                visitMethodDef(ast);
136                break;
137            case TokenTypes.LAMBDA:
138                visitLambda();
139                break;
140            case TokenTypes.LITERAL_RETURN:
141                visitReturn(ast);
142                break;
143            default:
144                throw new IllegalStateException(ast.toString());
145        }
146    }
147
148    @Override
149    public void leaveToken(DetailAST ast) {
150        switch (ast.getType()) {
151            case TokenTypes.CTOR_DEF:
152            case TokenTypes.METHOD_DEF:
153            case TokenTypes.LAMBDA:
154                leave(ast);
155                break;
156            case TokenTypes.LITERAL_RETURN:
157                // Do nothing
158                break;
159            default:
160                throw new IllegalStateException(ast.toString());
161        }
162    }
163
164    /**
165     * Creates new method context and places old one on the stack.
166     * @param ast method definition for check.
167     */
168    private void visitMethodDef(DetailAST ast) {
169        contextStack.push(context);
170        final DetailAST methodNameAST = ast.findFirstToken(TokenTypes.IDENT);
171        final boolean check = !format.matcher(methodNameAST.getText()).find();
172        context = new Context(check);
173    }
174
175    /**
176     * Checks number of return statements and restore previous context.
177     * @param ast node to leave.
178     */
179    private void leave(DetailAST ast) {
180        context.checkCount(ast);
181        context = contextStack.pop();
182    }
183
184    /**
185     * Creates new lambda context and places old one on the stack.
186     */
187    private void visitLambda() {
188        contextStack.push(context);
189        context = new Context(true);
190    }
191
192    /**
193     * Examines the return statement and tells context about it.
194     * @param ast return statement to check.
195     */
196    private void visitReturn(DetailAST ast) {
197        // we can't identify which max to use for lambdas, so we can only assign
198        // after the first return statement is seen
199        if (ast.getFirstChild().getType() == TokenTypes.SEMI) {
200            context.visitLiteralReturn(maxForVoid);
201        }
202        else {
203            context.visitLiteralReturn(max);
204        }
205    }
206
207    /**
208     * Class to encapsulate information about one method.
209     * @author <a href="mailto:simon@redhillconsulting.com.au">Simon Harris</a>
210     */
211    private class Context {
212        /** Whether we should check this method or not. */
213        private final boolean checking;
214        /** Counter for return statements. */
215        private int count;
216        /** Maximum allowed number of return statements. */
217        private Integer maxAllowed;
218
219        /**
220         * Creates new method context.
221         * @param checking should we check this method or not.
222         */
223        Context(boolean checking) {
224            this.checking = checking;
225        }
226
227        /**
228         * Increase the number of return statements.
229         * @param maxAssigned Maximum allowed number of return statements.
230         */
231        public void visitLiteralReturn(int maxAssigned) {
232            if (maxAllowed == null) {
233                maxAllowed = maxAssigned;
234            }
235
236            ++count;
237        }
238
239        /**
240         * Checks if number of return statements in the method are more
241         * than allowed.
242         * @param ast method def associated with this context.
243         */
244        public void checkCount(DetailAST ast) {
245            if (checking && maxAllowed != null && count > maxAllowed) {
246                log(ast.getLineNo(), ast.getColumnNo(), MSG_KEY, count, maxAllowed);
247            }
248        }
249    }
250}