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 java.util.Locale;
023
024import org.apache.commons.beanutils.ConversionException;
025import org.apache.commons.lang3.ArrayUtils;
026import org.apache.commons.lang3.StringUtils;
027
028import com.puppycrawl.tools.checkstyle.api.Check;
029import com.puppycrawl.tools.checkstyle.api.DetailAST;
030import com.puppycrawl.tools.checkstyle.api.TokenTypes;
031
032/**
033 * Checks for empty blocks. The policy to verify is specified using the {@link
034 * BlockOption} class and defaults to {@link BlockOption#STMT}.
035 *
036 * <p> By default the check will check the following blocks:
037 *  {@link TokenTypes#LITERAL_WHILE LITERAL_WHILE},
038 *  {@link TokenTypes#LITERAL_TRY LITERAL_TRY},
039 *  {@link TokenTypes#LITERAL_FINALLY LITERAL_FINALLY},
040 *  {@link TokenTypes#LITERAL_DO LITERAL_DO},
041 *  {@link TokenTypes#LITERAL_IF LITERAL_IF},
042 *  {@link TokenTypes#LITERAL_ELSE LITERAL_ELSE},
043 *  {@link TokenTypes#LITERAL_FOR LITERAL_FOR},
044 *  {@link TokenTypes#STATIC_INIT STATIC_INIT},
045 *  {@link TokenTypes#LITERAL_SWITCH LITERAL_SWITCH}.
046 *  {@link TokenTypes#LITERAL_SYNCHRONIZED LITERAL_SYNCHRONIZED}.
047 * </p>
048 *
049 * <p> An example of how to configure the check is:
050 * </p>
051 * <pre>
052 * &lt;module name="EmptyBlock"/&gt;
053 * </pre>
054 *
055 * <p> An example of how to configure the check for the {@link
056 * BlockOption#TEXT} policy and only try blocks is:
057 * </p>
058 *
059 * <pre>
060 * &lt;module name="EmptyBlock"&gt;
061 *    &lt;property name="tokens" value="LITERAL_TRY"/&gt;
062 *    &lt;property name="option" value="text"/&gt;
063 * &lt;/module&gt;
064 * </pre>
065 *
066 * @author Lars Kühne
067 */
068public class EmptyBlockCheck
069    extends Check {
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_STMT = "block.noStmt";
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.STMT;
084
085    /**
086     * Set the option to enforce.
087     * @param optionStr string to decode option from
088     * @throws ConversionException 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 ConversionException("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 ArrayUtils.EMPTY_INT_ARRAY;
140    }
141
142    @Override
143    public void visitToken(DetailAST ast) {
144        final DetailAST slistToken = ast.findFirstToken(TokenTypes.SLIST);
145        final DetailAST leftCurly;
146
147        if (slistToken == null) {
148            leftCurly = ast.findFirstToken(TokenTypes.LCURLY);
149        }
150        else {
151            leftCurly = slistToken;
152        }
153
154        if (leftCurly != null) {
155            if (option == BlockOption.STMT) {
156                boolean emptyBlock;
157                if (leftCurly.getType() == TokenTypes.LCURLY) {
158                    emptyBlock = leftCurly.getNextSibling().getType() != TokenTypes.CASE_GROUP;
159                }
160                else {
161                    emptyBlock = leftCurly.getChildCount() <= 1;
162                }
163                if (emptyBlock) {
164                    log(leftCurly.getLineNo(),
165                        leftCurly.getColumnNo(),
166                        MSG_KEY_BLOCK_NO_STMT,
167                        ast.getText());
168                }
169            }
170            else if (!hasText(leftCurly)) {
171                log(leftCurly.getLineNo(),
172                    leftCurly.getColumnNo(),
173                    MSG_KEY_BLOCK_EMPTY,
174                    ast.getText());
175            }
176        }
177    }
178
179    /**
180     * @param slistAST a {@code DetailAST} value
181     * @return whether the SLIST token contains any text.
182     */
183    protected boolean hasText(final DetailAST slistAST) {
184        final DetailAST rightCurly = slistAST.findFirstToken(TokenTypes.RCURLY);
185        final DetailAST rcurlyAST;
186
187        if (rightCurly == null) {
188            rcurlyAST = slistAST.getParent().findFirstToken(TokenTypes.RCURLY);
189        }
190        else {
191            rcurlyAST = rightCurly;
192        }
193        final int slistLineNo = slistAST.getLineNo();
194        final int slistColNo = slistAST.getColumnNo();
195        final int rcurlyLineNo = rcurlyAST.getLineNo();
196        final int rcurlyColNo = rcurlyAST.getColumnNo();
197        final String[] lines = getLines();
198        boolean returnValue = false;
199        if (slistLineNo == rcurlyLineNo) {
200            // Handle braces on the same line
201            final String txt = lines[slistLineNo - 1]
202                    .substring(slistColNo + 1, rcurlyColNo);
203            if (StringUtils.isNotBlank(txt)) {
204                returnValue = true;
205            }
206        }
207        else {
208            // check only whitespace of first & last lines
209            if (lines[slistLineNo - 1].substring(slistColNo + 1).trim().isEmpty()
210                    && lines[rcurlyLineNo - 1].substring(0, rcurlyColNo).trim().isEmpty()) {
211                // check if all lines are also only whitespace
212                returnValue = !checkIsAllLinesAreWhitespace(lines, slistLineNo, rcurlyLineNo);
213            }
214            else {
215                returnValue = true;
216            }
217        }
218        return returnValue;
219    }
220
221    /**
222     * Checks is all lines in array contain whitespaces only.
223     *
224     * @param lines
225     *            array of lines
226     * @param lineFrom
227     *            check from this line number
228     * @param lineTo
229     *            check to this line numbers
230     * @return true if lines contain only whitespaces
231     */
232    private static boolean checkIsAllLinesAreWhitespace(String[] lines, int lineFrom, int lineTo) {
233        boolean result = true;
234        for (int i = lineFrom; i < lineTo - 1; i++) {
235            if (!lines[i].trim().isEmpty()) {
236                result = false;
237                break;
238            }
239        }
240        return result;
241    }
242}