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;
023
024import com.puppycrawl.tools.checkstyle.api.Check;
025import com.puppycrawl.tools.checkstyle.api.DetailAST;
026import com.puppycrawl.tools.checkstyle.api.TokenTypes;
027
028/**
029 * <p>
030 * Checks for braces around code blocks.
031 * </p>
032 * <p> By default the check will check the following blocks:
033 *  {@link TokenTypes#LITERAL_DO LITERAL_DO},
034 *  {@link TokenTypes#LITERAL_ELSE LITERAL_ELSE},
035 *  {@link TokenTypes#LITERAL_FOR LITERAL_FOR},
036 *  {@link TokenTypes#LITERAL_IF LITERAL_IF},
037 *  {@link TokenTypes#LITERAL_WHILE LITERAL_WHILE}.
038 * </p>
039 * <p>
040 * An example of how to configure the check is:
041 * </p>
042 * <pre>
043 * &lt;module name="NeedBraces"/&gt;
044 * </pre>
045 * <p> An example of how to configure the check for {@code if} and
046 * {@code else} blocks is:
047 * </p>
048 * <pre>
049 * &lt;module name="NeedBraces"&gt;
050 *     &lt;property name="tokens" value="LITERAL_IF, LITERAL_ELSE"/&gt;
051 * &lt;/module&gt;
052 * </pre>
053 * Check has an option <b>allowSingleLineStatement</b> which allows single-line
054 * statements without braces, e.g.:
055 * <p>
056 * {@code
057 * if (obj.isValid()) return true;
058 * }
059 * </p>
060 * <p>
061 * {@code
062 * while (obj.isValid()) return true;
063 * }
064 * </p>
065 * <p>
066 * {@code
067 * do this.notify(); while (o != null);
068 * }
069 * </p>
070 * <p>
071 * {@code
072 * for (int i = 0; ; ) this.notify();
073 * }
074 * </p>
075 * <p>
076 * To configure the Check to allow {@code case, default} single-line statements
077 * without braces:
078 * </p>
079 *
080 * <pre>
081 * &lt;module name=&quot;NeedBraces&quot;&gt;
082 *     &lt;property name=&quot;tokens&quot; value=&quot;LITERAL_CASE, LITERAL_DEFAULT&quot;/&gt;
083 *     &lt;property name=&quot;allowSingleLineStatement&quot; value=&quot;true&quot;/&gt;
084 * &lt;/module&gt;
085 * </pre>
086 *
087 * <p>
088 * Such statements would be allowed:
089 * </p>
090 *
091 * <pre>
092 * {@code
093 * switch (num) {
094 *     case 1: counter++; break; // OK
095 *     case 6: counter += 10; break; // OK
096 *     default: counter = 100; break; // OK
097 * }
098 * }
099 * </pre>
100 *
101 *
102 * @author Rick Giles
103 * @author <a href="mailto:nesterenko-aleksey@list.ru">Aleksey Nesterenko</a>
104 */
105public class NeedBracesCheck extends Check {
106    /**
107     * A key is pointing to the warning message text in "messages.properties"
108     * file.
109     */
110    public static final String MSG_KEY_NEED_BRACES = "needBraces";
111
112    /**
113     * Check's option for skipping single-line statements.
114     */
115    private boolean allowSingleLineStatement;
116
117    /**
118     * Setter.
119     * @param allowSingleLineStatement Check's option for skipping single-line statements
120     */
121    public void setAllowSingleLineStatement(boolean allowSingleLineStatement) {
122        this.allowSingleLineStatement = allowSingleLineStatement;
123    }
124
125    @Override
126    public int[] getDefaultTokens() {
127        return new int[] {
128            TokenTypes.LITERAL_DO,
129            TokenTypes.LITERAL_ELSE,
130            TokenTypes.LITERAL_FOR,
131            TokenTypes.LITERAL_IF,
132            TokenTypes.LITERAL_WHILE,
133        };
134    }
135
136    @Override
137    public int[] getAcceptableTokens() {
138        return new int[] {
139            TokenTypes.LITERAL_DO,
140            TokenTypes.LITERAL_ELSE,
141            TokenTypes.LITERAL_FOR,
142            TokenTypes.LITERAL_IF,
143            TokenTypes.LITERAL_WHILE,
144            TokenTypes.LITERAL_CASE,
145            TokenTypes.LITERAL_DEFAULT,
146            TokenTypes.LAMBDA,
147        };
148    }
149
150    @Override
151    public int[] getRequiredTokens() {
152        return ArrayUtils.EMPTY_INT_ARRAY;
153    }
154
155    @Override
156    public void visitToken(DetailAST ast) {
157        final DetailAST slistAST = ast.findFirstToken(TokenTypes.SLIST);
158        boolean isElseIf = false;
159        if (ast.getType() == TokenTypes.LITERAL_ELSE
160            && ast.findFirstToken(TokenTypes.LITERAL_IF) != null) {
161            isElseIf = true;
162        }
163
164        final boolean skipStatement = isSkipStatement(ast);
165
166        if (slistAST == null && !isElseIf && !skipStatement) {
167            log(ast.getLineNo(), MSG_KEY_NEED_BRACES, ast.getText());
168        }
169    }
170
171    /**
172     * Checks if current statement can be skipped by "need braces" warning.
173     * @param statement if, for, while, do-while, lambda, else, case, default statements.
174     * @return true if current statement can be skipped by Check.
175     */
176    private boolean isSkipStatement(DetailAST statement) {
177        return allowSingleLineStatement && isSingleLineStatement(statement);
178    }
179
180    /**
181     * Checks if current statement is single-line statement, e.g.:
182     * <p>
183     * {@code
184     * if (obj.isValid()) return true;
185     * }
186     * </p>
187     * <p>
188     * {@code
189     * while (obj.isValid()) return true;
190     * }
191     * </p>
192     * @param statement if, for, while, do-while, lambda, else, case, default statements.
193     * @return true if current statement is single-line statement.
194     */
195    private static boolean isSingleLineStatement(DetailAST statement) {
196        boolean result;
197
198        switch (statement.getType()) {
199            case TokenTypes.LITERAL_IF:
200                result = isSingleLineIf(statement);
201                break;
202            case TokenTypes.LITERAL_FOR:
203                result = isSingleLineFor(statement);
204                break;
205            case TokenTypes.LITERAL_DO:
206                result = isSingleLineDoWhile(statement);
207                break;
208            case TokenTypes.LITERAL_WHILE:
209                result = isSingleLineWhile(statement);
210                break;
211            case TokenTypes.LAMBDA:
212                result = isSingleLineLambda(statement);
213                break;
214            case TokenTypes.LITERAL_CASE:
215                result = isSingleLineCase(statement);
216                break;
217            case TokenTypes.LITERAL_DEFAULT:
218                result = isSingleLineDefault(statement);
219                break;
220            default:
221                result = isSingleLineElse(statement);
222                break;
223        }
224
225        return result;
226    }
227
228    /**
229     * Checks if current while statement is single-line statement, e.g.:
230     * <p>
231     * {@code
232     * while (obj.isValid()) return true;
233     * }
234     * </p>
235     * @param literalWhile {@link TokenTypes#LITERAL_WHILE while statement}.
236     * @return true if current while statement is single-line statement.
237     */
238    private static boolean isSingleLineWhile(DetailAST literalWhile) {
239        boolean result = false;
240        if (literalWhile.getParent().getType() == TokenTypes.SLIST
241                && literalWhile.getLastChild().getType() != TokenTypes.SLIST) {
242            final DetailAST block = literalWhile.getLastChild().getPreviousSibling();
243            result = literalWhile.getLineNo() == block.getLineNo();
244        }
245        return result;
246    }
247
248    /**
249     * Checks if current do-while statement is single-line statement, e.g.:
250     * <p>
251     * {@code
252     * do this.notify(); while (o != null);
253     * }
254     * </p>
255     * @param literalDo {@link TokenTypes#LITERAL_DO do-while statement}.
256     * @return true if current do-while statement is single-line statement.
257     */
258    private static boolean isSingleLineDoWhile(DetailAST literalDo) {
259        boolean result = false;
260        if (literalDo.getParent().getType() == TokenTypes.SLIST
261                && literalDo.getFirstChild().getType() != TokenTypes.SLIST) {
262            final DetailAST block = literalDo.getFirstChild();
263            result = block.getLineNo() == literalDo.getLineNo();
264        }
265        return result;
266    }
267
268    /**
269     * Checks if current for statement is single-line statement, e.g.:
270     * <p>
271     * {@code
272     * for (int i = 0; ; ) this.notify();
273     * }
274     * </p>
275     * @param literalFor {@link TokenTypes#LITERAL_FOR for statement}.
276     * @return true if current for statement is single-line statement.
277     */
278    private static boolean isSingleLineFor(DetailAST literalFor) {
279        boolean result = false;
280        if (literalFor.getLastChild().getType() == TokenTypes.EMPTY_STAT) {
281            result = true;
282        }
283        else if (literalFor.getParent().getType() == TokenTypes.SLIST
284                && literalFor.getLastChild().getType() != TokenTypes.SLIST) {
285            final DetailAST block = findExpressionBlockInForLoop(literalFor);
286            result = literalFor.getLineNo() == block.getLineNo();
287        }
288        return result;
289    }
290
291    /**
292     * Detects and returns expression block in classical and enhanced for loops.
293     *
294     * @param literalFor parent for loop literal
295     * @return expression block
296     */
297    private static DetailAST findExpressionBlockInForLoop(DetailAST literalFor) {
298        final DetailAST forEachClause = literalFor.findFirstToken(TokenTypes.FOR_EACH_CLAUSE);
299        final DetailAST firstToken;
300        if (forEachClause != null) {
301            firstToken = forEachClause.findFirstToken(TokenTypes.EXPR);
302        }
303        else {
304            firstToken = literalFor.findFirstToken(TokenTypes.EXPR);
305        }
306        return firstToken;
307    }
308
309    /**
310     * Checks if current if statement is single-line statement, e.g.:
311     * <p>
312     * {@code
313     * if (obj.isValid()) return true;
314     * }
315     * </p>
316     * @param literalIf {@link TokenTypes#LITERAL_IF if statement}.
317     * @return true if current if statement is single-line statement.
318     */
319    private static boolean isSingleLineIf(DetailAST literalIf) {
320        boolean result = false;
321        final DetailAST ifCondition = literalIf.findFirstToken(TokenTypes.EXPR);
322        if (literalIf.getParent().getType() == TokenTypes.SLIST) {
323            final DetailAST literalIfLastChild = literalIf.getLastChild();
324            final DetailAST block;
325            if (literalIfLastChild.getType() == TokenTypes.LITERAL_ELSE) {
326                block = literalIfLastChild.getPreviousSibling();
327            }
328            else {
329                block = literalIfLastChild;
330            }
331            result = ifCondition.getLineNo() == block.getLineNo();
332        }
333        return result;
334    }
335
336    /**
337     * Checks if current lambda statement is single-line statement, e.g.:
338     * <p>
339     * {@code
340     * Runnable r = () -> System.out.println("Hello, world!");
341     * }
342     * </p>
343     * @param lambda {@link TokenTypes#LAMBDA lambda statement}.
344     * @return true if current lambda statement is single-line statement.
345     */
346    private static boolean isSingleLineLambda(DetailAST lambda) {
347        boolean result = false;
348        final DetailAST block = lambda.getLastChild();
349        if (block.getType() != TokenTypes.SLIST) {
350            result = lambda.getLineNo() == block.getLineNo();
351        }
352        return result;
353    }
354
355    /**
356     * Checks if current case statement is single-line statement, e.g.:
357     * <p>
358     * {@code
359     * case 1: doSomeStuff(); break;
360     * case 2: doSomeStuff(); break;
361     * }
362     * </p>
363     * @param literalCase {@link TokenTypes#LITERAL_CASE case statement}.
364     * @return true if current case statement is single-line statement.
365     */
366    private static boolean isSingleLineCase(DetailAST literalCase) {
367        boolean result = false;
368        final DetailAST slist = literalCase.getNextSibling();
369        final DetailAST block = slist.getFirstChild();
370        if (block.getType() != TokenTypes.SLIST) {
371            final DetailAST caseBreak = slist.findFirstToken(TokenTypes.LITERAL_BREAK);
372            final boolean atOneLine = literalCase.getLineNo() == block.getLineNo();
373            if (caseBreak != null) {
374                result = atOneLine && block.getLineNo() == caseBreak.getLineNo();
375            }
376        }
377        return result;
378    }
379
380    /**
381     * Checks if current default statement is single-line statement, e.g.:
382     * <p>
383     * {@code
384     * default: doSomeStuff();
385     * }
386     * </p>
387     * @param literalDefault {@link TokenTypes#LITERAL_DEFAULT default statement}.
388     * @return true if current default statement is single-line statement.
389     */
390    private static boolean isSingleLineDefault(DetailAST literalDefault) {
391        boolean result = false;
392        final DetailAST slist = literalDefault.getNextSibling();
393        final DetailAST block = slist.getFirstChild();
394        if (block.getType() != TokenTypes.SLIST) {
395            result = literalDefault.getLineNo() == block.getLineNo();
396        }
397        return result;
398    }
399
400    /**
401     * Checks if current else statement is single-line statement, e.g.:
402     * <p>
403     * {@code
404     * else doSomeStuff();
405     * }
406     * </p>
407     * @param literalElse {@link TokenTypes#LITERAL_ELSE else statement}.
408     * @return true if current else statement is single-line statement.
409     */
410    private static boolean isSingleLineElse(DetailAST literalElse) {
411        boolean result = false;
412        final DetailAST block = literalElse.getFirstChild();
413        if (block.getType() != TokenTypes.SLIST) {
414            result = literalElse.getLineNo() == block.getLineNo();
415        }
416        return result;
417    }
418}