001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2021 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 not 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        if (nextToken != null) {
464            while (nextToken.getType() == TokenTypes.LITERAL_ELSE) {
465                nextToken = Details.getNextToken(nextToken);
466            }
467
468            if (nextToken.getType() == TokenTypes.DO_WHILE) {
469                final DetailAST doWhileSemi = nextToken.getParent().getLastChild();
470                nextToken = Details.getNextToken(doWhileSemi);
471            }
472        }
473
474        return TokenUtil.areOnSameLine(details.lcurly, details.rcurly)
475            && (nextToken == null || !TokenUtil.areOnSameLine(details.rcurly, nextToken)
476                || isRightcurlyFollowedBySemicolon(details));
477    }
478
479    /**
480     * Checks whether the right curly is followed by a semicolon.
481     *
482     * @param details details for validation.
483     * @return true if the right curly is followed by a semicolon.
484     */
485    private static boolean isRightcurlyFollowedBySemicolon(Details details) {
486        return details.nextToken.getType() == TokenTypes.SEMI;
487    }
488
489    /**
490     * Checks if right curly has line break before.
491     *
492     * @param rightCurly right curly token.
493     * @return true, if right curly has line break before.
494     */
495    private static boolean hasLineBreakBefore(DetailAST rightCurly) {
496        DetailAST previousToken = rightCurly.getPreviousSibling();
497        if (previousToken == null) {
498            previousToken = rightCurly.getParent();
499        }
500        return !TokenUtil.areOnSameLine(rightCurly, previousToken);
501    }
502
503    /**
504     * Structure that contains all details for validation.
505     */
506    private static final class Details {
507
508        /**
509         * Token types that identify tokens that will never have SLIST in their AST.
510         */
511        private static final int[] TOKENS_WITH_NO_CHILD_SLIST = {
512            TokenTypes.CLASS_DEF,
513            TokenTypes.ENUM_DEF,
514            TokenTypes.ANNOTATION_DEF,
515            TokenTypes.INTERFACE_DEF,
516            TokenTypes.RECORD_DEF,
517        };
518
519        /** Right curly. */
520        private final DetailAST rcurly;
521        /** Left curly. */
522        private final DetailAST lcurly;
523        /** Next token. */
524        private final DetailAST nextToken;
525        /** Should check last right curly. */
526        private final boolean shouldCheckLastRcurly;
527
528        /**
529         * Constructor.
530         *
531         * @param lcurly the lcurly of the token whose details are being collected
532         * @param rcurly the rcurly of the token whose details are being collected
533         * @param nextToken the token after the token whose details are being collected
534         * @param shouldCheckLastRcurly boolean value to determine if to check last rcurly
535         */
536        private Details(DetailAST lcurly, DetailAST rcurly,
537                        DetailAST nextToken, boolean shouldCheckLastRcurly) {
538            this.lcurly = lcurly;
539            this.rcurly = rcurly;
540            this.nextToken = nextToken;
541            this.shouldCheckLastRcurly = shouldCheckLastRcurly;
542        }
543
544        /**
545         * Collects validation Details.
546         *
547         * @param ast a {@code DetailAST} value
548         * @return object containing all details to make a validation
549         */
550        private static Details getDetails(DetailAST ast) {
551            final Details details;
552            switch (ast.getType()) {
553                case TokenTypes.LITERAL_TRY:
554                case TokenTypes.LITERAL_CATCH:
555                case TokenTypes.LITERAL_FINALLY:
556                    details = getDetailsForTryCatchFinally(ast);
557                    break;
558                case TokenTypes.LITERAL_IF:
559                case TokenTypes.LITERAL_ELSE:
560                    details = getDetailsForIfElse(ast);
561                    break;
562                case TokenTypes.LITERAL_DO:
563                case TokenTypes.LITERAL_WHILE:
564                case TokenTypes.LITERAL_FOR:
565                    details = getDetailsForLoops(ast);
566                    break;
567                default:
568                    details = getDetailsForOthers(ast);
569                    break;
570            }
571            return details;
572        }
573
574        /**
575         * Collects validation details for LITERAL_TRY, LITERAL_CATCH, and LITERAL_FINALLY.
576         *
577         * @param ast a {@code DetailAST} value
578         * @return object containing all details to make a validation
579         */
580        private static Details getDetailsForTryCatchFinally(DetailAST ast) {
581            final DetailAST lcurly;
582            DetailAST nextToken;
583            final int tokenType = ast.getType();
584            if (tokenType == TokenTypes.LITERAL_TRY) {
585                if (ast.getFirstChild().getType() == TokenTypes.RESOURCE_SPECIFICATION) {
586                    lcurly = ast.getFirstChild().getNextSibling();
587                }
588                else {
589                    lcurly = ast.getFirstChild();
590                }
591                nextToken = lcurly.getNextSibling();
592            }
593            else {
594                nextToken = ast.getNextSibling();
595                lcurly = ast.getLastChild();
596            }
597
598            final boolean shouldCheckLastRcurly;
599            if (nextToken == null) {
600                shouldCheckLastRcurly = true;
601                nextToken = getNextToken(ast);
602            }
603            else {
604                shouldCheckLastRcurly = false;
605            }
606
607            final DetailAST rcurly = lcurly.getLastChild();
608            return new Details(lcurly, rcurly, nextToken, shouldCheckLastRcurly);
609        }
610
611        /**
612         * Collects validation details for LITERAL_IF and LITERAL_ELSE.
613         *
614         * @param ast a {@code DetailAST} value
615         * @return object containing all details to make a validation
616         */
617        private static Details getDetailsForIfElse(DetailAST ast) {
618            final boolean shouldCheckLastRcurly;
619            final DetailAST lcurly;
620            DetailAST nextToken = ast.findFirstToken(TokenTypes.LITERAL_ELSE);
621
622            if (nextToken == null) {
623                shouldCheckLastRcurly = true;
624                nextToken = getNextToken(ast);
625                lcurly = ast.getLastChild();
626            }
627            else {
628                shouldCheckLastRcurly = false;
629                lcurly = nextToken.getPreviousSibling();
630            }
631
632            DetailAST rcurly = null;
633            if (lcurly.getType() == TokenTypes.SLIST) {
634                rcurly = lcurly.getLastChild();
635            }
636            return new Details(lcurly, rcurly, nextToken, shouldCheckLastRcurly);
637        }
638
639        /**
640         * Collects validation details for CLASS_DEF, RECORD_DEF, METHOD DEF, CTOR_DEF, STATIC_INIT,
641         * INSTANCE_INIT, ANNOTATION_DEF, ENUM_DEF, and COMPACT_CTOR_DEF.
642         *
643         * @param ast a {@code DetailAST} value
644         * @return an object containing all details to make a validation
645         */
646        private static Details getDetailsForOthers(DetailAST ast) {
647            DetailAST rcurly = null;
648            final DetailAST lcurly;
649            final int tokenType = ast.getType();
650            if (isTokenWithNoChildSlist(tokenType)) {
651                final DetailAST child = ast.getLastChild();
652                lcurly = child.getFirstChild();
653                rcurly = child.getLastChild();
654            }
655            else {
656                lcurly = ast.findFirstToken(TokenTypes.SLIST);
657                if (lcurly != null) {
658                    // SLIST could be absent if method is abstract
659                    rcurly = lcurly.getLastChild();
660                }
661            }
662            return new Details(lcurly, rcurly, getNextToken(ast), true);
663        }
664
665        /**
666         * Tests whether the provided tokenType will never have a SLIST as child in its AST.
667         * Like CLASS_DEF, ANNOTATION_DEF etc.
668         *
669         * @param tokenType the tokenType to test against.
670         * @return weather provided tokenType is definition token.
671         */
672        private static boolean isTokenWithNoChildSlist(int tokenType) {
673            return Arrays.stream(TOKENS_WITH_NO_CHILD_SLIST).anyMatch(token -> token == tokenType);
674        }
675
676        /**
677         * Collects validation details for loops' tokens.
678         *
679         * @param ast a {@code DetailAST} value
680         * @return an object containing all details to make a validation
681         */
682        private static Details getDetailsForLoops(DetailAST ast) {
683            DetailAST rcurly = null;
684            final DetailAST lcurly;
685            final DetailAST nextToken;
686            final int tokenType = ast.getType();
687            final boolean shouldCheckLastRcurly;
688            if (tokenType == TokenTypes.LITERAL_DO) {
689                shouldCheckLastRcurly = false;
690                nextToken = ast.findFirstToken(TokenTypes.DO_WHILE);
691                lcurly = ast.findFirstToken(TokenTypes.SLIST);
692                if (lcurly != null) {
693                    rcurly = lcurly.getLastChild();
694                }
695            }
696            else {
697                shouldCheckLastRcurly = true;
698                lcurly = ast.findFirstToken(TokenTypes.SLIST);
699                if (lcurly != null) {
700                    // SLIST could be absent in code like "while(true);"
701                    rcurly = lcurly.getLastChild();
702                }
703                nextToken = getNextToken(ast);
704            }
705            return new Details(lcurly, rcurly, nextToken, shouldCheckLastRcurly);
706        }
707
708        /**
709         * Finds next token after the given one.
710         *
711         * @param ast the given node.
712         * @return the token which represents next lexical item.
713         */
714        private static DetailAST getNextToken(DetailAST ast) {
715            DetailAST next = null;
716            DetailAST parent = ast;
717            while (next == null && parent != null) {
718                next = parent.getNextSibling();
719                parent = parent.getParent();
720            }
721            return next;
722        }
723    }
724}