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