001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2020 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.Optional;
023
024import com.puppycrawl.tools.checkstyle.StatelessCheck;
025import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
026import com.puppycrawl.tools.checkstyle.api.DetailAST;
027import com.puppycrawl.tools.checkstyle.api.TokenTypes;
028import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
029import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
030
031/**
032 * <p>
033 * Checks for braces around code blocks.
034 * </p>
035 * <ul>
036 * <li>
037 * Property {@code allowSingleLineStatement} - allow single-line statements without braces.
038 * Type is {@code boolean}.
039 * Default value is {@code false}.
040 * </li>
041 * <li>
042 * Property {@code allowEmptyLoopBody} - allow loops with empty bodies.
043 * Type is {@code boolean}.
044 * Default value is {@code false}.
045 * </li>
046 * <li>
047 * Property {@code tokens} - tokens to check
048 * Type is {@code int[]}.
049 * Default value is:
050 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_DO">
051 * LITERAL_DO</a>,
052 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_ELSE">
053 * LITERAL_ELSE</a>,
054 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_FOR">
055 * LITERAL_FOR</a>,
056 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_IF">
057 * LITERAL_IF</a>,
058 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_WHILE">
059 * LITERAL_WHILE</a>.
060 * </li>
061 * </ul>
062 * <p>
063 * To configure the check:
064 * </p>
065 * <pre>
066 * &lt;module name="NeedBraces"/&gt;
067 * </pre>
068 * <p>
069 * To configure the check for {@code if} and {@code else} blocks:
070 * </p>
071 * <pre>
072 * &lt;module name=&quot;NeedBraces&quot;&gt;
073 *   &lt;property name=&quot;tokens&quot; value=&quot;LITERAL_IF, LITERAL_ELSE&quot;/&gt;
074 * &lt;/module&gt;
075 * </pre>
076 * <p>
077 * To configure the check to allow single-line statements
078 * ({@code if, while, do-while, for}) without braces:
079 * </p>
080 * <pre>
081 * &lt;module name=&quot;NeedBraces&quot;&gt;
082 *   &lt;property name=&quot;allowSingleLineStatement&quot; value=&quot;true&quot;/&gt;
083 * &lt;/module&gt;
084 * </pre>
085 * <p>
086 * Next statements won't be violated by check:
087 * </p>
088 * <pre>
089 * if (obj.isValid()) return true; // OK
090 * while (obj.isValid()) return true; // OK
091 * do this.notify(); while (o != null); // OK
092 * for (int i = 0; ; ) this.notify(); // OK
093 * </pre>
094 * <p>
095 * To configure the check to allow {@code case, default} single-line statements without braces:
096 * </p>
097 * <pre>
098 * &lt;module name=&quot;NeedBraces&quot;&gt;
099 *   &lt;property name=&quot;tokens&quot; value=&quot;LITERAL_CASE, LITERAL_DEFAULT&quot;/&gt;
100 *   &lt;property name=&quot;allowSingleLineStatement&quot; value=&quot;true&quot;/&gt;
101 * &lt;/module&gt;
102 * </pre>
103 * <p>
104 * Next statements won't be violated by check:
105 * </p>
106 * <pre>
107 * switch (num) {
108 *   case 1: counter++; break; // OK
109 *   case 6: counter += 10; break; // OK
110 *   default: counter = 100; break; // OK
111 * }
112 * </pre>
113 * <p>
114 * To configure the check to allow loops ({@code while, for}) with empty bodies:
115 * </p>
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 * <p>
122 * Next statements won't be violated by check:
123 * </p>
124 * <pre>
125 * while (value.incrementValue() &lt; 5); // OK
126 * for(int i = 0; i &lt; 10; value.incrementValue()); // OK
127 * </pre>
128 * <p>
129 * To configure the check to lambdas:
130 * </p>
131 * <pre>
132 * &lt;module name=&quot;NeedBraces&quot;&gt;
133 *   &lt;property name=&quot;tokens&quot; value=&quot;LAMBDA&quot;/&gt;
134 *   &lt;property name=&quot;allowSingleLineStatement&quot; value=&quot;true&quot;/&gt;
135 * &lt;/module&gt;
136 * </pre>
137 * <p>
138 * Results in following:
139 * </p>
140 * <pre>
141 * allowedFuture.addCallback(result -&gt; assertEquals("Invalid response",
142 *   EnumSet.of(HttpMethod.GET, HttpMethod.OPTIONS), result), // violation, lambda spans 2 lines
143 *   ex -&gt; fail(ex.getMessage())); // OK
144 *
145 * allowedFuture.addCallback(result -&gt; {
146 *   return assertEquals("Invalid response",
147 *     EnumSet.of(HttpMethod.GET, HttpMethod.OPTIONS), result);
148 *   }, // OK
149 *   ex -&gt; fail(ex.getMessage()));
150 * </pre>
151 * <p>
152 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
153 * </p>
154 * <p>
155 * Violation Message Keys:
156 * </p>
157 * <ul>
158 * <li>
159 * {@code needBraces}
160 * </li>
161 * </ul>
162 *
163 * @since 3.0
164 */
165@StatelessCheck
166public class NeedBracesCheck extends AbstractCheck {
167
168    /**
169     * A key is pointing to the warning message text in "messages.properties"
170     * file.
171     */
172    public static final String MSG_KEY_NEED_BRACES = "needBraces";
173
174    /**
175     * Allow single-line statements without braces.
176     */
177    private boolean allowSingleLineStatement;
178
179    /**
180     * Allow loops with empty bodies.
181     */
182    private boolean allowEmptyLoopBody;
183
184    /**
185     * Setter to allow single-line statements without braces.
186     *
187     * @param allowSingleLineStatement Check's option for skipping single-line statements
188     */
189    public void setAllowSingleLineStatement(boolean allowSingleLineStatement) {
190        this.allowSingleLineStatement = allowSingleLineStatement;
191    }
192
193    /**
194     * Setter to allow loops with empty bodies.
195     *
196     * @param allowEmptyLoopBody Check's option for allowing loops with empty body.
197     */
198    public void setAllowEmptyLoopBody(boolean allowEmptyLoopBody) {
199        this.allowEmptyLoopBody = allowEmptyLoopBody;
200    }
201
202    @Override
203    public int[] getDefaultTokens() {
204        return new int[] {
205            TokenTypes.LITERAL_DO,
206            TokenTypes.LITERAL_ELSE,
207            TokenTypes.LITERAL_FOR,
208            TokenTypes.LITERAL_IF,
209            TokenTypes.LITERAL_WHILE,
210        };
211    }
212
213    @Override
214    public int[] getAcceptableTokens() {
215        return new int[] {
216            TokenTypes.LITERAL_DO,
217            TokenTypes.LITERAL_ELSE,
218            TokenTypes.LITERAL_FOR,
219            TokenTypes.LITERAL_IF,
220            TokenTypes.LITERAL_WHILE,
221            TokenTypes.LITERAL_CASE,
222            TokenTypes.LITERAL_DEFAULT,
223            TokenTypes.LAMBDA,
224        };
225    }
226
227    @Override
228    public int[] getRequiredTokens() {
229        return CommonUtil.EMPTY_INT_ARRAY;
230    }
231
232    @Override
233    public void visitToken(DetailAST ast) {
234        final boolean hasNoSlist = ast.findFirstToken(TokenTypes.SLIST) == null;
235        if (hasNoSlist && !isSkipStatement(ast) && isBracesNeeded(ast)) {
236            log(ast, MSG_KEY_NEED_BRACES, ast.getText());
237        }
238    }
239
240    /**
241     * Checks if token needs braces.
242     * Some tokens have additional conditions:
243     * <ul>
244     *     <li>{@link TokenTypes#LITERAL_FOR}</li>
245     *     <li>{@link TokenTypes#LITERAL_WHILE}</li>
246     *     <li>{@link TokenTypes#LITERAL_CASE}</li>
247     *     <li>{@link TokenTypes#LITERAL_DEFAULT}</li>
248     *     <li>{@link TokenTypes#LITERAL_ELSE}</li>
249     * </ul>
250     * For all others default value {@code true} is returned.
251     *
252     * @param ast token to check
253     * @return result of additional checks for specific token types,
254     * {@code true} if there is no additional checks for token
255     */
256    private boolean isBracesNeeded(DetailAST ast) {
257        final boolean result;
258        switch (ast.getType()) {
259            case TokenTypes.LITERAL_FOR:
260            case TokenTypes.LITERAL_WHILE:
261                result = !isEmptyLoopBodyAllowed(ast);
262                break;
263            case TokenTypes.LITERAL_CASE:
264            case TokenTypes.LITERAL_DEFAULT:
265                result = hasUnbracedStatements(ast);
266                break;
267            case TokenTypes.LITERAL_ELSE:
268                result = ast.findFirstToken(TokenTypes.LITERAL_IF) == null;
269                break;
270            default:
271                result = true;
272                break;
273        }
274        return result;
275    }
276
277    /**
278     * Checks if current loop has empty body and can be skipped by this check.
279     *
280     * @param ast for, while statements.
281     * @return true if current loop can be skipped by check.
282     */
283    private boolean isEmptyLoopBodyAllowed(DetailAST ast) {
284        return allowEmptyLoopBody && ast.findFirstToken(TokenTypes.EMPTY_STAT) != null;
285    }
286
287    /**
288     * Checks if switch member (case, default statements) has statements without curly braces.
289     *
290     * @param ast case, default statements.
291     * @return true if switch member has unbraced statements, false otherwise.
292     */
293    private static boolean hasUnbracedStatements(DetailAST ast) {
294        final DetailAST nextSibling = ast.getNextSibling();
295        return nextSibling != null
296            && nextSibling.getType() == TokenTypes.SLIST
297            && nextSibling.getFirstChild().getType() != TokenTypes.SLIST;
298    }
299
300    /**
301     * Checks if current statement can be skipped by "need braces" warning.
302     *
303     * @param statement if, for, while, do-while, lambda, else, case, default statements.
304     * @return true if current statement can be skipped by Check.
305     */
306    private boolean isSkipStatement(DetailAST statement) {
307        return allowSingleLineStatement && isSingleLineStatement(statement);
308    }
309
310    /**
311     * Checks if current statement is single-line statement, e.g.:
312     * <p>
313     * {@code
314     * if (obj.isValid()) return true;
315     * }
316     * </p>
317     * <p>
318     * {@code
319     * while (obj.isValid()) return true;
320     * }
321     * </p>
322     *
323     * @param statement if, for, while, do-while, lambda, else, case, default statements.
324     * @return true if current statement is single-line statement.
325     */
326    private static boolean isSingleLineStatement(DetailAST statement) {
327        final boolean result;
328
329        switch (statement.getType()) {
330            case TokenTypes.LITERAL_IF:
331                result = isSingleLineIf(statement);
332                break;
333            case TokenTypes.LITERAL_FOR:
334                result = isSingleLineFor(statement);
335                break;
336            case TokenTypes.LITERAL_DO:
337                result = isSingleLineDoWhile(statement);
338                break;
339            case TokenTypes.LITERAL_WHILE:
340                result = isSingleLineWhile(statement);
341                break;
342            case TokenTypes.LAMBDA:
343                result = isSingleLineLambda(statement);
344                break;
345            case TokenTypes.LITERAL_CASE:
346            case TokenTypes.LITERAL_DEFAULT:
347                result = isSingleLineSwitchMember(statement);
348                break;
349            default:
350                result = isSingleLineElse(statement);
351                break;
352        }
353
354        return result;
355    }
356
357    /**
358     * Checks if current while statement is single-line statement, e.g.:
359     * <p>
360     * {@code
361     * while (obj.isValid()) return true;
362     * }
363     * </p>
364     *
365     * @param literalWhile {@link TokenTypes#LITERAL_WHILE while statement}.
366     * @return true if current while statement is single-line statement.
367     */
368    private static boolean isSingleLineWhile(DetailAST literalWhile) {
369        boolean result = false;
370        if (literalWhile.getParent().getType() == TokenTypes.SLIST) {
371            final DetailAST block = literalWhile.getLastChild().getPreviousSibling();
372            result = TokenUtil.areOnSameLine(literalWhile, block);
373        }
374        return result;
375    }
376
377    /**
378     * Checks if current do-while statement is single-line statement, e.g.:
379     * <p>
380     * {@code
381     * do this.notify(); while (o != null);
382     * }
383     * </p>
384     *
385     * @param literalDo {@link TokenTypes#LITERAL_DO do-while statement}.
386     * @return true if current do-while statement is single-line statement.
387     */
388    private static boolean isSingleLineDoWhile(DetailAST literalDo) {
389        boolean result = false;
390        if (literalDo.getParent().getType() == TokenTypes.SLIST) {
391            final DetailAST block = literalDo.getFirstChild();
392            result = TokenUtil.areOnSameLine(block, literalDo);
393        }
394        return result;
395    }
396
397    /**
398     * Checks if current for statement is single-line statement, e.g.:
399     * <p>
400     * {@code
401     * for (int i = 0; ; ) this.notify();
402     * }
403     * </p>
404     *
405     * @param literalFor {@link TokenTypes#LITERAL_FOR for statement}.
406     * @return true if current for statement is single-line statement.
407     */
408    private static boolean isSingleLineFor(DetailAST literalFor) {
409        boolean result = false;
410        if (literalFor.getLastChild().getType() == TokenTypes.EMPTY_STAT) {
411            result = true;
412        }
413        else if (literalFor.getParent().getType() == TokenTypes.SLIST) {
414            result = TokenUtil.areOnSameLine(literalFor, literalFor.getLastChild());
415        }
416        return result;
417    }
418
419    /**
420     * Checks if current if statement is single-line statement, e.g.:
421     * <p>
422     * {@code
423     * if (obj.isValid()) return true;
424     * }
425     * </p>
426     *
427     * @param literalIf {@link TokenTypes#LITERAL_IF if statement}.
428     * @return true if current if statement is single-line statement.
429     */
430    private static boolean isSingleLineIf(DetailAST literalIf) {
431        boolean result = false;
432        if (literalIf.getParent().getType() == TokenTypes.SLIST) {
433            final DetailAST literalIfLastChild = literalIf.getLastChild();
434            final DetailAST block;
435            if (literalIfLastChild.getType() == TokenTypes.LITERAL_ELSE) {
436                block = literalIfLastChild.getPreviousSibling();
437            }
438            else {
439                block = literalIfLastChild;
440            }
441            final DetailAST ifCondition = literalIf.findFirstToken(TokenTypes.EXPR);
442            result = TokenUtil.areOnSameLine(ifCondition, block);
443        }
444        return result;
445    }
446
447    /**
448     * Checks if current lambda statement is single-line statement, e.g.:
449     * <p>
450     * {@code
451     * Runnable r = () -> System.out.println("Hello, world!");
452     * }
453     * </p>
454     *
455     * @param lambda {@link TokenTypes#LAMBDA lambda statement}.
456     * @return true if current lambda statement is single-line statement.
457     */
458    private static boolean isSingleLineLambda(DetailAST lambda) {
459        final DetailAST lastLambdaToken = getLastLambdaToken(lambda);
460        return TokenUtil.areOnSameLine(lambda, lastLambdaToken);
461    }
462
463    /**
464     * Looks for the last token in lambda.
465     *
466     * @param lambda token to check.
467     * @return last token in lambda
468     */
469    private static DetailAST getLastLambdaToken(DetailAST lambda) {
470        DetailAST node = lambda;
471        do {
472            node = node.getLastChild();
473        } while (node.getLastChild() != null);
474        return node;
475    }
476
477    /**
478     * Checks if switch member (case or default statement) is single-line statement, e.g.:
479     * <p>
480     * {@code
481     * case 1: doSomeStuff(); break;
482     * case 2: doSomeStuff(); break;
483     * case 3: ;
484     * default: doSomeStuff();break;
485     * }
486     * </p>
487     *
488     * @param ast {@link TokenTypes#LITERAL_CASE case statement} or
489     * {@link TokenTypes#LITERAL_DEFAULT default statement}.
490     * @return true if current switch member is single-line statement.
491     */
492    private static boolean isSingleLineSwitchMember(DetailAST ast) {
493        return Optional.of(ast)
494                .map(DetailAST::getNextSibling)
495                .map(DetailAST::getLastChild)
496                .map(lastToken -> TokenUtil.areOnSameLine(ast, lastToken))
497                .orElse(true);
498    }
499
500    /**
501     * Checks if current else statement is single-line statement, e.g.:
502     * <p>
503     * {@code
504     * else doSomeStuff();
505     * }
506     * </p>
507     *
508     * @param literalElse {@link TokenTypes#LITERAL_ELSE else statement}.
509     * @return true if current else statement is single-line statement.
510     */
511    private static boolean isSingleLineElse(DetailAST literalElse) {
512        final DetailAST block = literalElse.getFirstChild();
513        return TokenUtil.areOnSameLine(literalElse, block);
514    }
515
516}