001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2020 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.Locale;
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.CommonUtil;
029
030/**
031 * <p>
032 * Checks for empty blocks. This check does not validate sequential blocks.
033 * </p>
034 * <p>
035 * Sequential blocks won't be checked. Also, no violations for fallthrough:
036 * </p>
037 * <pre>
038 * switch (a) {
039 *   case 1:                          // no violation
040 *   case 2:                          // no violation
041 *   case 3: someMethod(); { }        // no violation
042 *   default: break;
043 * }
044 * </pre>
045 * <p>
046 * This check processes LITERAL_CASE and LITERAL_DEFAULT separately.
047 * So, if tokens=LITERAL_DEFAULT, following code will not trigger any violation,
048 * as the empty block belongs to LITERAL_CASE:
049 * </p>
050 * <p>
051 * Configuration:
052 * </p>
053 * <pre>
054 * &lt;module name=&quot;EmptyBlock&quot;&gt;
055 *   &lt;property name=&quot;tokens&quot; value=&quot;LITERAL_DEFAULT&quot;/&gt;
056 * &lt;/module&gt;
057 * </pre>
058 * <p>
059 * Result:
060 * </p>
061 * <pre>
062 * switch (a) {
063 *   default:        // no violation for "default:" as empty block belong to "case 1:"
064 *   case 1: { }
065 * }
066 * </pre>
067 * <ul>
068 * <li>
069 * Property {@code option} - specify the policy on block contents.
070 * Type is {@code com.puppycrawl.tools.checkstyle.checks.blocks.BlockOption}.
071 * Default value is {@code statement}.
072 * </li>
073 * <li>
074 * Property {@code tokens} - tokens to check
075 * Type is {@code int[]}.
076 * Default value is:
077 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_WHILE">
078 * LITERAL_WHILE</a>,
079 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_TRY">
080 * LITERAL_TRY</a>,
081 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_FINALLY">
082 * LITERAL_FINALLY</a>,
083 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_DO">
084 * LITERAL_DO</a>,
085 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_IF">
086 * LITERAL_IF</a>,
087 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_ELSE">
088 * LITERAL_ELSE</a>,
089 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_FOR">
090 * LITERAL_FOR</a>,
091 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#INSTANCE_INIT">
092 * INSTANCE_INIT</a>,
093 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#STATIC_INIT">
094 * STATIC_INIT</a>,
095 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_SWITCH">
096 * LITERAL_SWITCH</a>,
097 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_SYNCHRONIZED">
098 * LITERAL_SYNCHRONIZED</a>.
099 * </li>
100 * </ul>
101 * <p>
102 * To configure the check:
103 * </p>
104 * <pre>
105 * &lt;module name="EmptyBlock"/&gt;
106 * </pre>
107 * <p>
108 * To configure the check for the {@code text} policy and only {@code try} blocks:
109 * </p>
110 * <pre>
111 * &lt;module name=&quot;EmptyBlock&quot;&gt;
112 *   &lt;property name=&quot;option&quot; value=&quot;text&quot;/&gt;
113 *   &lt;property name=&quot;tokens&quot; value=&quot;LITERAL_TRY&quot;/&gt;
114 * &lt;/module&gt;
115 * </pre>
116 * <p>
117 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
118 * </p>
119 * <p>
120 * Violation Message Keys:
121 * </p>
122 * <ul>
123 * <li>
124 * {@code block.empty}
125 * </li>
126 * <li>
127 * {@code block.noStatement}
128 * </li>
129 * </ul>
130 *
131 * @since 3.0
132 */
133@StatelessCheck
134public class EmptyBlockCheck
135    extends AbstractCheck {
136
137    /**
138     * A key is pointing to the warning message text in "messages.properties"
139     * file.
140     */
141    public static final String MSG_KEY_BLOCK_NO_STATEMENT = "block.noStatement";
142
143    /**
144     * A key is pointing to the warning message text in "messages.properties"
145     * file.
146     */
147    public static final String MSG_KEY_BLOCK_EMPTY = "block.empty";
148
149    /** Specify the policy on block contents. */
150    private BlockOption option = BlockOption.STATEMENT;
151
152    /**
153     * Setter to specify the policy on block contents.
154     *
155     * @param optionStr string to decode option from
156     * @throws IllegalArgumentException if unable to decode
157     */
158    public void setOption(String optionStr) {
159        option = BlockOption.valueOf(optionStr.trim().toUpperCase(Locale.ENGLISH));
160    }
161
162    @Override
163    public int[] getDefaultTokens() {
164        return new int[] {
165            TokenTypes.LITERAL_WHILE,
166            TokenTypes.LITERAL_TRY,
167            TokenTypes.LITERAL_FINALLY,
168            TokenTypes.LITERAL_DO,
169            TokenTypes.LITERAL_IF,
170            TokenTypes.LITERAL_ELSE,
171            TokenTypes.LITERAL_FOR,
172            TokenTypes.INSTANCE_INIT,
173            TokenTypes.STATIC_INIT,
174            TokenTypes.LITERAL_SWITCH,
175            TokenTypes.LITERAL_SYNCHRONIZED,
176        };
177    }
178
179    @Override
180    public int[] getAcceptableTokens() {
181        return new int[] {
182            TokenTypes.LITERAL_WHILE,
183            TokenTypes.LITERAL_TRY,
184            TokenTypes.LITERAL_CATCH,
185            TokenTypes.LITERAL_FINALLY,
186            TokenTypes.LITERAL_DO,
187            TokenTypes.LITERAL_IF,
188            TokenTypes.LITERAL_ELSE,
189            TokenTypes.LITERAL_FOR,
190            TokenTypes.INSTANCE_INIT,
191            TokenTypes.STATIC_INIT,
192            TokenTypes.LITERAL_SWITCH,
193            TokenTypes.LITERAL_SYNCHRONIZED,
194            TokenTypes.LITERAL_CASE,
195            TokenTypes.LITERAL_DEFAULT,
196            TokenTypes.ARRAY_INIT,
197        };
198    }
199
200    @Override
201    public int[] getRequiredTokens() {
202        return CommonUtil.EMPTY_INT_ARRAY;
203    }
204
205    @Override
206    public void visitToken(DetailAST ast) {
207        final DetailAST leftCurly = findLeftCurly(ast);
208        if (leftCurly != null) {
209            if (option == BlockOption.STATEMENT) {
210                final boolean emptyBlock;
211                if (leftCurly.getType() == TokenTypes.LCURLY) {
212                    emptyBlock = leftCurly.getNextSibling().getType() != TokenTypes.CASE_GROUP;
213                }
214                else {
215                    emptyBlock = leftCurly.getChildCount() <= 1;
216                }
217                if (emptyBlock) {
218                    log(leftCurly,
219                        MSG_KEY_BLOCK_NO_STATEMENT,
220                        ast.getText());
221                }
222            }
223            else if (!hasText(leftCurly)) {
224                log(leftCurly,
225                    MSG_KEY_BLOCK_EMPTY,
226                    ast.getText());
227            }
228        }
229    }
230
231    /**
232     * Checks if SLIST token contains any text.
233     *
234     * @param slistAST a {@code DetailAST} value
235     * @return whether the SLIST token contains any text.
236     */
237    private boolean hasText(final DetailAST slistAST) {
238        final DetailAST rightCurly = slistAST.findFirstToken(TokenTypes.RCURLY);
239        final DetailAST rcurlyAST;
240
241        if (rightCurly == null) {
242            rcurlyAST = slistAST.getParent().findFirstToken(TokenTypes.RCURLY);
243        }
244        else {
245            rcurlyAST = rightCurly;
246        }
247        final int slistLineNo = slistAST.getLineNo();
248        final int slistColNo = slistAST.getColumnNo();
249        final int rcurlyLineNo = rcurlyAST.getLineNo();
250        final int rcurlyColNo = rcurlyAST.getColumnNo();
251        final String[] lines = getLines();
252        boolean returnValue = false;
253        if (slistLineNo == rcurlyLineNo) {
254            // Handle braces on the same line
255            final String txt = lines[slistLineNo - 1]
256                    .substring(slistColNo + 1, rcurlyColNo);
257            if (!CommonUtil.isBlank(txt)) {
258                returnValue = true;
259            }
260        }
261        else {
262            final String firstLine = lines[slistLineNo - 1].substring(slistColNo + 1);
263            final String lastLine = lines[rcurlyLineNo - 1].substring(0, rcurlyColNo);
264            // check if all lines are also only whitespace
265            returnValue = !(CommonUtil.isBlank(firstLine) && CommonUtil.isBlank(lastLine))
266                    || !checkIsAllLinesAreWhitespace(lines, slistLineNo, rcurlyLineNo);
267        }
268        return returnValue;
269    }
270
271    /**
272     * Checks is all lines in array contain whitespaces only.
273     *
274     * @param lines
275     *            array of lines
276     * @param lineFrom
277     *            check from this line number
278     * @param lineTo
279     *            check to this line numbers
280     * @return true if lines contain only whitespaces
281     */
282    private static boolean checkIsAllLinesAreWhitespace(String[] lines, int lineFrom, int lineTo) {
283        boolean result = true;
284        for (int i = lineFrom; i < lineTo - 1; i++) {
285            if (!CommonUtil.isBlank(lines[i])) {
286                result = false;
287                break;
288            }
289        }
290        return result;
291    }
292
293    /**
294     * Calculates the left curly corresponding to the block to be checked.
295     *
296     * @param ast a {@code DetailAST} value
297     * @return the left curly corresponding to the block to be checked
298     */
299    private static DetailAST findLeftCurly(DetailAST ast) {
300        final DetailAST leftCurly;
301        final DetailAST slistAST = ast.findFirstToken(TokenTypes.SLIST);
302        if ((ast.getType() == TokenTypes.LITERAL_CASE
303                || ast.getType() == TokenTypes.LITERAL_DEFAULT)
304                && ast.getNextSibling() != null
305                && ast.getNextSibling().getFirstChild() != null
306                && ast.getNextSibling().getFirstChild().getType() == TokenTypes.SLIST) {
307            leftCurly = ast.getNextSibling().getFirstChild();
308        }
309        else if (slistAST == null) {
310            leftCurly = ast.findFirstToken(TokenTypes.LCURLY);
311        }
312        else {
313            leftCurly = slistAST;
314        }
315        return leftCurly;
316    }
317
318}