001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2018 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 com.puppycrawl.tools.checkstyle.StatelessCheck;
023import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
024import com.puppycrawl.tools.checkstyle.api.DetailAST;
025import com.puppycrawl.tools.checkstyle.api.TokenTypes;
026import com.puppycrawl.tools.checkstyle.utils.CommonUtils;
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 */
137@StatelessCheck
138public class NeedBracesCheck extends AbstractCheck {
139
140    /**
141     * A key is pointing to the warning message text in "messages.properties"
142     * file.
143     */
144    public static final String MSG_KEY_NEED_BRACES = "needBraces";
145
146    /**
147     * Check's option for skipping single-line statements.
148     */
149    private boolean allowSingleLineStatement;
150
151    /**
152     * Check's option for allowing loops with empty body.
153     */
154    private boolean allowEmptyLoopBody;
155
156    /**
157     * Setter.
158     * @param allowSingleLineStatement Check's option for skipping single-line statements
159     */
160    public void setAllowSingleLineStatement(boolean allowSingleLineStatement) {
161        this.allowSingleLineStatement = allowSingleLineStatement;
162    }
163
164    /**
165     * Sets whether to allow empty loop body.
166     * @param allowEmptyLoopBody Check's option for allowing loops with empty body.
167     */
168    public void setAllowEmptyLoopBody(boolean allowEmptyLoopBody) {
169        this.allowEmptyLoopBody = allowEmptyLoopBody;
170    }
171
172    @Override
173    public int[] getDefaultTokens() {
174        return new int[] {
175            TokenTypes.LITERAL_DO,
176            TokenTypes.LITERAL_ELSE,
177            TokenTypes.LITERAL_FOR,
178            TokenTypes.LITERAL_IF,
179            TokenTypes.LITERAL_WHILE,
180        };
181    }
182
183    @Override
184    public int[] getAcceptableTokens() {
185        return new int[] {
186            TokenTypes.LITERAL_DO,
187            TokenTypes.LITERAL_ELSE,
188            TokenTypes.LITERAL_FOR,
189            TokenTypes.LITERAL_IF,
190            TokenTypes.LITERAL_WHILE,
191            TokenTypes.LITERAL_CASE,
192            TokenTypes.LITERAL_DEFAULT,
193            TokenTypes.LAMBDA,
194        };
195    }
196
197    @Override
198    public int[] getRequiredTokens() {
199        return CommonUtils.EMPTY_INT_ARRAY;
200    }
201
202    @Override
203    public void visitToken(DetailAST ast) {
204        final DetailAST slistAST = ast.findFirstToken(TokenTypes.SLIST);
205        boolean isElseIf = false;
206        if (ast.getType() == TokenTypes.LITERAL_ELSE
207            && ast.findFirstToken(TokenTypes.LITERAL_IF) != null) {
208            isElseIf = true;
209        }
210        final boolean isDefaultInAnnotation = isDefaultInAnnotation(ast);
211        final boolean skipStatement = isSkipStatement(ast);
212        final boolean skipEmptyLoopBody = allowEmptyLoopBody && isEmptyLoopBody(ast);
213
214        if (slistAST == null && !isElseIf && !isDefaultInAnnotation
215                && !skipStatement && !skipEmptyLoopBody) {
216            log(ast.getLineNo(), MSG_KEY_NEED_BRACES, ast.getText());
217        }
218    }
219
220    /**
221     * Checks if ast is the default token of an annotation field.
222     * @param ast ast to test.
223     * @return true if current ast is default and it is part of annotation.
224     */
225    private static boolean isDefaultInAnnotation(DetailAST ast) {
226        boolean isDefaultInAnnotation = false;
227        if (ast.getType() == TokenTypes.LITERAL_DEFAULT
228                && ast.getParent().getType() == TokenTypes.ANNOTATION_FIELD_DEF) {
229            isDefaultInAnnotation = true;
230        }
231        return isDefaultInAnnotation;
232    }
233
234    /**
235     * Checks if current statement can be skipped by "need braces" warning.
236     * @param statement if, for, while, do-while, lambda, else, case, default statements.
237     * @return true if current statement can be skipped by Check.
238     */
239    private boolean isSkipStatement(DetailAST statement) {
240        return allowSingleLineStatement && isSingleLineStatement(statement);
241    }
242
243    /**
244     * Checks if current loop statement does not have body, e.g.:
245     * <p>
246     * {@code
247     *   while (value.incrementValue() < 5);
248     *   ...
249     *   for(int i = 0; i < 10; value.incrementValue());
250     * }
251     * </p>
252     * @param ast ast token.
253     * @return true if current loop statement does not have body.
254     */
255    private static boolean isEmptyLoopBody(DetailAST ast) {
256        boolean noBodyLoop = false;
257
258        if (ast.getType() == TokenTypes.LITERAL_FOR
259                || ast.getType() == TokenTypes.LITERAL_WHILE) {
260            DetailAST currentToken = ast.getFirstChild();
261            while (currentToken.getNextSibling() != null) {
262                currentToken = currentToken.getNextSibling();
263            }
264            noBodyLoop = currentToken.getType() == TokenTypes.EMPTY_STAT;
265        }
266        return noBodyLoop;
267    }
268
269    /**
270     * Checks if current statement is single-line statement, e.g.:
271     * <p>
272     * {@code
273     * if (obj.isValid()) return true;
274     * }
275     * </p>
276     * <p>
277     * {@code
278     * while (obj.isValid()) return true;
279     * }
280     * </p>
281     * @param statement if, for, while, do-while, lambda, else, case, default statements.
282     * @return true if current statement is single-line statement.
283     */
284    private static boolean isSingleLineStatement(DetailAST statement) {
285        final boolean result;
286
287        switch (statement.getType()) {
288            case TokenTypes.LITERAL_IF:
289                result = isSingleLineIf(statement);
290                break;
291            case TokenTypes.LITERAL_FOR:
292                result = isSingleLineFor(statement);
293                break;
294            case TokenTypes.LITERAL_DO:
295                result = isSingleLineDoWhile(statement);
296                break;
297            case TokenTypes.LITERAL_WHILE:
298                result = isSingleLineWhile(statement);
299                break;
300            case TokenTypes.LAMBDA:
301                result = isSingleLineLambda(statement);
302                break;
303            case TokenTypes.LITERAL_CASE:
304                result = isSingleLineCase(statement);
305                break;
306            case TokenTypes.LITERAL_DEFAULT:
307                result = isSingleLineDefault(statement);
308                break;
309            default:
310                result = isSingleLineElse(statement);
311                break;
312        }
313
314        return result;
315    }
316
317    /**
318     * Checks if current while statement is single-line statement, e.g.:
319     * <p>
320     * {@code
321     * while (obj.isValid()) return true;
322     * }
323     * </p>
324     * @param literalWhile {@link TokenTypes#LITERAL_WHILE while statement}.
325     * @return true if current while statement is single-line statement.
326     */
327    private static boolean isSingleLineWhile(DetailAST literalWhile) {
328        boolean result = false;
329        if (literalWhile.getParent().getType() == TokenTypes.SLIST
330                && literalWhile.getLastChild().getType() != TokenTypes.SLIST) {
331            final DetailAST block = literalWhile.getLastChild().getPreviousSibling();
332            result = literalWhile.getLineNo() == block.getLineNo();
333        }
334        return result;
335    }
336
337    /**
338     * Checks if current do-while statement is single-line statement, e.g.:
339     * <p>
340     * {@code
341     * do this.notify(); while (o != null);
342     * }
343     * </p>
344     * @param literalDo {@link TokenTypes#LITERAL_DO do-while statement}.
345     * @return true if current do-while statement is single-line statement.
346     */
347    private static boolean isSingleLineDoWhile(DetailAST literalDo) {
348        boolean result = false;
349        if (literalDo.getParent().getType() == TokenTypes.SLIST
350                && literalDo.getFirstChild().getType() != TokenTypes.SLIST) {
351            final DetailAST block = literalDo.getFirstChild();
352            result = block.getLineNo() == literalDo.getLineNo();
353        }
354        return result;
355    }
356
357    /**
358     * Checks if current for statement is single-line statement, e.g.:
359     * <p>
360     * {@code
361     * for (int i = 0; ; ) this.notify();
362     * }
363     * </p>
364     * @param literalFor {@link TokenTypes#LITERAL_FOR for statement}.
365     * @return true if current for statement is single-line statement.
366     */
367    private static boolean isSingleLineFor(DetailAST literalFor) {
368        boolean result = false;
369        if (literalFor.getLastChild().getType() == TokenTypes.EMPTY_STAT) {
370            result = true;
371        }
372        else if (literalFor.getParent().getType() == TokenTypes.SLIST
373                && literalFor.getLastChild().getType() != TokenTypes.SLIST) {
374            result = literalFor.getLineNo() == literalFor.getLastChild().getLineNo();
375        }
376        return result;
377    }
378
379    /**
380     * Checks if current if statement is single-line statement, e.g.:
381     * <p>
382     * {@code
383     * if (obj.isValid()) return true;
384     * }
385     * </p>
386     * @param literalIf {@link TokenTypes#LITERAL_IF if statement}.
387     * @return true if current if statement is single-line statement.
388     */
389    private static boolean isSingleLineIf(DetailAST literalIf) {
390        boolean result = false;
391        if (literalIf.getParent().getType() == TokenTypes.SLIST) {
392            final DetailAST literalIfLastChild = literalIf.getLastChild();
393            final DetailAST block;
394            if (literalIfLastChild.getType() == TokenTypes.LITERAL_ELSE) {
395                block = literalIfLastChild.getPreviousSibling();
396            }
397            else {
398                block = literalIfLastChild;
399            }
400            final DetailAST ifCondition = literalIf.findFirstToken(TokenTypes.EXPR);
401            result = ifCondition.getLineNo() == block.getLineNo();
402        }
403        return result;
404    }
405
406    /**
407     * Checks if current lambda statement is single-line statement, e.g.:
408     * <p>
409     * {@code
410     * Runnable r = () -> System.out.println("Hello, world!");
411     * }
412     * </p>
413     * @param lambda {@link TokenTypes#LAMBDA lambda statement}.
414     * @return true if current lambda statement is single-line statement.
415     */
416    private static boolean isSingleLineLambda(DetailAST lambda) {
417        boolean result = false;
418        final DetailAST block = lambda.getLastChild();
419        if (block.getType() != TokenTypes.SLIST) {
420            result = lambda.getLineNo() == block.getLineNo();
421        }
422        return result;
423    }
424
425    /**
426     * Checks if current case statement is single-line statement, e.g.:
427     * <p>
428     * {@code
429     * case 1: doSomeStuff(); break;
430     * case 2: doSomeStuff(); break;
431     * case 3: ;
432     * }
433     * </p>
434     * @param literalCase {@link TokenTypes#LITERAL_CASE case statement}.
435     * @return true if current case statement is single-line statement.
436     */
437    private static boolean isSingleLineCase(DetailAST literalCase) {
438        boolean result = false;
439        final DetailAST slist = literalCase.getNextSibling();
440        if (slist == null) {
441            result = true;
442        }
443        else {
444            final DetailAST block = slist.getFirstChild();
445            if (block.getType() != TokenTypes.SLIST) {
446                final DetailAST caseBreak = slist.findFirstToken(TokenTypes.LITERAL_BREAK);
447                if (caseBreak != null) {
448                    final boolean atOneLine = literalCase.getLineNo() == block.getLineNo();
449                    result = atOneLine && block.getLineNo() == caseBreak.getLineNo();
450                }
451            }
452        }
453        return result;
454    }
455
456    /**
457     * Checks if current default statement is single-line statement, e.g.:
458     * <p>
459     * {@code
460     * default: doSomeStuff();
461     * }
462     * </p>
463     * @param literalDefault {@link TokenTypes#LITERAL_DEFAULT default statement}.
464     * @return true if current default statement is single-line statement.
465     */
466    private static boolean isSingleLineDefault(DetailAST literalDefault) {
467        boolean result = false;
468        final DetailAST slist = literalDefault.getNextSibling();
469        if (slist == null) {
470            result = true;
471        }
472        else {
473            final DetailAST block = slist.getFirstChild();
474            if (block != null && block.getType() != TokenTypes.SLIST) {
475                result = literalDefault.getLineNo() == block.getLineNo();
476            }
477        }
478        return result;
479    }
480
481    /**
482     * Checks if current else statement is single-line statement, e.g.:
483     * <p>
484     * {@code
485     * else doSomeStuff();
486     * }
487     * </p>
488     * @param literalElse {@link TokenTypes#LITERAL_ELSE else statement}.
489     * @return true if current else statement is single-line statement.
490     */
491    private static boolean isSingleLineElse(DetailAST literalElse) {
492        boolean result = false;
493        final DetailAST block = literalElse.getFirstChild();
494        if (block.getType() != TokenTypes.SLIST) {
495            result = literalElse.getLineNo() == block.getLineNo();
496        }
497        return result;
498    }
499
500}