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}