001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2019 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 * Checks for empty blocks. This check does not validate sequential blocks.
032 * The policy to verify is specified using the {@link
033 * BlockOption} class and defaults to {@link BlockOption#STATEMENT}.
034 *
035 * <p> By default the check will check the following blocks:
036 *  {@link TokenTypes#LITERAL_WHILE LITERAL_WHILE},
037 *  {@link TokenTypes#LITERAL_TRY LITERAL_TRY},
038 *  {@link TokenTypes#LITERAL_FINALLY LITERAL_FINALLY},
039 *  {@link TokenTypes#LITERAL_DO LITERAL_DO},
040 *  {@link TokenTypes#LITERAL_IF LITERAL_IF},
041 *  {@link TokenTypes#LITERAL_ELSE LITERAL_ELSE},
042 *  {@link TokenTypes#LITERAL_FOR LITERAL_FOR},
043 *  {@link TokenTypes#STATIC_INIT STATIC_INIT},
044 *  {@link TokenTypes#LITERAL_SWITCH LITERAL_SWITCH}.
045 *  {@link TokenTypes#LITERAL_SYNCHRONIZED LITERAL_SYNCHRONIZED}.
046 * </p>
047 *
048 * <p> An example of how to configure the check is:
049 * </p>
050 * <pre>
051 * &lt;module name="EmptyBlock"/&gt;
052 * </pre>
053 *
054 * <p> An example of how to configure the check for the {@link
055 * BlockOption#TEXT} policy and only try blocks is:
056 * </p>
057 *
058 * <pre>
059 * &lt;module name="EmptyBlock"&gt;
060 *    &lt;property name="tokens" value="LITERAL_TRY"/&gt;
061 *    &lt;property name="option" value="text"/&gt;
062 * &lt;/module&gt;
063 * </pre>
064 *
065 */
066@StatelessCheck
067public class EmptyBlockCheck
068    extends AbstractCheck {
069
070    /**
071     * A key is pointing to the warning message text in "messages.properties"
072     * file.
073     */
074    public static final String MSG_KEY_BLOCK_NO_STATEMENT = "block.noStatement";
075
076    /**
077     * A key is pointing to the warning message text in "messages.properties"
078     * file.
079     */
080    public static final String MSG_KEY_BLOCK_EMPTY = "block.empty";
081
082    /** The policy to enforce. */
083    private BlockOption option = BlockOption.STATEMENT;
084
085    /**
086     * Set the option to enforce.
087     * @param optionStr string to decode option from
088     * @throws IllegalArgumentException if unable to decode
089     */
090    public void setOption(String optionStr) {
091        option = BlockOption.valueOf(optionStr.trim().toUpperCase(Locale.ENGLISH));
092    }
093
094    @Override
095    public int[] getDefaultTokens() {
096        return new int[] {
097            TokenTypes.LITERAL_WHILE,
098            TokenTypes.LITERAL_TRY,
099            TokenTypes.LITERAL_FINALLY,
100            TokenTypes.LITERAL_DO,
101            TokenTypes.LITERAL_IF,
102            TokenTypes.LITERAL_ELSE,
103            TokenTypes.LITERAL_FOR,
104            TokenTypes.INSTANCE_INIT,
105            TokenTypes.STATIC_INIT,
106            TokenTypes.LITERAL_SWITCH,
107            TokenTypes.LITERAL_SYNCHRONIZED,
108        };
109    }
110
111    @Override
112    public int[] getAcceptableTokens() {
113        return new int[] {
114            TokenTypes.LITERAL_WHILE,
115            TokenTypes.LITERAL_TRY,
116            TokenTypes.LITERAL_CATCH,
117            TokenTypes.LITERAL_FINALLY,
118            TokenTypes.LITERAL_DO,
119            TokenTypes.LITERAL_IF,
120            TokenTypes.LITERAL_ELSE,
121            TokenTypes.LITERAL_FOR,
122            TokenTypes.INSTANCE_INIT,
123            TokenTypes.STATIC_INIT,
124            TokenTypes.LITERAL_SWITCH,
125            TokenTypes.LITERAL_SYNCHRONIZED,
126            TokenTypes.LITERAL_CASE,
127            TokenTypes.LITERAL_DEFAULT,
128            TokenTypes.ARRAY_INIT,
129        };
130    }
131
132    @Override
133    public int[] getRequiredTokens() {
134        return CommonUtil.EMPTY_INT_ARRAY;
135    }
136
137    @Override
138    public void visitToken(DetailAST ast) {
139        final DetailAST leftCurly = findLeftCurly(ast);
140        if (leftCurly != null) {
141            if (option == BlockOption.STATEMENT) {
142                final boolean emptyBlock;
143                if (leftCurly.getType() == TokenTypes.LCURLY) {
144                    emptyBlock = leftCurly.getNextSibling().getType() != TokenTypes.CASE_GROUP;
145                }
146                else {
147                    emptyBlock = leftCurly.getChildCount() <= 1;
148                }
149                if (emptyBlock) {
150                    log(leftCurly,
151                        MSG_KEY_BLOCK_NO_STATEMENT,
152                        ast.getText());
153                }
154            }
155            else if (!hasText(leftCurly)) {
156                log(leftCurly,
157                    MSG_KEY_BLOCK_EMPTY,
158                    ast.getText());
159            }
160        }
161    }
162
163    /**
164     * Checks if SLIST token contains any text.
165     * @param slistAST a {@code DetailAST} value
166     * @return whether the SLIST token contains any text.
167     */
168    private boolean hasText(final DetailAST slistAST) {
169        final DetailAST rightCurly = slistAST.findFirstToken(TokenTypes.RCURLY);
170        final DetailAST rcurlyAST;
171
172        if (rightCurly == null) {
173            rcurlyAST = slistAST.getParent().findFirstToken(TokenTypes.RCURLY);
174        }
175        else {
176            rcurlyAST = rightCurly;
177        }
178        final int slistLineNo = slistAST.getLineNo();
179        final int slistColNo = slistAST.getColumnNo();
180        final int rcurlyLineNo = rcurlyAST.getLineNo();
181        final int rcurlyColNo = rcurlyAST.getColumnNo();
182        final String[] lines = getLines();
183        boolean returnValue = false;
184        if (slistLineNo == rcurlyLineNo) {
185            // Handle braces on the same line
186            final String txt = lines[slistLineNo - 1]
187                    .substring(slistColNo + 1, rcurlyColNo);
188            if (!CommonUtil.isBlank(txt)) {
189                returnValue = true;
190            }
191        }
192        else {
193            final String firstLine = lines[slistLineNo - 1].substring(slistColNo + 1);
194            final String lastLine = lines[rcurlyLineNo - 1].substring(0, rcurlyColNo);
195            // check if all lines are also only whitespace
196            returnValue = !(CommonUtil.isBlank(firstLine) && CommonUtil.isBlank(lastLine))
197                    || !checkIsAllLinesAreWhitespace(lines, slistLineNo, rcurlyLineNo);
198        }
199        return returnValue;
200    }
201
202    /**
203     * Checks is all lines in array contain whitespaces only.
204     *
205     * @param lines
206     *            array of lines
207     * @param lineFrom
208     *            check from this line number
209     * @param lineTo
210     *            check to this line numbers
211     * @return true if lines contain only whitespaces
212     */
213    private static boolean checkIsAllLinesAreWhitespace(String[] lines, int lineFrom, int lineTo) {
214        boolean result = true;
215        for (int i = lineFrom; i < lineTo - 1; i++) {
216            if (!CommonUtil.isBlank(lines[i])) {
217                result = false;
218                break;
219            }
220        }
221        return result;
222    }
223
224    /**
225     * Calculates the left curly corresponding to the block to be checked.
226     *
227     * @param ast a {@code DetailAST} value
228     * @return the left curly corresponding to the block to be checked
229     */
230    private static DetailAST findLeftCurly(DetailAST ast) {
231        final DetailAST leftCurly;
232        final DetailAST slistAST = ast.findFirstToken(TokenTypes.SLIST);
233        if ((ast.getType() == TokenTypes.LITERAL_CASE
234                || ast.getType() == TokenTypes.LITERAL_DEFAULT)
235                && ast.getNextSibling() != null
236                && ast.getNextSibling().getFirstChild() != null
237                && ast.getNextSibling().getFirstChild().getType() == TokenTypes.SLIST) {
238            leftCurly = ast.getNextSibling().getFirstChild();
239        }
240        else if (slistAST == null) {
241            leftCurly = ast.findFirstToken(TokenTypes.LCURLY);
242        }
243        else {
244            leftCurly = slistAST;
245        }
246        return leftCurly;
247    }
248
249}