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#STMT}.
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_STMT = "block.noStmt";
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.STMT;
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.STMT) {
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_STMT,
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     * @param slistAST a {@code DetailAST} value
170     * @return whether the SLIST token contains any text.
171     */
172    protected boolean hasText(final DetailAST slistAST) {
173        final DetailAST rightCurly = slistAST.findFirstToken(TokenTypes.RCURLY);
174        final DetailAST rcurlyAST;
175
176        if (rightCurly == null) {
177            rcurlyAST = slistAST.getParent().findFirstToken(TokenTypes.RCURLY);
178        }
179        else {
180            rcurlyAST = rightCurly;
181        }
182        final int slistLineNo = slistAST.getLineNo();
183        final int slistColNo = slistAST.getColumnNo();
184        final int rcurlyLineNo = rcurlyAST.getLineNo();
185        final int rcurlyColNo = rcurlyAST.getColumnNo();
186        final String[] lines = getLines();
187        boolean returnValue = false;
188        if (slistLineNo == rcurlyLineNo) {
189            // Handle braces on the same line
190            final String txt = lines[slistLineNo - 1]
191                    .substring(slistColNo + 1, rcurlyColNo);
192            if (!CommonUtils.isBlank(txt)) {
193                returnValue = true;
194            }
195        }
196        else {
197            // check only whitespace of first & last lines
198            if (lines[slistLineNo - 1].substring(slistColNo + 1).trim().isEmpty()
199                    && lines[rcurlyLineNo - 1].substring(0, rcurlyColNo).trim().isEmpty()) {
200                // check if all lines are also only whitespace
201                returnValue = !checkIsAllLinesAreWhitespace(lines, slistLineNo, rcurlyLineNo);
202            }
203            else {
204                returnValue = true;
205            }
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 (!lines[i].trim().isEmpty()) {
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().getType() == TokenTypes.SLIST) {
245            leftCurly = ast.getNextSibling().getFirstChild();
246        }
247        else if (slistAST == null) {
248            leftCurly = ast.findFirstToken(TokenTypes.LCURLY);
249        }
250        else {
251            leftCurly = slistAST;
252        }
253        return leftCurly;
254    }
255}