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