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