1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2019 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.Locale;
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.CommonUtil;
29  
30  /**
31   * <p>
32   * Checks for empty blocks. This check does not validate sequential blocks.
33   * </p>
34   * <p>
35   * Sequential blocks won't be checked. Also, no violations for fallthrough:
36   * </p>
37   * <pre>
38   * switch (a) {
39   *   case 1:                          // no violation
40   *   case 2:                          // no violation
41   *   case 3: someMethod(); { }        // no violation
42   *   default: break;
43   * }
44   * </pre>
45   * <p>
46   * This check processes LITERAL_CASE and LITERAL_DEFAULT separately.
47   * So, if tokens=LITERAL_DEFAULT, following code will not trigger any violation,
48   * as the empty block belongs to LITERAL_CASE:
49   * </p>
50   * <p>
51   * Configuration:
52   * </p>
53   * <pre>
54   * &lt;module name=&quot;EmptyBlock&quot;&gt;
55   *   &lt;property name=&quot;tokens&quot; value=&quot;LITERAL_DEFAULT&quot;/&gt;
56   * &lt;/module&gt;
57   * </pre>
58   * <p>
59   * Result:
60   * </p>
61   * <pre>
62   * switch (a) {
63   *   default:        // no violation for "default:" as empty block belong to "case 1:"
64   *   case 1: { }
65   * }
66   * </pre>
67   * <ul>
68   * <li>
69   * Property {@code option} - specify the policy on block contents.
70   * Default value is {@code statement}.
71   * </li>
72   * <li>
73   * Property {@code tokens} - tokens to check
74   * Default value is:
75   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_WHILE">
76   * LITERAL_WHILE</a>,
77   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_TRY">
78   * LITERAL_TRY</a>,
79   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_FINALLY">
80   * LITERAL_FINALLY</a>,
81   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_DO">
82   * LITERAL_DO</a>,
83   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_IF">
84   * LITERAL_IF</a>,
85   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_ELSE">
86   * LITERAL_ELSE</a>,
87   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_FOR">
88   * LITERAL_FOR</a>,
89   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#INSTANCE_INIT">
90   * INSTANCE_INIT</a>,
91   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#STATIC_INIT">
92   * STATIC_INIT</a>,
93   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_SWITCH">
94   * LITERAL_SWITCH</a>,
95   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_SYNCHRONIZED">
96   * LITERAL_SYNCHRONIZED</a>.
97   * </li>
98   * </ul>
99   * <p>
100  * To configure the check:
101  * </p>
102  * <pre>
103  * &lt;module name="EmptyBlock"/&gt;
104  * </pre>
105  * <p>
106  * To configure the check for the {@code text} policy and only {@code try} blocks:
107  * </p>
108  * <pre>
109  * &lt;module name=&quot;EmptyBlock&quot;&gt;
110  *   &lt;property name=&quot;option&quot; value=&quot;text&quot;/&gt;
111  *   &lt;property name=&quot;tokens&quot; value=&quot;LITERAL_TRY&quot;/&gt;
112  * &lt;/module&gt;
113  * </pre>
114  *
115  * @since 3.0
116  */
117 @StatelessCheck
118 public class EmptyBlockCheck
119     extends AbstractCheck {
120 
121     /**
122      * A key is pointing to the warning message text in "messages.properties"
123      * file.
124      */
125     public static final String MSG_KEY_BLOCK_NO_STATEMENT = "block.noStatement";
126 
127     /**
128      * A key is pointing to the warning message text in "messages.properties"
129      * file.
130      */
131     public static final String MSG_KEY_BLOCK_EMPTY = "block.empty";
132 
133     /** Specify the policy on block contents. */
134     private BlockOption option = BlockOption.STATEMENT;
135 
136     /**
137      * Setter to specify the policy on block contents.
138      * @param optionStr string to decode option from
139      * @throws IllegalArgumentException if unable to decode
140      */
141     public void setOption(String optionStr) {
142         option = BlockOption.valueOf(optionStr.trim().toUpperCase(Locale.ENGLISH));
143     }
144 
145     @Override
146     public int[] getDefaultTokens() {
147         return new int[] {
148             TokenTypes.LITERAL_WHILE,
149             TokenTypes.LITERAL_TRY,
150             TokenTypes.LITERAL_FINALLY,
151             TokenTypes.LITERAL_DO,
152             TokenTypes.LITERAL_IF,
153             TokenTypes.LITERAL_ELSE,
154             TokenTypes.LITERAL_FOR,
155             TokenTypes.INSTANCE_INIT,
156             TokenTypes.STATIC_INIT,
157             TokenTypes.LITERAL_SWITCH,
158             TokenTypes.LITERAL_SYNCHRONIZED,
159         };
160     }
161 
162     @Override
163     public int[] getAcceptableTokens() {
164         return new int[] {
165             TokenTypes.LITERAL_WHILE,
166             TokenTypes.LITERAL_TRY,
167             TokenTypes.LITERAL_CATCH,
168             TokenTypes.LITERAL_FINALLY,
169             TokenTypes.LITERAL_DO,
170             TokenTypes.LITERAL_IF,
171             TokenTypes.LITERAL_ELSE,
172             TokenTypes.LITERAL_FOR,
173             TokenTypes.INSTANCE_INIT,
174             TokenTypes.STATIC_INIT,
175             TokenTypes.LITERAL_SWITCH,
176             TokenTypes.LITERAL_SYNCHRONIZED,
177             TokenTypes.LITERAL_CASE,
178             TokenTypes.LITERAL_DEFAULT,
179             TokenTypes.ARRAY_INIT,
180         };
181     }
182 
183     @Override
184     public int[] getRequiredTokens() {
185         return CommonUtil.EMPTY_INT_ARRAY;
186     }
187 
188     @Override
189     public void visitToken(DetailAST ast) {
190         final DetailAST leftCurly = findLeftCurly(ast);
191         if (leftCurly != null) {
192             if (option == BlockOption.STATEMENT) {
193                 final boolean emptyBlock;
194                 if (leftCurly.getType() == TokenTypes.LCURLY) {
195                     emptyBlock = leftCurly.getNextSibling().getType() != TokenTypes.CASE_GROUP;
196                 }
197                 else {
198                     emptyBlock = leftCurly.getChildCount() <= 1;
199                 }
200                 if (emptyBlock) {
201                     log(leftCurly,
202                         MSG_KEY_BLOCK_NO_STATEMENT,
203                         ast.getText());
204                 }
205             }
206             else if (!hasText(leftCurly)) {
207                 log(leftCurly,
208                     MSG_KEY_BLOCK_EMPTY,
209                     ast.getText());
210             }
211         }
212     }
213 
214     /**
215      * Checks if SLIST token contains any text.
216      * @param slistAST a {@code DetailAST} value
217      * @return whether the SLIST token contains any text.
218      */
219     private boolean hasText(final DetailAST slistAST) {
220         final DetailAST rightCurly = slistAST.findFirstToken(TokenTypes.RCURLY);
221         final DetailAST rcurlyAST;
222 
223         if (rightCurly == null) {
224             rcurlyAST = slistAST.getParent().findFirstToken(TokenTypes.RCURLY);
225         }
226         else {
227             rcurlyAST = rightCurly;
228         }
229         final int slistLineNo = slistAST.getLineNo();
230         final int slistColNo = slistAST.getColumnNo();
231         final int rcurlyLineNo = rcurlyAST.getLineNo();
232         final int rcurlyColNo = rcurlyAST.getColumnNo();
233         final String[] lines = getLines();
234         boolean returnValue = false;
235         if (slistLineNo == rcurlyLineNo) {
236             // Handle braces on the same line
237             final String txt = lines[slistLineNo - 1]
238                     .substring(slistColNo + 1, rcurlyColNo);
239             if (!CommonUtil.isBlank(txt)) {
240                 returnValue = true;
241             }
242         }
243         else {
244             final String firstLine = lines[slistLineNo - 1].substring(slistColNo + 1);
245             final String lastLine = lines[rcurlyLineNo - 1].substring(0, rcurlyColNo);
246             // check if all lines are also only whitespace
247             returnValue = !(CommonUtil.isBlank(firstLine) && CommonUtil.isBlank(lastLine))
248                     || !checkIsAllLinesAreWhitespace(lines, slistLineNo, rcurlyLineNo);
249         }
250         return returnValue;
251     }
252 
253     /**
254      * Checks is all lines in array contain whitespaces only.
255      *
256      * @param lines
257      *            array of lines
258      * @param lineFrom
259      *            check from this line number
260      * @param lineTo
261      *            check to this line numbers
262      * @return true if lines contain only whitespaces
263      */
264     private static boolean checkIsAllLinesAreWhitespace(String[] lines, int lineFrom, int lineTo) {
265         boolean result = true;
266         for (int i = lineFrom; i < lineTo - 1; i++) {
267             if (!CommonUtil.isBlank(lines[i])) {
268                 result = false;
269                 break;
270             }
271         }
272         return result;
273     }
274 
275     /**
276      * Calculates the left curly corresponding to the block to be checked.
277      *
278      * @param ast a {@code DetailAST} value
279      * @return the left curly corresponding to the block to be checked
280      */
281     private static DetailAST findLeftCurly(DetailAST ast) {
282         final DetailAST leftCurly;
283         final DetailAST slistAST = ast.findFirstToken(TokenTypes.SLIST);
284         if ((ast.getType() == TokenTypes.LITERAL_CASE
285                 || ast.getType() == TokenTypes.LITERAL_DEFAULT)
286                 && ast.getNextSibling() != null
287                 && ast.getNextSibling().getFirstChild() != null
288                 && ast.getNextSibling().getFirstChild().getType() == TokenTypes.SLIST) {
289             leftCurly = ast.getNextSibling().getFirstChild();
290         }
291         else if (slistAST == null) {
292             leftCurly = ast.findFirstToken(TokenTypes.LCURLY);
293         }
294         else {
295             leftCurly = slistAST;
296         }
297         return leftCurly;
298     }
299 
300 }