001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2018 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.blocks; 021 022import java.util.regex.Pattern; 023 024import com.puppycrawl.tools.checkstyle.StatelessCheck; 025import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 026import com.puppycrawl.tools.checkstyle.api.DetailAST; 027import com.puppycrawl.tools.checkstyle.api.TokenTypes; 028import com.puppycrawl.tools.checkstyle.utils.CommonUtils; 029 030/** 031 * <p> 032 * Checks for empty catch blocks. There are two options to make validation more precise: 033 * </p> 034 * 035 * <p><b>exceptionVariableName</b> - the name of variable associated with exception, 036 * if Check meets variable name matching specified value - empty block is suppressed.<br> 037 * default value: "^$" 038 * </p> 039 * 040 * <p><b>commentFormat</b> - the format of the first comment inside empty catch 041 * block, if Check meets comment inside empty catch block matching specified format 042 * - empty block is suppressed. If it is multi-line comment - only its first line is analyzed.<br> 043 * default value: ".*"<br> 044 * So, by default Check allows empty catch block with any comment inside. 045 * </p> 046 * <p> 047 * If both options are specified - they are applied by <b>any of them is matching</b>. 048 * </p> 049 * Examples: 050 * <p> 051 * To configure the Check to suppress empty catch block if exception's variable name is 052 * <b>expected</b> or <b>ignore</b>: 053 * </p> 054 * <pre> 055 * <module name="EmptyCatchBlock"> 056 * <property name="exceptionVariableName" value="ignore|expected;/> 057 * </module> 058 * </pre> 059 * 060 * <p>Such empty blocks would be both suppressed:<br> 061 * </p> 062 * <pre> 063 * {@code 064 * try { 065 * throw new RuntimeException(); 066 * } catch (RuntimeException expected) { 067 * } 068 * } 069 * {@code 070 * try { 071 * throw new RuntimeException(); 072 * } catch (RuntimeException ignore) { 073 * } 074 * } 075 * </pre> 076 * <p> 077 * To configure the Check to suppress empty catch block if single-line comment inside 078 * is "//This is expected": 079 * </p> 080 * <pre> 081 * <module name="EmptyCatchBlock"> 082 * <property name="commentFormat" value="This is expected"/> 083 * </module> 084 * </pre> 085 * 086 * <p>Such empty block would be suppressed:<br> 087 * </p> 088 * <pre> 089 * {@code 090 * try { 091 * throw new RuntimeException(); 092 * } catch (RuntimeException ex) { 093 * //This is expected 094 * } 095 * } 096 * </pre> 097 * <p> 098 * To configure the Check to suppress empty catch block if single-line comment inside 099 * is "//This is expected" or exception's variable name is "myException": 100 * </p> 101 * <pre> 102 * <module name="EmptyCatchBlock"> 103 * <property name="commentFormat" value="This is expected"/> 104 * <property name="exceptionVariableName" value="myException"/> 105 * </module> 106 * </pre> 107 * 108 * <p>Such empty blocks would be both suppressed:<br> 109 * </p> 110 * <pre> 111 * {@code 112 * try { 113 * throw new RuntimeException(); 114 * } catch (RuntimeException ex) { 115 * //This is expected 116 * } 117 * } 118 * {@code 119 * try { 120 * throw new RuntimeException(); 121 * } catch (RuntimeException myException) { 122 * 123 * } 124 * } 125 * </pre> 126 * @author <a href="mailto:nesterenko-aleksey@list.ru">Aleksey Nesterenko</a> 127 */ 128@StatelessCheck 129public class EmptyCatchBlockCheck extends AbstractCheck { 130 131 /** 132 * A key is pointing to the warning message text in "messages.properties" 133 * file. 134 */ 135 public static final String MSG_KEY_CATCH_BLOCK_EMPTY = "catch.block.empty"; 136 137 /** Format of skipping exception's variable name. */ 138 private String exceptionVariableName = "^$"; 139 140 /** Format of comment. */ 141 private String commentFormat = ".*"; 142 143 /** 144 * Regular expression pattern compiled from exception's variable name. 145 */ 146 private Pattern variableNameRegexp = Pattern.compile(exceptionVariableName); 147 148 /** 149 * Regular expression pattern compiled from comment's format. 150 */ 151 private Pattern commentRegexp = Pattern.compile(commentFormat); 152 153 /** 154 * Setter for exception's variable name format. 155 * @param exceptionVariableName 156 * format of exception's variable name. 157 * @throws org.apache.commons.beanutils.ConversionException 158 * if unable to create Pattern object. 159 */ 160 public void setExceptionVariableName(String exceptionVariableName) { 161 this.exceptionVariableName = exceptionVariableName; 162 variableNameRegexp = CommonUtils.createPattern(exceptionVariableName); 163 } 164 165 /** 166 * Setter for comment format. 167 * @param commentFormat 168 * format of comment. 169 * @throws org.apache.commons.beanutils.ConversionException 170 * if unable to create Pattern object. 171 */ 172 public void setCommentFormat(String commentFormat) { 173 this.commentFormat = commentFormat; 174 commentRegexp = CommonUtils.createPattern(commentFormat); 175 } 176 177 @Override 178 public int[] getDefaultTokens() { 179 return getRequiredTokens(); 180 } 181 182 @Override 183 public int[] getAcceptableTokens() { 184 return getRequiredTokens(); 185 } 186 187 @Override 188 public int[] getRequiredTokens() { 189 return new int[] { 190 TokenTypes.LITERAL_CATCH, 191 }; 192 } 193 194 @Override 195 public boolean isCommentNodesRequired() { 196 return true; 197 } 198 199 @Override 200 public void visitToken(DetailAST ast) { 201 visitCatchBlock(ast); 202 } 203 204 /** 205 * Visits catch ast node, if it is empty catch block - checks it according to 206 * Check's options. If exception's variable name or comment inside block are matching 207 * specified regexp - skips from consideration, else - puts violation. 208 * @param catchAst {@link TokenTypes#LITERAL_CATCH LITERAL_CATCH} 209 */ 210 private void visitCatchBlock(DetailAST catchAst) { 211 if (isEmptyCatchBlock(catchAst)) { 212 final String commentContent = getCommentFirstLine(catchAst); 213 if (isVerifiable(catchAst, commentContent)) { 214 log(catchAst.getLineNo(), MSG_KEY_CATCH_BLOCK_EMPTY); 215 } 216 } 217 } 218 219 /** 220 * Gets the first line of comment in catch block. If comment is single-line - 221 * returns it fully, else if comment is multi-line - returns the first line. 222 * @param catchAst {@link TokenTypes#LITERAL_CATCH LITERAL_CATCH} 223 * @return the first line of comment in catch block, "" if no comment was found. 224 */ 225 private static String getCommentFirstLine(DetailAST catchAst) { 226 final DetailAST slistToken = catchAst.getLastChild(); 227 final DetailAST firstElementInBlock = slistToken.getFirstChild(); 228 String commentContent = ""; 229 if (firstElementInBlock.getType() == TokenTypes.SINGLE_LINE_COMMENT) { 230 commentContent = firstElementInBlock.getFirstChild().getText(); 231 } 232 else if (firstElementInBlock.getType() == TokenTypes.BLOCK_COMMENT_BEGIN) { 233 commentContent = firstElementInBlock.getFirstChild().getText(); 234 final String[] lines = commentContent.split(System.getProperty("line.separator")); 235 for (String line : lines) { 236 if (!line.isEmpty()) { 237 commentContent = line; 238 break; 239 } 240 } 241 } 242 return commentContent; 243 } 244 245 /** 246 * Checks if current empty catch block is verifiable according to Check's options 247 * (exception's variable name and comment format are both in consideration). 248 * @param emptyCatchAst empty catch {@link TokenTypes#LITERAL_CATCH LITERAL_CATCH} block. 249 * @param commentContent text of comment. 250 * @return true if empty catch block is verifiable by Check. 251 */ 252 private boolean isVerifiable(DetailAST emptyCatchAst, String commentContent) { 253 final String variableName = getExceptionVariableName(emptyCatchAst); 254 final boolean isMatchingVariableName = variableNameRegexp 255 .matcher(variableName).find(); 256 final boolean isMatchingCommentContent = !commentContent.isEmpty() 257 && commentRegexp.matcher(commentContent).find(); 258 return !isMatchingVariableName && !isMatchingCommentContent; 259 } 260 261 /** 262 * Checks if catch block is empty or contains only comments. 263 * @param catchAst {@link TokenTypes#LITERAL_CATCH LITERAL_CATCH} 264 * @return true if catch block is empty. 265 */ 266 private static boolean isEmptyCatchBlock(DetailAST catchAst) { 267 boolean result = true; 268 final DetailAST slistToken = catchAst.findFirstToken(TokenTypes.SLIST); 269 DetailAST catchBlockStmt = slistToken.getFirstChild(); 270 while (catchBlockStmt.getType() != TokenTypes.RCURLY) { 271 if (catchBlockStmt.getType() != TokenTypes.SINGLE_LINE_COMMENT 272 && catchBlockStmt.getType() != TokenTypes.BLOCK_COMMENT_BEGIN) { 273 result = false; 274 break; 275 } 276 catchBlockStmt = catchBlockStmt.getNextSibling(); 277 } 278 return result; 279 } 280 281 /** 282 * Gets variable's name associated with exception. 283 * @param catchAst {@link TokenTypes#LITERAL_CATCH LITERAL_CATCH} 284 * @return Variable's name associated with exception. 285 */ 286 private static String getExceptionVariableName(DetailAST catchAst) { 287 final DetailAST parameterDef = catchAst.findFirstToken(TokenTypes.PARAMETER_DEF); 288 final DetailAST variableName = parameterDef.findFirstToken(TokenTypes.IDENT); 289 return variableName.getText(); 290 } 291 292}