001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2015 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 org.apache.commons.lang3.ArrayUtils;
023import org.apache.commons.lang3.StringUtils;
024
025import com.puppycrawl.tools.checkstyle.api.DetailAST;
026import com.puppycrawl.tools.checkstyle.api.TokenTypes;
027import com.puppycrawl.tools.checkstyle.checks.AbstractOptionCheck;
028
029/**
030 * Checks for empty blocks. The policy to verify is specified using the {@link
031 * BlockOption} class and defaults to {@link BlockOption#STMT}.
032 *
033 * <p> By default the check will check the following blocks:
034 *  {@link TokenTypes#LITERAL_WHILE LITERAL_WHILE},
035 *  {@link TokenTypes#LITERAL_TRY LITERAL_TRY},
036 *  {@link TokenTypes#LITERAL_FINALLY LITERAL_FINALLY},
037 *  {@link TokenTypes#LITERAL_DO LITERAL_DO},
038 *  {@link TokenTypes#LITERAL_IF LITERAL_IF},
039 *  {@link TokenTypes#LITERAL_ELSE LITERAL_ELSE},
040 *  {@link TokenTypes#LITERAL_FOR LITERAL_FOR},
041 *  {@link TokenTypes#STATIC_INIT STATIC_INIT},
042 *  {@link TokenTypes#LITERAL_SWITCH LITERAL_SWITCH}.
043 *  {@link TokenTypes#LITERAL_SYNCHRONIZED LITERAL_SYNCHRONIZED}.
044 * </p>
045 *
046 * <p> An example of how to configure the check is:
047 * </p>
048 * <pre>
049 * &lt;module name="EmptyBlock"/&gt;
050 * </pre>
051 *
052 * <p> An example of how to configure the check for the {@link
053 * BlockOption#TEXT} policy and only try blocks is:
054 * </p>
055 *
056 * <pre>
057 * &lt;module name="EmptyBlock"&gt;
058 *    &lt;property name="tokens" value="LITERAL_TRY"/&gt;
059 *    &lt;property name="option" value="text"/&gt;
060 * &lt;/module&gt;
061 * </pre>
062 *
063 * @author Lars Kühne
064 */
065public class EmptyBlockCheck
066    extends AbstractOptionCheck<BlockOption> {
067    /**
068     * A key is pointing to the warning message text in "messages.properties"
069     * file.
070     */
071    public static final String MSG_KEY_BLOCK_NO_STMT = "block.noStmt";
072
073    /**
074     * A key is pointing to the warning message text in "messages.properties"
075     * file.
076     */
077    public static final String MSG_KEY_BLOCK_EMPTY = "block.empty";
078
079    /**
080     * Creates a new {@code EmptyBlockCheck} instance.
081     */
082    public EmptyBlockCheck() {
083        super(BlockOption.STMT, BlockOption.class);
084    }
085
086    @Override
087    public int[] getDefaultTokens() {
088        return new int[] {
089            TokenTypes.LITERAL_WHILE,
090            TokenTypes.LITERAL_TRY,
091            TokenTypes.LITERAL_FINALLY,
092            TokenTypes.LITERAL_DO,
093            TokenTypes.LITERAL_IF,
094            TokenTypes.LITERAL_ELSE,
095            TokenTypes.LITERAL_FOR,
096            TokenTypes.INSTANCE_INIT,
097            TokenTypes.STATIC_INIT,
098            TokenTypes.LITERAL_SWITCH,
099            TokenTypes.LITERAL_SYNCHRONIZED,
100        };
101    }
102
103    @Override
104    public int[] getAcceptableTokens() {
105        return new int[] {
106            TokenTypes.LITERAL_WHILE,
107            TokenTypes.LITERAL_TRY,
108            TokenTypes.LITERAL_CATCH,
109            TokenTypes.LITERAL_FINALLY,
110            TokenTypes.LITERAL_DO,
111            TokenTypes.LITERAL_IF,
112            TokenTypes.LITERAL_ELSE,
113            TokenTypes.LITERAL_FOR,
114            TokenTypes.INSTANCE_INIT,
115            TokenTypes.STATIC_INIT,
116            TokenTypes.LITERAL_SWITCH,
117            TokenTypes.LITERAL_SYNCHRONIZED,
118            TokenTypes.LITERAL_CASE,
119            TokenTypes.LITERAL_DEFAULT,
120            TokenTypes.ARRAY_INIT,
121        };
122    }
123
124    @Override
125    public int[] getRequiredTokens() {
126        return ArrayUtils.EMPTY_INT_ARRAY;
127    }
128
129    @Override
130    public void visitToken(DetailAST ast) {
131        final DetailAST slistToken = ast.findFirstToken(TokenTypes.SLIST);
132        final DetailAST leftCurly;
133
134        if (slistToken == null) {
135            leftCurly = ast.findFirstToken(TokenTypes.LCURLY);
136        }
137        else {
138            leftCurly = slistToken;
139        }
140
141        if (leftCurly != null) {
142            if (getAbstractOption() == BlockOption.STMT) {
143                boolean emptyBlock;
144                if (leftCurly.getType() == TokenTypes.LCURLY) {
145                    emptyBlock = leftCurly.getNextSibling().getType() != TokenTypes.CASE_GROUP;
146                }
147                else {
148                    emptyBlock = leftCurly.getChildCount() <= 1;
149                }
150                if (emptyBlock) {
151                    log(leftCurly.getLineNo(),
152                        leftCurly.getColumnNo(),
153                        MSG_KEY_BLOCK_NO_STMT,
154                        ast.getText());
155                }
156            }
157            else if (!hasText(leftCurly)) {
158                log(leftCurly.getLineNo(),
159                    leftCurly.getColumnNo(),
160                    MSG_KEY_BLOCK_EMPTY,
161                    ast.getText());
162            }
163        }
164    }
165
166    /**
167     * @param slistAST a {@code DetailAST} value
168     * @return whether the SLIST token contains any text.
169     */
170    protected boolean hasText(final DetailAST slistAST) {
171        final DetailAST rightCurly = slistAST.findFirstToken(TokenTypes.RCURLY);
172        final DetailAST rcurlyAST;
173
174        if (rightCurly == null) {
175            rcurlyAST = slistAST.getParent().findFirstToken(TokenTypes.RCURLY);
176        }
177        else {
178            rcurlyAST = rightCurly;
179        }
180        final int slistLineNo = slistAST.getLineNo();
181        final int slistColNo = slistAST.getColumnNo();
182        final int rcurlyLineNo = rcurlyAST.getLineNo();
183        final int rcurlyColNo = rcurlyAST.getColumnNo();
184        final String[] lines = getLines();
185        boolean retVal = false;
186        if (slistLineNo == rcurlyLineNo) {
187            // Handle braces on the same line
188            final String txt = lines[slistLineNo - 1]
189                    .substring(slistColNo + 1, rcurlyColNo);
190            if (StringUtils.isNotBlank(txt)) {
191                retVal = true;
192            }
193        }
194        else {
195            // check only whitespace of first & last lines
196            if (!lines[slistLineNo - 1]
197                .substring(slistColNo + 1).trim().isEmpty()
198                    || !lines[rcurlyLineNo - 1]
199                .substring(0, rcurlyColNo).trim().isEmpty()) {
200                retVal = true;
201            }
202            else {
203                // check if all lines are also only whitespace
204                retVal = !checkIsAllLinesAreWhitespace(lines, slistLineNo, rcurlyLineNo);
205            }
206        }
207        return retVal;
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}