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;
024import java.util.regex.Pattern;
025
026import com.puppycrawl.tools.checkstyle.api.Check;
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 * Rationale: Too many return points can be indication that code is
038 * attempting to do too much or may be difficult to understand.
039 * </p>
040 *
041 * @author <a href="mailto:simon@redhillconsulting.com.au">Simon Harris</a>
042 */
043public final class ReturnCountCheck extends Check {
044
045    /**
046     * A key is pointing to the warning message text in "messages.properties"
047     * file.
048     */
049    public static final String MSG_KEY = "return.count";
050
051    /** The format string of the regexp. */
052    private String format = "^equals$";
053    /** The regexp to match against. */
054    private Pattern regexp = Pattern.compile(format);
055
056    /** Stack of method contexts. */
057    private final Deque<Context> contextStack = new ArrayDeque<>();
058    /** Maximum allowed number of return statements. */
059    private int max = 2;
060    /** Current method context. */
061    private Context context;
062
063    @Override
064    public int[] getDefaultTokens() {
065        return new int[] {
066            TokenTypes.CTOR_DEF,
067            TokenTypes.METHOD_DEF,
068            TokenTypes.LAMBDA,
069            TokenTypes.LITERAL_RETURN,
070        };
071    }
072
073    @Override
074    public int[] getRequiredTokens() {
075        return new int[]{
076            TokenTypes.LITERAL_RETURN,
077        };
078    }
079
080    @Override
081    public int[] getAcceptableTokens() {
082        return new int[] {
083            TokenTypes.CTOR_DEF,
084            TokenTypes.METHOD_DEF,
085            TokenTypes.LAMBDA,
086            TokenTypes.LITERAL_RETURN,
087        };
088    }
089
090    /**
091     * Set the format to the specified regular expression.
092     * @param format a {@code String} value
093     * @throws org.apache.commons.beanutils.ConversionException unable to parse format
094     */
095    public void setFormat(String format) {
096        this.format = format;
097        regexp = CommonUtils.createPattern(format);
098    }
099
100    /**
101     * Getter for max property.
102     * @return maximum allowed number of return statements.
103     */
104    public int getMax() {
105        return max;
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    @Override
117    public void beginTree(DetailAST rootAST) {
118        context = new Context(false);
119        contextStack.clear();
120    }
121
122    @Override
123    public void visitToken(DetailAST ast) {
124        switch (ast.getType()) {
125            case TokenTypes.CTOR_DEF:
126            case TokenTypes.METHOD_DEF:
127                visitMethodDef(ast);
128                break;
129            case TokenTypes.LAMBDA:
130                visitLambda();
131                break;
132            case TokenTypes.LITERAL_RETURN:
133                context.visitLiteralReturn();
134                break;
135            default:
136                throw new IllegalStateException(ast.toString());
137        }
138    }
139
140    @Override
141    public void leaveToken(DetailAST ast) {
142        switch (ast.getType()) {
143            case TokenTypes.CTOR_DEF:
144            case TokenTypes.METHOD_DEF:
145            case TokenTypes.LAMBDA:
146                leave(ast);
147                break;
148            case TokenTypes.LITERAL_RETURN:
149                // Do nothing
150                break;
151            default:
152                throw new IllegalStateException(ast.toString());
153        }
154    }
155
156    /**
157     * Creates new method context and places old one on the stack.
158     * @param ast method definition for check.
159     */
160    private void visitMethodDef(DetailAST ast) {
161        contextStack.push(context);
162        final DetailAST methodNameAST = ast.findFirstToken(TokenTypes.IDENT);
163        final boolean check = !regexp.matcher(methodNameAST.getText()).find();
164        context = new Context(check);
165    }
166
167    /**
168     * Checks number of return statements and restore previous context.
169     * @param ast node to leave.
170     */
171    private void leave(DetailAST ast) {
172        context.checkCount(ast);
173        context = contextStack.pop();
174    }
175
176    /**
177     * Creates new lambda context and places old one on the stack.
178     */
179    private void visitLambda() {
180        contextStack.push(context);
181        context = new Context(true);
182    }
183
184    /**
185     * Class to encapsulate information about one method.
186     * @author <a href="mailto:simon@redhillconsulting.com.au">Simon Harris</a>
187     */
188    private class Context {
189        /** Whether we should check this method or not. */
190        private final boolean checking;
191        /** Counter for return statements. */
192        private int count;
193
194        /**
195         * Creates new method context.
196         * @param checking should we check this method or not.
197         */
198        Context(boolean checking) {
199            this.checking = checking;
200            count = 0;
201        }
202
203        /** Increase number of return statements. */
204        public void visitLiteralReturn() {
205            ++count;
206        }
207
208        /**
209         * Checks if number of return statements in method more
210         * than allowed.
211         * @param ast method def associated with this context.
212         */
213        public void checkCount(DetailAST ast) {
214            if (checking && count > getMax()) {
215                log(ast.getLineNo(), ast.getColumnNo(), MSG_KEY,
216                    count, getMax());
217            }
218        }
219    }
220}