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