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