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