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