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