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