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}