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