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