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 the following options:
054 * <p><b>allowSingleLineStatement</b> which allows single-line statements without braces, e.g.:</p>
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><b>allowEmptyLoopBody</b> which allows loops with empty bodies, e.g.:</p>
076 * <p>
077 * {@code
078 * while (value.incrementValue() < 5);
079 * }
080 * </p>
081 * <p>
082 * {@code
083 * for(int i = 0; i < 10; value.incrementValue());
084 * }
085 * </p>
086 * <p>Default value for allowEmptyLoopBody option is <b>false</b>.</p>
087 * <p>
088 * To configure the Check to allow {@code case, default} single-line statements
089 * without braces:
090 * </p>
091 *
092 * <pre>
093 * &lt;module name=&quot;NeedBraces&quot;&gt;
094 *     &lt;property name=&quot;tokens&quot; value=&quot;LITERAL_CASE, LITERAL_DEFAULT&quot;/&gt;
095 *     &lt;property name=&quot;allowSingleLineStatement&quot; value=&quot;true&quot;/&gt;
096 * &lt;/module&gt;
097 * </pre>
098 *
099 * <p>
100 * Such statements would be allowed:
101 * </p>
102 *
103 * <pre>
104 * {@code
105 * switch (num) {
106 *     case 1: counter++; break; // OK
107 *     case 6: counter += 10; break; // OK
108 *     default: counter = 100; break; // OK
109 * }
110 * }
111 * </pre>
112 * <p>
113 * To configure the Check to allow {@code while, for} loops with empty bodies:
114 * </p>
115 *
116 * <pre>
117 * &lt;module name=&quot;NeedBraces&quot;&gt;
118 *     &lt;property name=&quot;allowEmptyLoopBody&quot; value=&quot;true&quot;/&gt;
119 * &lt;/module&gt;
120 * </pre>
121 *
122 * <p>
123 * Such statements would be allowed:
124 * </p>
125 *
126 * <pre>
127 * {@code
128 * while (value.incrementValue() &lt; 5); // OK
129 * for(int i = 0; i &lt; 10; value.incrementValue()); // OK
130 * }
131 * </pre>
132 *
133 * @author Rick Giles
134 * @author <a href="mailto:nesterenko-aleksey@list.ru">Aleksey Nesterenko</a>
135 * @author <a href="mailto:andreyselkin@gmail.com">Andrei Selkin</a>
136 */
137public class NeedBracesCheck extends Check {
138    /**
139     * A key is pointing to the warning message text in "messages.properties"
140     * file.
141     */
142    public static final String MSG_KEY_NEED_BRACES = "needBraces";
143
144    /**
145     * Check's option for skipping single-line statements.
146     */
147    private boolean allowSingleLineStatement;
148
149    /**
150     * Check's option for allowing loops with empty body.
151     */
152    private boolean allowEmptyLoopBody;
153
154    /**
155     * Setter.
156     * @param allowSingleLineStatement Check's option for skipping single-line statements
157     */
158    public void setAllowSingleLineStatement(boolean allowSingleLineStatement) {
159        this.allowSingleLineStatement = allowSingleLineStatement;
160    }
161
162    /**
163     * Sets whether to allow empty loop body.
164     * @param allowEmptyLoopBody Check's option for allowing loops with empty body.
165     */
166    public void setAllowEmptyLoopBody(boolean allowEmptyLoopBody) {
167        this.allowEmptyLoopBody = allowEmptyLoopBody;
168    }
169
170    @Override
171    public int[] getDefaultTokens() {
172        return new int[] {
173            TokenTypes.LITERAL_DO,
174            TokenTypes.LITERAL_ELSE,
175            TokenTypes.LITERAL_FOR,
176            TokenTypes.LITERAL_IF,
177            TokenTypes.LITERAL_WHILE,
178        };
179    }
180
181    @Override
182    public int[] getAcceptableTokens() {
183        return new int[] {
184            TokenTypes.LITERAL_DO,
185            TokenTypes.LITERAL_ELSE,
186            TokenTypes.LITERAL_FOR,
187            TokenTypes.LITERAL_IF,
188            TokenTypes.LITERAL_WHILE,
189            TokenTypes.LITERAL_CASE,
190            TokenTypes.LITERAL_DEFAULT,
191            TokenTypes.LAMBDA,
192        };
193    }
194
195    @Override
196    public int[] getRequiredTokens() {
197        return ArrayUtils.EMPTY_INT_ARRAY;
198    }
199
200    @Override
201    public void visitToken(DetailAST ast) {
202        final DetailAST slistAST = ast.findFirstToken(TokenTypes.SLIST);
203        boolean isElseIf = false;
204        if (ast.getType() == TokenTypes.LITERAL_ELSE
205            && ast.findFirstToken(TokenTypes.LITERAL_IF) != null) {
206            isElseIf = true;
207        }
208
209        final boolean skipStatement = isSkipStatement(ast);
210        final boolean skipEmptyLoopBody = allowEmptyLoopBody && isEmptyLoopBody(ast);
211
212        if (slistAST == null && !isElseIf && !skipStatement && !skipEmptyLoopBody) {
213            log(ast.getLineNo(), MSG_KEY_NEED_BRACES, ast.getText());
214        }
215    }
216
217    /**
218     * Checks if current statement can be skipped by "need braces" warning.
219     * @param statement if, for, while, do-while, lambda, else, case, default statements.
220     * @return true if current statement can be skipped by Check.
221     */
222    private boolean isSkipStatement(DetailAST statement) {
223        return allowSingleLineStatement && isSingleLineStatement(statement);
224    }
225
226    /**
227     * Checks if current loop statement does not have body, e.g.:
228     * <p>
229     * {@code
230     *   while (value.incrementValue() < 5);
231     *   ...
232     *   for(int i = 0; i < 10; value.incrementValue());
233     * }
234     * </p>
235     * @param ast ast token.
236     * @return true if current loop statement does not have body.
237     */
238    private boolean isEmptyLoopBody(DetailAST ast) {
239        boolean noBodyLoop = false;
240
241        if (ast.getType() == TokenTypes.LITERAL_FOR
242                || ast.getType() == TokenTypes.LITERAL_WHILE) {
243            DetailAST currentToken = ast.getFirstChild();
244            while (currentToken.getNextSibling() != null) {
245                currentToken = currentToken.getNextSibling();
246            }
247            noBodyLoop = currentToken.getType() == TokenTypes.EMPTY_STAT;
248        }
249        return noBodyLoop;
250    }
251
252    /**
253     * Checks if current statement is single-line statement, e.g.:
254     * <p>
255     * {@code
256     * if (obj.isValid()) return true;
257     * }
258     * </p>
259     * <p>
260     * {@code
261     * while (obj.isValid()) return true;
262     * }
263     * </p>
264     * @param statement if, for, while, do-while, lambda, else, case, default statements.
265     * @return true if current statement is single-line statement.
266     */
267    private static boolean isSingleLineStatement(DetailAST statement) {
268        boolean result;
269
270        switch (statement.getType()) {
271            case TokenTypes.LITERAL_IF:
272                result = isSingleLineIf(statement);
273                break;
274            case TokenTypes.LITERAL_FOR:
275                result = isSingleLineFor(statement);
276                break;
277            case TokenTypes.LITERAL_DO:
278                result = isSingleLineDoWhile(statement);
279                break;
280            case TokenTypes.LITERAL_WHILE:
281                result = isSingleLineWhile(statement);
282                break;
283            case TokenTypes.LAMBDA:
284                result = isSingleLineLambda(statement);
285                break;
286            case TokenTypes.LITERAL_CASE:
287                result = isSingleLineCase(statement);
288                break;
289            case TokenTypes.LITERAL_DEFAULT:
290                result = isSingleLineDefault(statement);
291                break;
292            default:
293                result = isSingleLineElse(statement);
294                break;
295        }
296
297        return result;
298    }
299
300    /**
301     * Checks if current while statement is single-line statement, e.g.:
302     * <p>
303     * {@code
304     * while (obj.isValid()) return true;
305     * }
306     * </p>
307     * @param literalWhile {@link TokenTypes#LITERAL_WHILE while statement}.
308     * @return true if current while statement is single-line statement.
309     */
310    private static boolean isSingleLineWhile(DetailAST literalWhile) {
311        boolean result = false;
312        if (literalWhile.getParent().getType() == TokenTypes.SLIST
313                && literalWhile.getLastChild().getType() != TokenTypes.SLIST) {
314            final DetailAST block = literalWhile.getLastChild().getPreviousSibling();
315            result = literalWhile.getLineNo() == block.getLineNo();
316        }
317        return result;
318    }
319
320    /**
321     * Checks if current do-while statement is single-line statement, e.g.:
322     * <p>
323     * {@code
324     * do this.notify(); while (o != null);
325     * }
326     * </p>
327     * @param literalDo {@link TokenTypes#LITERAL_DO do-while statement}.
328     * @return true if current do-while statement is single-line statement.
329     */
330    private static boolean isSingleLineDoWhile(DetailAST literalDo) {
331        boolean result = false;
332        if (literalDo.getParent().getType() == TokenTypes.SLIST
333                && literalDo.getFirstChild().getType() != TokenTypes.SLIST) {
334            final DetailAST block = literalDo.getFirstChild();
335            result = block.getLineNo() == literalDo.getLineNo();
336        }
337        return result;
338    }
339
340    /**
341     * Checks if current for statement is single-line statement, e.g.:
342     * <p>
343     * {@code
344     * for (int i = 0; ; ) this.notify();
345     * }
346     * </p>
347     * @param literalFor {@link TokenTypes#LITERAL_FOR for statement}.
348     * @return true if current for statement is single-line statement.
349     */
350    private static boolean isSingleLineFor(DetailAST literalFor) {
351        boolean result = false;
352        if (literalFor.getLastChild().getType() == TokenTypes.EMPTY_STAT) {
353            result = true;
354        }
355        else if (literalFor.getParent().getType() == TokenTypes.SLIST
356                && literalFor.getLastChild().getType() != TokenTypes.SLIST) {
357            final DetailAST block = findExpressionBlockInForLoop(literalFor);
358            if (block == null) {
359                result = literalFor.getLineNo() == literalFor.getLastChild().getLineNo();
360            }
361            else {
362                result = literalFor.getLineNo() == block.getLineNo();
363            }
364        }
365        return result;
366    }
367
368    /**
369     * Detects and returns expression block in classical and enhanced for loops.
370     *
371     * @param literalFor parent for loop literal
372     * @return expression block
373     */
374    private static DetailAST findExpressionBlockInForLoop(DetailAST literalFor) {
375        final DetailAST forEachClause = literalFor.findFirstToken(TokenTypes.FOR_EACH_CLAUSE);
376        final DetailAST firstToken;
377        if (forEachClause == null) {
378            firstToken = literalFor.findFirstToken(TokenTypes.EXPR);
379        }
380        else {
381            firstToken = forEachClause.findFirstToken(TokenTypes.EXPR);
382        }
383        return firstToken;
384    }
385
386    /**
387     * Checks if current if statement is single-line statement, e.g.:
388     * <p>
389     * {@code
390     * if (obj.isValid()) return true;
391     * }
392     * </p>
393     * @param literalIf {@link TokenTypes#LITERAL_IF if statement}.
394     * @return true if current if statement is single-line statement.
395     */
396    private static boolean isSingleLineIf(DetailAST literalIf) {
397        boolean result = false;
398        final DetailAST ifCondition = literalIf.findFirstToken(TokenTypes.EXPR);
399        if (literalIf.getParent().getType() == TokenTypes.SLIST) {
400            final DetailAST literalIfLastChild = literalIf.getLastChild();
401            final DetailAST block;
402            if (literalIfLastChild.getType() == TokenTypes.LITERAL_ELSE) {
403                block = literalIfLastChild.getPreviousSibling();
404            }
405            else {
406                block = literalIfLastChild;
407            }
408            result = ifCondition.getLineNo() == block.getLineNo();
409        }
410        return result;
411    }
412
413    /**
414     * Checks if current lambda statement is single-line statement, e.g.:
415     * <p>
416     * {@code
417     * Runnable r = () -> System.out.println("Hello, world!");
418     * }
419     * </p>
420     * @param lambda {@link TokenTypes#LAMBDA lambda statement}.
421     * @return true if current lambda statement is single-line statement.
422     */
423    private static boolean isSingleLineLambda(DetailAST lambda) {
424        boolean result = false;
425        final DetailAST block = lambda.getLastChild();
426        if (block.getType() != TokenTypes.SLIST) {
427            result = lambda.getLineNo() == block.getLineNo();
428        }
429        return result;
430    }
431
432    /**
433     * Checks if current case statement is single-line statement, e.g.:
434     * <p>
435     * {@code
436     * case 1: doSomeStuff(); break;
437     * case 2: doSomeStuff(); break;
438     * }
439     * </p>
440     * @param literalCase {@link TokenTypes#LITERAL_CASE case statement}.
441     * @return true if current case statement is single-line statement.
442     */
443    private static boolean isSingleLineCase(DetailAST literalCase) {
444        boolean result = false;
445        final DetailAST slist = literalCase.getNextSibling();
446        final DetailAST block = slist.getFirstChild();
447        if (block.getType() != TokenTypes.SLIST) {
448            final DetailAST caseBreak = slist.findFirstToken(TokenTypes.LITERAL_BREAK);
449            final boolean atOneLine = literalCase.getLineNo() == block.getLineNo();
450            if (caseBreak != null) {
451                result = atOneLine && block.getLineNo() == caseBreak.getLineNo();
452            }
453        }
454        return result;
455    }
456
457    /**
458     * Checks if current default statement is single-line statement, e.g.:
459     * <p>
460     * {@code
461     * default: doSomeStuff();
462     * }
463     * </p>
464     * @param literalDefault {@link TokenTypes#LITERAL_DEFAULT default statement}.
465     * @return true if current default statement is single-line statement.
466     */
467    private static boolean isSingleLineDefault(DetailAST literalDefault) {
468        boolean result = false;
469        final DetailAST slist = literalDefault.getNextSibling();
470        final DetailAST block = slist.getFirstChild();
471        if (block.getType() != TokenTypes.SLIST) {
472            result = literalDefault.getLineNo() == block.getLineNo();
473        }
474        return result;
475    }
476
477    /**
478     * Checks if current else statement is single-line statement, e.g.:
479     * <p>
480     * {@code
481     * else doSomeStuff();
482     * }
483     * </p>
484     * @param literalElse {@link TokenTypes#LITERAL_ELSE else statement}.
485     * @return true if current else statement is single-line statement.
486     */
487    private static boolean isSingleLineElse(DetailAST literalElse) {
488        boolean result = false;
489        final DetailAST block = literalElse.getFirstChild();
490        if (block.getType() != TokenTypes.SLIST) {
491            result = literalElse.getLineNo() == block.getLineNo();
492        }
493        return result;
494    }
495}