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