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