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