001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2026 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.NullUtil; 029import com.puppycrawl.tools.checkstyle.utils.TokenUtil; 030 031/** 032 * <div> 033 * Checks for empty catch blocks. 034 * By default, check allows empty catch block with any comment inside. 035 * </div> 036 * 037 * <p> 038 * Notes: 039 * There are two options to make validation more precise: <b>exceptionVariableName</b> and 040 * <b>commentFormat</b>. 041 * If both options are specified - they are applied by <b>any of them is matching</b>. 042 * </p> 043 * 044 * @since 6.4 045 */ 046@StatelessCheck 047public class EmptyCatchBlockCheck extends AbstractCheck { 048 049 /** 050 * A key is pointing to the warning message text in "messages.properties" file. 051 */ 052 public static final String MSG_KEY_CATCH_BLOCK_EMPTY = "catch.block.empty"; 053 054 /** 055 * A pattern to split on line ends. 056 */ 057 private static final Pattern LINE_END_PATTERN = Pattern.compile("\\r?+\\n|\\r"); 058 059 /** 060 * Specify the RegExp for the name of the variable associated with exception. 061 * If check meets variable name matching specified value - empty block is suppressed. 062 */ 063 private Pattern exceptionVariableName = Pattern.compile("^$"); 064 065 /** 066 * Specify the RegExp for the first comment inside empty catch block. 067 * If check meets comment inside empty catch block matching specified format - empty 068 * block is suppressed. If it is multi-line comment - only its first line is analyzed. 069 */ 070 private Pattern commentFormat = Pattern.compile(".*"); 071 072 /** 073 * Creates a new {@code EmptyCatchBlockCheck} instance. 074 */ 075 public EmptyCatchBlockCheck() { 076 // no code by default 077 } 078 079 /** 080 * Setter to specify the RegExp for the name of the variable associated with exception. 081 * If check meets variable name matching specified value - empty block is suppressed. 082 * 083 * @param exceptionVariablePattern 084 * pattern of exception's variable name. 085 * @since 6.4 086 */ 087 public void setExceptionVariableName(Pattern exceptionVariablePattern) { 088 exceptionVariableName = exceptionVariablePattern; 089 } 090 091 /** 092 * Setter to specify the RegExp for the first comment inside empty catch block. 093 * If check meets comment inside empty catch block matching specified format - empty 094 * block is suppressed. If it is multi-line comment - only its first line is analyzed. 095 * 096 * @param commentPattern 097 * pattern of comment. 098 * @since 6.4 099 */ 100 public void setCommentFormat(Pattern commentPattern) { 101 commentFormat = commentPattern; 102 } 103 104 @Override 105 public int[] getDefaultTokens() { 106 return getRequiredTokens(); 107 } 108 109 @Override 110 public int[] getAcceptableTokens() { 111 return getRequiredTokens(); 112 } 113 114 @Override 115 public int[] getRequiredTokens() { 116 return new int[] { 117 TokenTypes.LITERAL_CATCH, 118 }; 119 } 120 121 @Override 122 public boolean isCommentNodesRequired() { 123 return true; 124 } 125 126 @Override 127 public void visitToken(DetailAST ast) { 128 visitCatchBlock(ast); 129 } 130 131 /** 132 * Visits catch ast node, if it is empty catch block - checks it according to 133 * Check's options. If exception's variable name or comment inside block are matching 134 * specified regexp - skips from consideration, else - puts violation. 135 * 136 * @param catchAst {@link TokenTypes#LITERAL_CATCH LITERAL_CATCH} 137 */ 138 private void visitCatchBlock(DetailAST catchAst) { 139 if (isEmptyCatchBlock(catchAst)) { 140 final String commentContent = getCommentFirstLine(catchAst); 141 if (isVerifiable(catchAst, commentContent)) { 142 log(catchAst.findFirstToken(TokenTypes.SLIST), MSG_KEY_CATCH_BLOCK_EMPTY); 143 } 144 } 145 } 146 147 /** 148 * Gets the first line of comment in catch block. If comment is single-line - 149 * returns it fully, else if comment is multi-line - returns the first line. 150 * 151 * @param catchAst {@link TokenTypes#LITERAL_CATCH LITERAL_CATCH} 152 * @return the first line of comment in catch block, "" if no comment was found. 153 */ 154 private static String getCommentFirstLine(DetailAST catchAst) { 155 final DetailAST slistToken = catchAst.getLastChild(); 156 final DetailAST firstElementInBlock = slistToken.getFirstChild(); 157 String commentContent = ""; 158 if (firstElementInBlock.getType() == TokenTypes.SINGLE_LINE_COMMENT) { 159 commentContent = firstElementInBlock.getFirstChild().getText(); 160 } 161 else if (firstElementInBlock.getType() == TokenTypes.BLOCK_COMMENT_BEGIN) { 162 commentContent = firstElementInBlock.getFirstChild().getText(); 163 final String[] lines = LINE_END_PATTERN.split(commentContent, -1); 164 for (String line : lines) { 165 if (!line.isEmpty()) { 166 commentContent = line; 167 break; 168 } 169 } 170 } 171 return commentContent; 172 } 173 174 /** 175 * Checks if current empty catch block is verifiable according to Check's options 176 * (exception's variable name and comment format are both in consideration). 177 * 178 * @param emptyCatchAst empty catch {@link TokenTypes#LITERAL_CATCH LITERAL_CATCH} block. 179 * @param commentContent text of comment. 180 * @return true if empty catch block is verifiable by Check. 181 */ 182 private boolean isVerifiable(DetailAST emptyCatchAst, String commentContent) { 183 final String variableName = getExceptionVariableName(emptyCatchAst); 184 final boolean isMatchingVariableName = exceptionVariableName 185 .matcher(variableName).find(); 186 final boolean isMatchingCommentContent = !commentContent.isEmpty() 187 && commentFormat.matcher(commentContent).find(); 188 return !isMatchingVariableName && !isMatchingCommentContent; 189 } 190 191 /** 192 * Checks if catch block is empty or contains only comments. 193 * 194 * @param catchAst {@link TokenTypes#LITERAL_CATCH LITERAL_CATCH} 195 * @return true if catch block is empty. 196 */ 197 private static boolean isEmptyCatchBlock(DetailAST catchAst) { 198 boolean result = true; 199 final DetailAST slistToken = catchAst.findFirstToken(TokenTypes.SLIST); 200 DetailAST catchBlockStmt = slistToken.getFirstChild(); 201 while (catchBlockStmt.getType() != TokenTypes.RCURLY) { 202 if (catchBlockStmt.getType() != TokenTypes.SINGLE_LINE_COMMENT 203 && catchBlockStmt.getType() != TokenTypes.BLOCK_COMMENT_BEGIN) { 204 result = false; 205 break; 206 } 207 catchBlockStmt = catchBlockStmt.getNextSibling(); 208 } 209 return result; 210 } 211 212 /** 213 * Gets variable's name associated with exception. 214 * 215 * @param catchAst {@link TokenTypes#LITERAL_CATCH LITERAL_CATCH} 216 * @return Variable's name associated with exception. 217 */ 218 private static String getExceptionVariableName(DetailAST catchAst) { 219 final DetailAST parameterDef = NullUtil.notNull( 220 catchAst.findFirstToken(TokenTypes.PARAMETER_DEF)); 221 final DetailAST variableName = TokenUtil.getIdent(parameterDef); 222 return variableName.getText(); 223 } 224 225}