1 ///////////////////////////////////////////////////////////////////////////////////////////////
2 // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3 // Copyright (C) 2001-2026 the original author or authors.
4 //
5 // This library is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU Lesser General Public
7 // License as published by the Free Software Foundation; either
8 // version 2.1 of the License, or (at your option) any later version.
9 //
10 // This library is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 // Lesser General Public License for more details.
14 //
15 // You should have received a copy of the GNU Lesser General Public
16 // License along with this library; if not, write to the Free Software
17 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 ///////////////////////////////////////////////////////////////////////////////////////////////
19
20 package com.puppycrawl.tools.checkstyle.checks.blocks;
21
22 import java.util.regex.Pattern;
23
24 import com.puppycrawl.tools.checkstyle.StatelessCheck;
25 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
26 import com.puppycrawl.tools.checkstyle.api.DetailAST;
27 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
28 import com.puppycrawl.tools.checkstyle.utils.NullUtil;
29 import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
30
31 /**
32 * <div>
33 * Checks for empty catch blocks.
34 * By default, check allows empty catch block with any comment inside.
35 * </div>
36 *
37 * <p>
38 * Notes:
39 * There are two options to make validation more precise: <b>exceptionVariableName</b> and
40 * <b>commentFormat</b>.
41 * If both options are specified - they are applied by <b>any of them is matching</b>.
42 * </p>
43 *
44 * @since 6.4
45 */
46 @StatelessCheck
47 public class EmptyCatchBlockCheck extends AbstractCheck {
48
49 /**
50 * A key is pointing to the warning message text in "messages.properties"
51 * file.
52 */
53 public static final String MSG_KEY_CATCH_BLOCK_EMPTY = "catch.block.empty";
54
55 /**
56 * A pattern to split on line ends.
57 */
58 private static final Pattern LINE_END_PATTERN = Pattern.compile("\\r?+\\n|\\r");
59
60 /**
61 * Specify the RegExp for the name of the variable associated with exception.
62 * If check meets variable name matching specified value - empty block is suppressed.
63 */
64 private Pattern exceptionVariableName = Pattern.compile("^$");
65
66 /**
67 * Specify the RegExp for the first comment inside empty catch block.
68 * If check meets comment inside empty catch block matching specified format - empty
69 * block is suppressed. If it is multi-line comment - only its first line is analyzed.
70 */
71 private Pattern commentFormat = Pattern.compile(".*");
72
73 /**
74 * Setter to specify the RegExp for the name of the variable associated with exception.
75 * If check meets variable name matching specified value - empty block is suppressed.
76 *
77 * @param exceptionVariablePattern
78 * pattern of exception's variable name.
79 * @since 6.4
80 */
81 public void setExceptionVariableName(Pattern exceptionVariablePattern) {
82 exceptionVariableName = exceptionVariablePattern;
83 }
84
85 /**
86 * Setter to specify the RegExp for the first comment inside empty catch block.
87 * If check meets comment inside empty catch block matching specified format - empty
88 * block is suppressed. If it is multi-line comment - only its first line is analyzed.
89 *
90 * @param commentPattern
91 * pattern of comment.
92 * @since 6.4
93 */
94 public void setCommentFormat(Pattern commentPattern) {
95 commentFormat = commentPattern;
96 }
97
98 @Override
99 public int[] getDefaultTokens() {
100 return getRequiredTokens();
101 }
102
103 @Override
104 public int[] getAcceptableTokens() {
105 return getRequiredTokens();
106 }
107
108 @Override
109 public int[] getRequiredTokens() {
110 return new int[] {
111 TokenTypes.LITERAL_CATCH,
112 };
113 }
114
115 @Override
116 public boolean isCommentNodesRequired() {
117 return true;
118 }
119
120 @Override
121 public void visitToken(DetailAST ast) {
122 visitCatchBlock(ast);
123 }
124
125 /**
126 * Visits catch ast node, if it is empty catch block - checks it according to
127 * Check's options. If exception's variable name or comment inside block are matching
128 * specified regexp - skips from consideration, else - puts violation.
129 *
130 * @param catchAst {@link TokenTypes#LITERAL_CATCH LITERAL_CATCH}
131 */
132 private void visitCatchBlock(DetailAST catchAst) {
133 if (isEmptyCatchBlock(catchAst)) {
134 final String commentContent = getCommentFirstLine(catchAst);
135 if (isVerifiable(catchAst, commentContent)) {
136 log(catchAst.findFirstToken(TokenTypes.SLIST), MSG_KEY_CATCH_BLOCK_EMPTY);
137 }
138 }
139 }
140
141 /**
142 * Gets the first line of comment in catch block. If comment is single-line -
143 * returns it fully, else if comment is multi-line - returns the first line.
144 *
145 * @param catchAst {@link TokenTypes#LITERAL_CATCH LITERAL_CATCH}
146 * @return the first line of comment in catch block, "" if no comment was found.
147 */
148 private static String getCommentFirstLine(DetailAST catchAst) {
149 final DetailAST slistToken = catchAst.getLastChild();
150 final DetailAST firstElementInBlock = slistToken.getFirstChild();
151 String commentContent = "";
152 if (firstElementInBlock.getType() == TokenTypes.SINGLE_LINE_COMMENT) {
153 commentContent = firstElementInBlock.getFirstChild().getText();
154 }
155 else if (firstElementInBlock.getType() == TokenTypes.BLOCK_COMMENT_BEGIN) {
156 commentContent = firstElementInBlock.getFirstChild().getText();
157 final String[] lines = LINE_END_PATTERN.split(commentContent, -1);
158 for (String line : lines) {
159 if (!line.isEmpty()) {
160 commentContent = line;
161 break;
162 }
163 }
164 }
165 return commentContent;
166 }
167
168 /**
169 * Checks if current empty catch block is verifiable according to Check's options
170 * (exception's variable name and comment format are both in consideration).
171 *
172 * @param emptyCatchAst empty catch {@link TokenTypes#LITERAL_CATCH LITERAL_CATCH} block.
173 * @param commentContent text of comment.
174 * @return true if empty catch block is verifiable by Check.
175 */
176 private boolean isVerifiable(DetailAST emptyCatchAst, String commentContent) {
177 final String variableName = getExceptionVariableName(emptyCatchAst);
178 final boolean isMatchingVariableName = exceptionVariableName
179 .matcher(variableName).find();
180 final boolean isMatchingCommentContent = !commentContent.isEmpty()
181 && commentFormat.matcher(commentContent).find();
182 return !isMatchingVariableName && !isMatchingCommentContent;
183 }
184
185 /**
186 * Checks if catch block is empty or contains only comments.
187 *
188 * @param catchAst {@link TokenTypes#LITERAL_CATCH LITERAL_CATCH}
189 * @return true if catch block is empty.
190 */
191 private static boolean isEmptyCatchBlock(DetailAST catchAst) {
192 boolean result = true;
193 final DetailAST slistToken = catchAst.findFirstToken(TokenTypes.SLIST);
194 DetailAST catchBlockStmt = slistToken.getFirstChild();
195 while (catchBlockStmt.getType() != TokenTypes.RCURLY) {
196 if (catchBlockStmt.getType() != TokenTypes.SINGLE_LINE_COMMENT
197 && catchBlockStmt.getType() != TokenTypes.BLOCK_COMMENT_BEGIN) {
198 result = false;
199 break;
200 }
201 catchBlockStmt = catchBlockStmt.getNextSibling();
202 }
203 return result;
204 }
205
206 /**
207 * Gets variable's name associated with exception.
208 *
209 * @param catchAst {@link TokenTypes#LITERAL_CATCH LITERAL_CATCH}
210 * @return Variable's name associated with exception.
211 */
212 private static String getExceptionVariableName(DetailAST catchAst) {
213 final DetailAST parameterDef = NullUtil.notNull(
214 catchAst.findFirstToken(TokenTypes.PARAMETER_DEF));
215 final DetailAST variableName = TokenUtil.getIdent(parameterDef);
216 return variableName.getText();
217 }
218
219 }