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.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     * 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        try {
092            option = BlockOption.valueOf(optionStr.trim().toUpperCase(Locale.ENGLISH));
093        }
094        catch (IllegalArgumentException iae) {
095            throw new IllegalArgumentException("unable to parse " + optionStr, iae);
096        }
097    }
098
099    @Override
100    public int[] getDefaultTokens() {
101        return new int[] {
102            TokenTypes.LITERAL_WHILE,
103            TokenTypes.LITERAL_TRY,
104            TokenTypes.LITERAL_FINALLY,
105            TokenTypes.LITERAL_DO,
106            TokenTypes.LITERAL_IF,
107            TokenTypes.LITERAL_ELSE,
108            TokenTypes.LITERAL_FOR,
109            TokenTypes.INSTANCE_INIT,
110            TokenTypes.STATIC_INIT,
111            TokenTypes.LITERAL_SWITCH,
112            TokenTypes.LITERAL_SYNCHRONIZED,
113        };
114    }
115
116    @Override
117    public int[] getAcceptableTokens() {
118        return new int[] {
119            TokenTypes.LITERAL_WHILE,
120            TokenTypes.LITERAL_TRY,
121            TokenTypes.LITERAL_CATCH,
122            TokenTypes.LITERAL_FINALLY,
123            TokenTypes.LITERAL_DO,
124            TokenTypes.LITERAL_IF,
125            TokenTypes.LITERAL_ELSE,
126            TokenTypes.LITERAL_FOR,
127            TokenTypes.INSTANCE_INIT,
128            TokenTypes.STATIC_INIT,
129            TokenTypes.LITERAL_SWITCH,
130            TokenTypes.LITERAL_SYNCHRONIZED,
131            TokenTypes.LITERAL_CASE,
132            TokenTypes.LITERAL_DEFAULT,
133            TokenTypes.ARRAY_INIT,
134        };
135    }
136
137    @Override
138    public int[] getRequiredTokens() {
139        return CommonUtils.EMPTY_INT_ARRAY;
140    }
141
142    @Override
143    public void visitToken(DetailAST ast) {
144        final DetailAST leftCurly = findLeftCurly(ast);
145        if (leftCurly != null) {
146            if (option == BlockOption.STATEMENT) {
147                final boolean emptyBlock;
148                if (leftCurly.getType() == TokenTypes.LCURLY) {
149                    emptyBlock = leftCurly.getNextSibling().getType() != TokenTypes.CASE_GROUP;
150                }
151                else {
152                    emptyBlock = leftCurly.getChildCount() <= 1;
153                }
154                if (emptyBlock) {
155                    log(leftCurly.getLineNo(),
156                        leftCurly.getColumnNo(),
157                        MSG_KEY_BLOCK_NO_STATEMENT,
158                        ast.getText());
159                }
160            }
161            else if (!hasText(leftCurly)) {
162                log(leftCurly.getLineNo(),
163                    leftCurly.getColumnNo(),
164                    MSG_KEY_BLOCK_EMPTY,
165                    ast.getText());
166            }
167        }
168    }
169
170    /**
171     * Checks if SLIST token contains any text.
172     * @param slistAST a {@code DetailAST} value
173     * @return whether the SLIST token contains any text.
174     */
175    private boolean hasText(final DetailAST slistAST) {
176        final DetailAST rightCurly = slistAST.findFirstToken(TokenTypes.RCURLY);
177        final DetailAST rcurlyAST;
178
179        if (rightCurly == null) {
180            rcurlyAST = slistAST.getParent().findFirstToken(TokenTypes.RCURLY);
181        }
182        else {
183            rcurlyAST = rightCurly;
184        }
185        final int slistLineNo = slistAST.getLineNo();
186        final int slistColNo = slistAST.getColumnNo();
187        final int rcurlyLineNo = rcurlyAST.getLineNo();
188        final int rcurlyColNo = rcurlyAST.getColumnNo();
189        final String[] lines = getLines();
190        boolean returnValue = false;
191        if (slistLineNo == rcurlyLineNo) {
192            // Handle braces on the same line
193            final String txt = lines[slistLineNo - 1]
194                    .substring(slistColNo + 1, rcurlyColNo);
195            if (!CommonUtils.isBlank(txt)) {
196                returnValue = true;
197            }
198        }
199        else {
200            final String firstLine = lines[slistLineNo - 1].substring(slistColNo + 1);
201            final String lastLine = lines[rcurlyLineNo - 1].substring(0, rcurlyColNo);
202            // check if all lines are also only whitespace
203            returnValue = !(CommonUtils.isBlank(firstLine) && CommonUtils.isBlank(lastLine))
204                    || !checkIsAllLinesAreWhitespace(lines, slistLineNo, rcurlyLineNo);
205        }
206        return returnValue;
207    }
208
209    /**
210     * Checks is all lines in array contain whitespaces only.
211     *
212     * @param lines
213     *            array of lines
214     * @param lineFrom
215     *            check from this line number
216     * @param lineTo
217     *            check to this line numbers
218     * @return true if lines contain only whitespaces
219     */
220    private static boolean checkIsAllLinesAreWhitespace(String[] lines, int lineFrom, int lineTo) {
221        boolean result = true;
222        for (int i = lineFrom; i < lineTo - 1; i++) {
223            if (!CommonUtils.isBlank(lines[i])) {
224                result = false;
225                break;
226            }
227        }
228        return result;
229    }
230
231    /**
232     * Calculates the left curly corresponding to the block to be checked.
233     *
234     * @param ast a {@code DetailAST} value
235     * @return the left curly corresponding to the block to be checked
236     */
237    private static DetailAST findLeftCurly(DetailAST ast) {
238        final DetailAST leftCurly;
239        final DetailAST slistAST = ast.findFirstToken(TokenTypes.SLIST);
240        if ((ast.getType() == TokenTypes.LITERAL_CASE
241                || ast.getType() == TokenTypes.LITERAL_DEFAULT)
242                && ast.getNextSibling() != null
243                && ast.getNextSibling().getFirstChild() != 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}