View Javadoc
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       * Creates a new {@code EmptyCatchBlockCheck} instance.
75       */
76      public EmptyCatchBlockCheck() {
77          // no code by default
78      }
79  
80      /**
81       * Setter to specify the RegExp for the name of the variable associated with exception.
82       * If check meets variable name matching specified value - empty block is suppressed.
83       *
84       * @param exceptionVariablePattern
85       *        pattern of exception's variable name.
86       * @since 6.4
87       */
88      public void setExceptionVariableName(Pattern exceptionVariablePattern) {
89          exceptionVariableName = exceptionVariablePattern;
90      }
91  
92      /**
93       * Setter to specify the RegExp for the first comment inside empty catch block.
94       * If check meets comment inside empty catch block matching specified format - empty
95       * block is suppressed. If it is multi-line comment - only its first line is analyzed.
96       *
97       * @param commentPattern
98       *        pattern of comment.
99       * @since 6.4
100      */
101     public void setCommentFormat(Pattern commentPattern) {
102         commentFormat = commentPattern;
103     }
104 
105     @Override
106     public int[] getDefaultTokens() {
107         return getRequiredTokens();
108     }
109 
110     @Override
111     public int[] getAcceptableTokens() {
112         return getRequiredTokens();
113     }
114 
115     @Override
116     public int[] getRequiredTokens() {
117         return new int[] {
118             TokenTypes.LITERAL_CATCH,
119         };
120     }
121 
122     @Override
123     public boolean isCommentNodesRequired() {
124         return true;
125     }
126 
127     @Override
128     public void visitToken(DetailAST ast) {
129         visitCatchBlock(ast);
130     }
131 
132     /**
133      * Visits catch ast node, if it is empty catch block - checks it according to
134      *  Check's options. If exception's variable name or comment inside block are matching
135      *   specified regexp - skips from consideration, else - puts violation.
136      *
137      * @param catchAst {@link TokenTypes#LITERAL_CATCH LITERAL_CATCH}
138      */
139     private void visitCatchBlock(DetailAST catchAst) {
140         if (isEmptyCatchBlock(catchAst)) {
141             final String commentContent = getCommentFirstLine(catchAst);
142             if (isVerifiable(catchAst, commentContent)) {
143                 log(catchAst.findFirstToken(TokenTypes.SLIST), MSG_KEY_CATCH_BLOCK_EMPTY);
144             }
145         }
146     }
147 
148     /**
149      * Gets the first line of comment in catch block. If comment is single-line -
150      *  returns it fully, else if comment is multi-line - returns the first line.
151      *
152      * @param catchAst {@link TokenTypes#LITERAL_CATCH LITERAL_CATCH}
153      * @return the first line of comment in catch block, "" if no comment was found.
154      */
155     private static String getCommentFirstLine(DetailAST catchAst) {
156         final DetailAST slistToken = catchAst.getLastChild();
157         final DetailAST firstElementInBlock = slistToken.getFirstChild();
158         String commentContent = "";
159         if (firstElementInBlock.getType() == TokenTypes.SINGLE_LINE_COMMENT) {
160             commentContent = firstElementInBlock.getFirstChild().getText();
161         }
162         else if (firstElementInBlock.getType() == TokenTypes.BLOCK_COMMENT_BEGIN) {
163             commentContent = firstElementInBlock.getFirstChild().getText();
164             final String[] lines = LINE_END_PATTERN.split(commentContent, -1);
165             for (String line : lines) {
166                 if (!line.isEmpty()) {
167                     commentContent = line;
168                     break;
169                 }
170             }
171         }
172         return commentContent;
173     }
174 
175     /**
176      * Checks if current empty catch block is verifiable according to Check's options
177      *  (exception's variable name and comment format are both in consideration).
178      *
179      * @param emptyCatchAst empty catch {@link TokenTypes#LITERAL_CATCH LITERAL_CATCH} block.
180      * @param commentContent text of comment.
181      * @return true if empty catch block is verifiable by Check.
182      */
183     private boolean isVerifiable(DetailAST emptyCatchAst, String commentContent) {
184         final String variableName = getExceptionVariableName(emptyCatchAst);
185         final boolean isMatchingVariableName = exceptionVariableName
186                 .matcher(variableName).find();
187         final boolean isMatchingCommentContent = !commentContent.isEmpty()
188                  && commentFormat.matcher(commentContent).find();
189         return !isMatchingVariableName && !isMatchingCommentContent;
190     }
191 
192     /**
193      * Checks if catch block is empty or contains only comments.
194      *
195      * @param catchAst {@link TokenTypes#LITERAL_CATCH LITERAL_CATCH}
196      * @return true if catch block is empty.
197      */
198     private static boolean isEmptyCatchBlock(DetailAST catchAst) {
199         boolean result = true;
200         final DetailAST slistToken = catchAst.findFirstToken(TokenTypes.SLIST);
201         DetailAST catchBlockStmt = slistToken.getFirstChild();
202         while (catchBlockStmt.getType() != TokenTypes.RCURLY) {
203             if (catchBlockStmt.getType() != TokenTypes.SINGLE_LINE_COMMENT
204                  && catchBlockStmt.getType() != TokenTypes.BLOCK_COMMENT_BEGIN) {
205                 result = false;
206                 break;
207             }
208             catchBlockStmt = catchBlockStmt.getNextSibling();
209         }
210         return result;
211     }
212 
213     /**
214      * Gets variable's name associated with exception.
215      *
216      * @param catchAst {@link TokenTypes#LITERAL_CATCH LITERAL_CATCH}
217      * @return Variable's name associated with exception.
218      */
219     private static String getExceptionVariableName(DetailAST catchAst) {
220         final DetailAST parameterDef = NullUtil.notNull(
221                 catchAst.findFirstToken(TokenTypes.PARAMETER_DEF));
222         final DetailAST variableName = TokenUtil.getIdent(parameterDef);
223         return variableName.getText();
224     }
225 
226 }