1 ////////////////////////////////////////////////////////////////////////////////
2 // checkstyle: Checks Java source code for adherence to a set of rules.
3 // Copyright (C) 2001-2015 the original author or authors.
4 //
5 // This library is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU Lesser General Public
7 // License as published by the Free Software Foundation; either
8 // version 2.1 of the License, or (at your option) any later version.
9 //
10 // This library is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 // Lesser General Public License for more details.
14 //
15 // You should have received a copy of the GNU Lesser General Public
16 // License along with this library; if not, write to the Free Software
17 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 ////////////////////////////////////////////////////////////////////////////////
19 package com.puppycrawl.tools.checkstyle.checks.coding;
20
21 import com.puppycrawl.tools.checkstyle.api.DetailAST;
22 import com.puppycrawl.tools.checkstyle.api.FastStack;
23 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
24 import com.puppycrawl.tools.checkstyle.checks.AbstractFormatCheck;
25
26 /**
27 * <p>
28 * Restricts return statements to a specified count (default = 2).
29 * Ignores specified methods (<code>equals()</code> by default).
30 * </p>
31 * <p>
32 * Rationale: Too many return points can be indication that code is
33 * attempting to do too much or may be difficult to understand.
34 * </p>
35 *
36 * @author <a href="mailto:simon@redhillconsulting.com.au">Simon Harris</a>
37 * TODO: Test for inside a static block
38 */
39 public final class ReturnCountCheck extends AbstractFormatCheck
40 {
41
42 /**
43 * A key is pointing to the warning message text in "messages.properties"
44 * file.
45 */
46 public static final String MSG_KEY = "return.count";
47
48 /** Default allowed number of return statements. */
49 private static final int DEFAULT_MAX = 2;
50
51 /** Stack of method contexts. */
52 private final FastStack<Context> contextStack = FastStack.newInstance();
53 /** Maximum allowed number of return stmts. */
54 private int max;
55 /** Current method context. */
56 private Context context;
57
58 /** Creates new instance of the checks. */
59 public ReturnCountCheck()
60 {
61 super("^equals$");
62 setMax(DEFAULT_MAX);
63 }
64
65 @Override
66 public int[] getDefaultTokens()
67 {
68 return new int[] {
69 TokenTypes.CTOR_DEF,
70 TokenTypes.METHOD_DEF,
71 TokenTypes.LITERAL_RETURN,
72 };
73 }
74
75 @Override
76 public int[] getRequiredTokens()
77 {
78 return new int[]{
79 TokenTypes.CTOR_DEF,
80 TokenTypes.METHOD_DEF,
81 };
82 }
83
84 @Override
85 public int[] getAcceptableTokens()
86 {
87 return new int[] {
88 TokenTypes.CTOR_DEF,
89 TokenTypes.METHOD_DEF,
90 TokenTypes.LITERAL_RETURN,
91 };
92 }
93
94 /**
95 * Getter for max property.
96 * @return maximum allowed number of return statements.
97 */
98 public int getMax()
99 {
100 return max;
101 }
102
103 /**
104 * Setter for max property.
105 * @param max maximum allowed number of return statements.
106 */
107 public void setMax(int max)
108 {
109 this.max = max;
110 }
111
112 @Override
113 public void beginTree(DetailAST rootAST)
114 {
115 context = null;
116 contextStack.clear();
117 }
118
119 @Override
120 public void visitToken(DetailAST ast)
121 {
122 switch (ast.getType()) {
123 case TokenTypes.CTOR_DEF:
124 case TokenTypes.METHOD_DEF:
125 visitMethodDef(ast);
126 break;
127 case TokenTypes.LITERAL_RETURN:
128 context.visitLiteralReturn();
129 break;
130 default:
131 throw new IllegalStateException(ast.toString());
132 }
133 }
134
135 @Override
136 public void leaveToken(DetailAST ast)
137 {
138 switch (ast.getType()) {
139 case TokenTypes.CTOR_DEF:
140 case TokenTypes.METHOD_DEF:
141 leaveMethodDef(ast);
142 break;
143 case TokenTypes.LITERAL_RETURN:
144 // Do nothing
145 break;
146 default:
147 throw new IllegalStateException(ast.toString());
148 }
149 }
150
151 /**
152 * Creates new method context and places old one on the stack.
153 * @param ast method definition for check.
154 */
155 private void visitMethodDef(DetailAST ast)
156 {
157 contextStack.push(context);
158 final DetailAST methodNameAST = ast.findFirstToken(TokenTypes.IDENT);
159 context =
160 new Context(!getRegexp().matcher(methodNameAST.getText()).find());
161 }
162
163 /**
164 * Checks number of return statements and restore
165 * previous method context.
166 * @param ast method def node.
167 */
168 private void leaveMethodDef(DetailAST ast)
169 {
170 context.checkCount(ast);
171 context = contextStack.pop();
172 }
173
174 /**
175 * Class to encapsulate information about one method.
176 * @author <a href="mailto:simon@redhillconsulting.com.au">Simon Harris</a>
177 */
178 private class Context
179 {
180 /** Whether we should check this method or not. */
181 private final boolean checking;
182 /** Counter for return statements. */
183 private int count;
184
185 /**
186 * Creates new method context.
187 * @param checking should we check this method or not.
188 */
189 public Context(boolean checking)
190 {
191 this.checking = checking;
192 count = 0;
193 }
194
195 /** Increase number of return statements. */
196 public void visitLiteralReturn()
197 {
198 ++count;
199 }
200
201 /**
202 * Checks if number of return statements in method more
203 * than allowed.
204 * @param ast method def associated with this context.
205 */
206 public void checkCount(DetailAST ast)
207 {
208 if (checking && (count > getMax())) {
209 log(ast.getLineNo(), ast.getColumnNo(), MSG_KEY,
210 count, getMax());
211 }
212 }
213 }
214 }