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.Arrays;
023import java.util.Locale;
024
025import com.puppycrawl.tools.checkstyle.DetailAstImpl;
026import com.puppycrawl.tools.checkstyle.StatelessCheck;
027import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
028import com.puppycrawl.tools.checkstyle.api.DetailAST;
029import com.puppycrawl.tools.checkstyle.api.TokenTypes;
030import com.puppycrawl.tools.checkstyle.utils.CheckUtil;
031import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
032
033/**
034 * <p>
035 * Checks the placement of right curly braces ({@code '}'}) for code blocks. This check supports
036 * if-else, try-catch-finally blocks, while-loops, for-loops,
037 * method definitions, class definitions, constructor definitions,
038 * instance, static initialization blocks, annotation definitions and enum definitions.
039 * For right curly brace of expression blocks please follow issue
040 * <a href="https://github.com/checkstyle/checkstyle/issues/5945">#5945</a>.
041 * </p>
042 * <ul>
043 * <li>
044 * Property {@code option} - Specify the policy on placement of a right curly brace
045 * (<code>'}'</code>).
046 * Default value is {@code same}.
047 * </li>
048 * <li>
049 * Property {@code tokens} - tokens to check
050 * Default value is:
051 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_TRY">
052 * LITERAL_TRY</a>,
053 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_CATCH">
054 * LITERAL_CATCH</a>,
055 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_FINALLY">
056 * LITERAL_FINALLY</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_ELSE">
060 * LITERAL_ELSE</a>.
061 * </li>
062 * </ul>
063 * <p>
064 * To configure the check:
065 * </p>
066 * <pre>
067 * &lt;module name="RightCurly"/&gt;
068 * </pre>
069 * <p>
070 * To configure the check with policy {@code alone} for {@code else} and
071 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#METHOD_DEF">
072 * METHOD_DEF</a> tokens:
073 * </p>
074 * <pre>
075 * &lt;module name=&quot;RightCurly&quot;&gt;
076 *   &lt;property name=&quot;option&quot; value=&quot;alone&quot;/&gt;
077 *   &lt;property name=&quot;tokens&quot; value=&quot;LITERAL_ELSE, METHOD_DEF&quot;/&gt;
078 * &lt;/module&gt;
079 * </pre>
080 *
081 * @since 3.0
082 *
083 */
084@StatelessCheck
085public class RightCurlyCheck extends AbstractCheck {
086
087    /**
088     * A key is pointing to the warning message text in "messages.properties"
089     * file.
090     */
091    public static final String MSG_KEY_LINE_BREAK_BEFORE = "line.break.before";
092
093    /**
094     * A key is pointing to the warning message text in "messages.properties"
095     * file.
096     */
097    public static final String MSG_KEY_LINE_ALONE = "line.alone";
098
099    /**
100     * A key is pointing to the warning message text in "messages.properties"
101     * file.
102     */
103    public static final String MSG_KEY_LINE_SAME = "line.same";
104
105    /**
106     * Specify the policy on placement of a right curly brace (<code>'}'</code>).
107     */
108    private RightCurlyOption option = RightCurlyOption.SAME;
109
110    /**
111     * Setter to specify the policy on placement of a right curly brace (<code>'}'</code>).
112     * @param optionStr string to decode option from
113     * @throws IllegalArgumentException if unable to decode
114     */
115    public void setOption(String optionStr) {
116        option = RightCurlyOption.valueOf(optionStr.trim().toUpperCase(Locale.ENGLISH));
117    }
118
119    @Override
120    public int[] getDefaultTokens() {
121        return new int[] {
122            TokenTypes.LITERAL_TRY,
123            TokenTypes.LITERAL_CATCH,
124            TokenTypes.LITERAL_FINALLY,
125            TokenTypes.LITERAL_IF,
126            TokenTypes.LITERAL_ELSE,
127        };
128    }
129
130    @Override
131    public int[] getAcceptableTokens() {
132        return new int[] {
133            TokenTypes.LITERAL_TRY,
134            TokenTypes.LITERAL_CATCH,
135            TokenTypes.LITERAL_FINALLY,
136            TokenTypes.LITERAL_IF,
137            TokenTypes.LITERAL_ELSE,
138            TokenTypes.CLASS_DEF,
139            TokenTypes.METHOD_DEF,
140            TokenTypes.CTOR_DEF,
141            TokenTypes.LITERAL_FOR,
142            TokenTypes.LITERAL_WHILE,
143            TokenTypes.LITERAL_DO,
144            TokenTypes.STATIC_INIT,
145            TokenTypes.INSTANCE_INIT,
146            TokenTypes.ANNOTATION_DEF,
147            TokenTypes.ENUM_DEF,
148        };
149    }
150
151    @Override
152    public int[] getRequiredTokens() {
153        return CommonUtil.EMPTY_INT_ARRAY;
154    }
155
156    @Override
157    public void visitToken(DetailAST ast) {
158        final Details details = Details.getDetails(ast);
159        final DetailAST rcurly = details.rcurly;
160
161        if (rcurly != null) {
162            final String violation = validate(details);
163            if (!violation.isEmpty()) {
164                log(rcurly, violation, "}", rcurly.getColumnNo() + 1);
165            }
166        }
167    }
168
169    /**
170     * Does general validation.
171     * @param details for validation.
172     * @return violation message or empty string
173     *     if there was not violation during validation.
174     */
175    private String validate(Details details) {
176        String violation = "";
177        if (shouldHaveLineBreakBefore(option, details)) {
178            violation = MSG_KEY_LINE_BREAK_BEFORE;
179        }
180        else if (shouldBeOnSameLine(option, details)) {
181            violation = MSG_KEY_LINE_SAME;
182        }
183        else if (shouldBeAloneOnLine(option, details, getLine(details.rcurly.getLineNo() - 1))) {
184            violation = MSG_KEY_LINE_ALONE;
185        }
186        return violation;
187    }
188
189    /**
190     * Checks whether a right curly should have a line break before.
191     * @param bracePolicy option for placing the right curly brace.
192     * @param details details for validation.
193     * @return true if a right curly should have a line break before.
194     */
195    private static boolean shouldHaveLineBreakBefore(RightCurlyOption bracePolicy,
196                                                     Details details) {
197        return bracePolicy == RightCurlyOption.SAME
198                && !hasLineBreakBefore(details.rcurly)
199                && details.lcurly.getLineNo() != details.rcurly.getLineNo();
200    }
201
202    /**
203     * Checks that a right curly should be on the same line as the next statement.
204     * @param bracePolicy option for placing the right curly brace
205     * @param details Details for validation
206     * @return true if a right curly should be alone on a line.
207     */
208    private static boolean shouldBeOnSameLine(RightCurlyOption bracePolicy, Details details) {
209        return bracePolicy == RightCurlyOption.SAME
210                && !details.shouldCheckLastRcurly
211                && details.rcurly.getLineNo() != details.nextToken.getLineNo();
212    }
213
214    /**
215     * Checks that a right curly should be alone on a line.
216     * @param bracePolicy option for placing the right curly brace
217     * @param details Details for validation
218     * @param targetSrcLine A string with contents of rcurly's line
219     * @return true if a right curly should be alone on a line.
220     */
221    private static boolean shouldBeAloneOnLine(RightCurlyOption bracePolicy,
222                                               Details details,
223                                               String targetSrcLine) {
224        return bracePolicy == RightCurlyOption.ALONE
225                    && shouldBeAloneOnLineWithAloneOption(details, targetSrcLine)
226                || (bracePolicy == RightCurlyOption.ALONE_OR_SINGLELINE
227                    || details.shouldCheckLastRcurly)
228                    && shouldBeAloneOnLineWithNotAloneOption(details, targetSrcLine);
229    }
230
231    /**
232     * Whether right curly should be alone on line when ALONE option is used.
233     * @param details details for validation.
234     * @param targetSrcLine A string with contents of rcurly's line
235     * @return true, if right curly should be alone on line when ALONE option is used.
236     */
237    private static boolean shouldBeAloneOnLineWithAloneOption(Details details,
238                                                              String targetSrcLine) {
239        return !isAloneOnLine(details, targetSrcLine);
240    }
241
242    /**
243     * Whether right curly should be alone on line when ALONE_OR_SINGLELINE or SAME option is used.
244     * @param details details for validation.
245     * @param targetSrcLine A string with contents of rcurly's line
246     * @return true, if right curly should be alone on line
247     *         when ALONE_OR_SINGLELINE or SAME option is used.
248     */
249    private static boolean shouldBeAloneOnLineWithNotAloneOption(Details details,
250                                                                 String targetSrcLine) {
251        return shouldBeAloneOnLineWithAloneOption(details, targetSrcLine)
252                && !isBlockAloneOnSingleLine(details);
253    }
254
255    /**
256     * Checks whether right curly is alone on a line.
257     * @param details for validation.
258     * @param targetSrcLine A string with contents of rcurly's line
259     * @return true if right curly is alone on a line.
260     */
261    private static boolean isAloneOnLine(Details details, String targetSrcLine) {
262        final DetailAST rcurly = details.rcurly;
263        final DetailAST nextToken = details.nextToken;
264        return (rcurly.getLineNo() != nextToken.getLineNo() || skipDoubleBraceInstInit(details))
265                && CommonUtil.hasWhitespaceBefore(details.rcurly.getColumnNo(), targetSrcLine);
266    }
267
268    /**
269     * This method determines if the double brace initialization should be skipped over by the
270     * check. Double brace initializations are treated differently. The corresponding inner
271     * rcurly is treated as if it was alone on line even when it may be followed by another
272     * rcurly and a semi, raising no violations.
273     * <i>Please do note though that the line should not contain anything other than the following
274     * right curly and the semi following it or else violations will be raised.</i>
275     * Only the kind of double brace initializations shown in the following example code will be
276     * skipped over:<br>
277     * <pre>
278     *     {@code Map<String, String> map = new LinkedHashMap<>() {{
279     *           put("alpha", "man");
280     *       }}; // no violation}
281     * </pre>
282     *
283     * @param details {@link Details} object containing the details relevant to the rcurly
284     * @return if the double brace initialization rcurly should be skipped over by the check
285     */
286    private static boolean skipDoubleBraceInstInit(Details details) {
287        final DetailAST rcurly = details.rcurly;
288        final DetailAST tokenAfterNextToken = Details.getNextToken(details.nextToken);
289        return rcurly.getParent().getParent().getType() == TokenTypes.INSTANCE_INIT
290                && details.nextToken.getType() == TokenTypes.RCURLY
291                && rcurly.getLineNo() != Details.getNextToken(tokenAfterNextToken).getLineNo();
292    }
293
294    /**
295     * Checks whether block has a single-line format and is alone on a line.
296     * @param details for validation.
297     * @return true if block has single-line format and is alone on a line.
298     */
299    private static boolean isBlockAloneOnSingleLine(Details details) {
300        final DetailAST rcurly = details.rcurly;
301        final DetailAST lcurly = details.lcurly;
302        DetailAST nextToken = details.nextToken;
303        while (nextToken.getType() == TokenTypes.LITERAL_ELSE) {
304            nextToken = Details.getNextToken(nextToken);
305        }
306        if (nextToken.getType() == TokenTypes.DO_WHILE) {
307            final DetailAST doWhileSemi = nextToken.getParent().getLastChild();
308            nextToken = Details.getNextToken(doWhileSemi);
309        }
310        return rcurly.getLineNo() == lcurly.getLineNo()
311                && (rcurly.getLineNo() != nextToken.getLineNo()
312                || isRightcurlyFollowedBySemicolon(details));
313    }
314
315    /**
316     * Checks whether the right curly is followed by a semicolon.
317     * @param details details for validation.
318     * @return true if the right curly is followed by a semicolon.
319     */
320    private static boolean isRightcurlyFollowedBySemicolon(Details details) {
321        return details.nextToken.getType() == TokenTypes.SEMI;
322    }
323
324    /**
325     * Checks if right curly has line break before.
326     * @param rightCurly right curly token.
327     * @return true, if right curly has line break before.
328     */
329    private static boolean hasLineBreakBefore(DetailAST rightCurly) {
330        DetailAST previousToken = rightCurly.getPreviousSibling();
331        if (previousToken == null) {
332            previousToken = rightCurly.getParent();
333        }
334        return rightCurly.getLineNo() != previousToken.getLineNo();
335    }
336
337    /**
338     * Structure that contains all details for validation.
339     */
340    private static final class Details {
341
342        /**
343         * Token types that identify tokens that will never have SLIST in their AST.
344         */
345        private static final int[] TOKENS_WITH_NO_CHILD_SLIST = {
346            TokenTypes.CLASS_DEF,
347            TokenTypes.ENUM_DEF,
348            TokenTypes.ANNOTATION_DEF,
349        };
350
351        /** Right curly. */
352        private final DetailAST rcurly;
353        /** Left curly. */
354        private final DetailAST lcurly;
355        /** Next token. */
356        private final DetailAST nextToken;
357        /** Should check last right curly. */
358        private final boolean shouldCheckLastRcurly;
359
360        /**
361         * Constructor.
362         * @param lcurly the lcurly of the token whose details are being collected
363         * @param rcurly the rcurly of the token whose details are being collected
364         * @param nextToken the token after the token whose details are being collected
365         * @param shouldCheckLastRcurly boolean value to determine if to check last rcurly
366         */
367        private Details(DetailAST lcurly, DetailAST rcurly,
368                        DetailAST nextToken, boolean shouldCheckLastRcurly) {
369            this.lcurly = lcurly;
370            this.rcurly = rcurly;
371            this.nextToken = nextToken;
372            this.shouldCheckLastRcurly = shouldCheckLastRcurly;
373        }
374
375        /**
376         * Collects validation Details.
377         * @param ast a {@code DetailAST} value
378         * @return object containing all details to make a validation
379         */
380        private static Details getDetails(DetailAST ast) {
381            final Details details;
382            switch (ast.getType()) {
383                case TokenTypes.LITERAL_TRY:
384                case TokenTypes.LITERAL_CATCH:
385                case TokenTypes.LITERAL_FINALLY:
386                    details = getDetailsForTryCatchFinally(ast);
387                    break;
388                case TokenTypes.LITERAL_IF:
389                case TokenTypes.LITERAL_ELSE:
390                    details = getDetailsForIfElse(ast);
391                    break;
392                case TokenTypes.LITERAL_DO:
393                case TokenTypes.LITERAL_WHILE:
394                case TokenTypes.LITERAL_FOR:
395                    details = getDetailsForLoops(ast);
396                    break;
397                default:
398                    details = getDetailsForOthers(ast);
399                    break;
400            }
401            return details;
402        }
403
404        /**
405         * Collects validation details for LITERAL_TRY, LITERAL_CATCH, and LITERAL_FINALLY.
406         * @param ast a {@code DetailAST} value
407         * @return object containing all details to make a validation
408         */
409        private static Details getDetailsForTryCatchFinally(DetailAST ast) {
410            final DetailAST lcurly;
411            DetailAST nextToken;
412            final int tokenType = ast.getType();
413            if (tokenType == TokenTypes.LITERAL_TRY) {
414                if (ast.getFirstChild().getType() == TokenTypes.RESOURCE_SPECIFICATION) {
415                    lcurly = ast.getFirstChild().getNextSibling();
416                }
417                else {
418                    lcurly = ast.getFirstChild();
419                }
420                nextToken = lcurly.getNextSibling();
421            }
422            else {
423                nextToken = ast.getNextSibling();
424                lcurly = ast.getLastChild();
425            }
426
427            final boolean shouldCheckLastRcurly;
428            if (nextToken == null) {
429                shouldCheckLastRcurly = true;
430                nextToken = getNextToken(ast);
431            }
432            else {
433                shouldCheckLastRcurly = false;
434            }
435
436            final DetailAST rcurly = lcurly.getLastChild();
437            return new Details(lcurly, rcurly, nextToken, shouldCheckLastRcurly);
438        }
439
440        /**
441         * Collects validation details for LITERAL_IF and LITERAL_ELSE.
442         * @param ast a {@code DetailAST} value
443         * @return object containing all details to make a validation
444         */
445        private static Details getDetailsForIfElse(DetailAST ast) {
446            final boolean shouldCheckLastRcurly;
447            final DetailAST lcurly;
448            DetailAST nextToken = ast.findFirstToken(TokenTypes.LITERAL_ELSE);
449
450            if (nextToken == null) {
451                shouldCheckLastRcurly = true;
452                nextToken = getNextToken(ast);
453                lcurly = ast.getLastChild();
454            }
455            else {
456                shouldCheckLastRcurly = false;
457                lcurly = nextToken.getPreviousSibling();
458            }
459
460            DetailAST rcurly = null;
461            if (lcurly.getType() == TokenTypes.SLIST) {
462                rcurly = lcurly.getLastChild();
463            }
464            return new Details(lcurly, rcurly, nextToken, shouldCheckLastRcurly);
465        }
466
467        /**
468         * Collects validation details for CLASS_DEF, METHOD DEF, CTOR_DEF, STATIC_INIT,
469         * INSTANCE_INIT, ANNOTATION_DEF and ENUM_DEF.
470         * @param ast a {@code DetailAST} value
471         * @return an object containing all details to make a validation
472         */
473        private static Details getDetailsForOthers(DetailAST ast) {
474            DetailAST rcurly = null;
475            final DetailAST lcurly;
476            final int tokenType = ast.getType();
477            if (isTokenWithNoChildSlist(tokenType)) {
478                final DetailAST child = ast.getLastChild();
479                lcurly = child.getFirstChild();
480                rcurly = child.getLastChild();
481            }
482            else {
483                lcurly = ast.findFirstToken(TokenTypes.SLIST);
484                if (lcurly != null) {
485                    // SLIST could be absent if method is abstract
486                    rcurly = lcurly.getLastChild();
487                }
488            }
489            return new Details(lcurly, rcurly, getNextToken(ast), true);
490        }
491
492        /**
493         * Tests whether the provided tokenType will never have a SLIST as child in its AST.
494         * Like CLASS_DEF, ANNOTATION_DEF etc.
495         * @param tokenType the tokenType to test against.
496         * @return weather provided tokenType is definition token.
497         */
498        private static boolean isTokenWithNoChildSlist(int tokenType) {
499            return Arrays.stream(TOKENS_WITH_NO_CHILD_SLIST).anyMatch(token -> token == tokenType);
500        }
501
502        /**
503         * Collects validation details for loops' tokens.
504         * @param ast a {@code DetailAST} value
505         * @return an object containing all details to make a validation
506         */
507        private static Details getDetailsForLoops(DetailAST ast) {
508            DetailAST rcurly = null;
509            final DetailAST lcurly;
510            final DetailAST nextToken;
511            final int tokenType = ast.getType();
512            final boolean shouldCheckLastRcurly;
513            if (tokenType == TokenTypes.LITERAL_DO) {
514                shouldCheckLastRcurly = false;
515                nextToken = ast.findFirstToken(TokenTypes.DO_WHILE);
516                lcurly = ast.findFirstToken(TokenTypes.SLIST);
517                if (lcurly != null) {
518                    rcurly = lcurly.getLastChild();
519                }
520            }
521            else {
522                shouldCheckLastRcurly = true;
523                lcurly = ast.findFirstToken(TokenTypes.SLIST);
524                if (lcurly != null) {
525                    // SLIST could be absent in code like "while(true);"
526                    rcurly = lcurly.getLastChild();
527                }
528                nextToken = getNextToken(ast);
529            }
530            return new Details(lcurly, rcurly, nextToken, shouldCheckLastRcurly);
531        }
532
533        /**
534         * Finds next token after the given one.
535         * @param ast the given node.
536         * @return the token which represents next lexical item.
537         */
538        private static DetailAST getNextToken(DetailAST ast) {
539            DetailAST next = null;
540            DetailAST parent = ast;
541            while (next == null && parent != null) {
542                next = parent.getNextSibling();
543                parent = parent.getParent();
544            }
545            if (next == null) {
546                // a DetailAST object with DetailAST#NOT_INITIALIZED for line and column numbers
547                // that no 'actual' DetailAST objects can have.
548                next = new DetailAstImpl();
549            }
550            else {
551                next = CheckUtil.getFirstNode(next);
552            }
553            return next;
554        }
555
556    }
557
558}