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 * <module name="RightCurly"/> 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 * <module name="RightCurly"> 126 * <property name="option" value="alone"/> 127 * <property name="tokens" value="LITERAL_ELSE, METHOD_DEF"/> 128 * </module> 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 * <module name="RightCurly"> 171 * <property name="option" value="alone_or_singleline"/> 172 * <property name="tokens" value="LITERAL_IF, METHOD_DEF"/> 173 * </module> 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}