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