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