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