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       * 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 }