001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2014  Oliver Burn
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////////////////////////////////////////////////////////////////////////////////
019package com.puppycrawl.tools.checkstyle.checks.coding;
020
021import com.puppycrawl.tools.checkstyle.api.DetailAST;
022import com.puppycrawl.tools.checkstyle.api.FastStack;
023import com.puppycrawl.tools.checkstyle.api.TokenTypes;
024import com.puppycrawl.tools.checkstyle.checks.AbstractFormatCheck;
025
026/**
027 * <p>
028 * Restricts return statements to a specified count (default = 2).
029 * Ignores specified methods (<code>equals()</code> by default).
030 * </p>
031 * <p>
032 * Rationale: Too many return points can be indication that code is
033 * attempting to do too much or may be difficult to understand.
034 * </p>
035 *
036 * @author <a href="mailto:simon@redhillconsulting.com.au">Simon Harris</a>
037 * TODO: Test for inside a static block
038 */
039public final class ReturnCountCheck extends AbstractFormatCheck
040{
041    /** Default allowed number of return statements. */
042    private static final int DEFAULT_MAX = 2;
043
044    /** Stack of method contexts. */
045    private final FastStack<Context> contextStack = FastStack.newInstance();
046    /** Maximum allowed number of return stmts. */
047    private int max;
048    /** Current method context. */
049    private Context context;
050
051    /** Creates new instance of the checks. */
052    public ReturnCountCheck()
053    {
054        super("^equals$");
055        setMax(DEFAULT_MAX);
056    }
057
058    @Override
059    public int[] getDefaultTokens()
060    {
061        return new int[] {
062            TokenTypes.CTOR_DEF,
063            TokenTypes.METHOD_DEF,
064            TokenTypes.LITERAL_RETURN,
065        };
066    }
067
068    @Override
069    public int[] getRequiredTokens()
070    {
071        return new int[]{
072            TokenTypes.CTOR_DEF,
073            TokenTypes.METHOD_DEF,
074        };
075    }
076
077    /**
078     * Getter for max property.
079     * @return maximum allowed number of return statements.
080     */
081    public int getMax()
082    {
083        return max;
084    }
085
086    /**
087     * Setter for max property.
088     * @param max maximum allowed number of return statements.
089     */
090    public void setMax(int max)
091    {
092        this.max = max;
093    }
094
095    @Override
096    public void beginTree(DetailAST rootAST)
097    {
098        context = null;
099        contextStack.clear();
100    }
101
102    @Override
103    public void visitToken(DetailAST ast)
104    {
105        switch (ast.getType()) {
106            case TokenTypes.CTOR_DEF:
107            case TokenTypes.METHOD_DEF:
108                visitMethodDef(ast);
109                break;
110            case TokenTypes.LITERAL_RETURN:
111                context.visitLiteralReturn();
112                break;
113            default:
114                throw new IllegalStateException(ast.toString());
115        }
116    }
117
118    @Override
119    public void leaveToken(DetailAST ast)
120    {
121        switch (ast.getType()) {
122            case TokenTypes.CTOR_DEF:
123            case TokenTypes.METHOD_DEF:
124                leaveMethodDef(ast);
125                break;
126            case TokenTypes.LITERAL_RETURN:
127                // Do nothing
128                break;
129            default:
130                throw new IllegalStateException(ast.toString());
131        }
132    }
133
134    /**
135     * Creates new method context and places old one on the stack.
136     * @param ast method definition for check.
137     */
138    private void visitMethodDef(DetailAST ast)
139    {
140        contextStack.push(context);
141        final DetailAST methodNameAST = ast.findFirstToken(TokenTypes.IDENT);
142        context =
143            new Context(!getRegexp().matcher(methodNameAST.getText()).find());
144    }
145
146    /**
147     * Checks number of return statements and restore
148     * previous method context.
149     * @param ast method def node.
150     */
151    private void leaveMethodDef(DetailAST ast)
152    {
153        context.checkCount(ast);
154        context = contextStack.pop();
155    }
156
157    /**
158     * Class to encapsulate information about one method.
159     * @author <a href="mailto:simon@redhillconsulting.com.au">Simon Harris</a>
160     */
161    private class Context
162    {
163        /** Whether we should check this method or not. */
164        private final boolean checking;
165        /** Counter for return statements. */
166        private int count;
167
168        /**
169         * Creates new method context.
170         * @param checking should we check this method or not.
171         */
172        public Context(boolean checking)
173        {
174            this.checking = checking;
175            count = 0;
176        }
177
178        /** Increase number of return statements. */
179        public void visitLiteralReturn()
180        {
181            ++count;
182        }
183
184        /**
185         * Checks if number of return statements in method more
186         * than allowed.
187         * @param ast method def associated with this context.
188         */
189        public void checkCount(DetailAST ast)
190        {
191            if (checking && (count > getMax())) {
192                log(ast.getLineNo(), ast.getColumnNo(), "return.count",
193                    count, getMax());
194            }
195        }
196    }
197}