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