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, switch statements, 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} for 165 * <a href="https://docs.oracle.com/javase/tutorial/java/nutsandbolts/switch.html"> 166 * Switch</a> Statements: 167 * </p> 168 * <pre> 169 * <module name="RightCurly"> 170 * <property name="option" value="alone"/> 171 * <property name="tokens" value="LITERAL_SWITCH"/> 172 * </module> 173 * </pre> 174 * <pre> 175 * class Test { 176 * 177 * public void method0() { 178 * int mode = 0; 179 * switch (mode) { 180 * case 1: 181 * int x = 1; 182 * break; 183 * default: 184 * x = 0; 185 * } // ok, RightCurly is alone 186 * } 187 * 188 * public void method0() { 189 * int mode = 0; 190 * switch (mode) { 191 * case 1: 192 * int x = 1; 193 * break; 194 * default: 195 * x = 0; } // violation, RightCurly should be alone on a line 196 * } 197 * 198 * } 199 * </pre> 200 * <p> 201 * To configure the check with policy {@code alone_or_singleline} for {@code if} and 202 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#METHOD_DEF"> 203 * METHOD_DEF</a> 204 * tokens: 205 * </p> 206 * <pre> 207 * <module name="RightCurly"> 208 * <property name="option" value="alone_or_singleline"/> 209 * <property name="tokens" value="LITERAL_IF, METHOD_DEF"/> 210 * </module> 211 * </pre> 212 * <p> 213 * Example: 214 * </p> 215 * <pre> 216 * public class Test { 217 * 218 * public void test() { 219 * 220 * if (foo) { 221 * bar(); 222 * } else { // violation, right curly must be alone on line 223 * bar(); 224 * } 225 * 226 * if (foo) { 227 * bar(); 228 * } // OK 229 * else { 230 * bar(); 231 * } 232 * 233 * try { 234 * bar(); 235 * } catch (Exception e) { // OK because config did not set token LITERAL_TRY 236 * bar(); 237 * } 238 * 239 * } // OK 240 * 241 * public void violate() { bar(); } // OK , because singleline 242 * } 243 * </pre> 244 * <p> 245 * To configure the check with policy {@code alone_or_singleline} for 246 * <a href="https://docs.oracle.com/javase/tutorial/java/nutsandbolts/switch.html"> 247 * Switch</a> 248 * Statements: 249 * </p> 250 * <pre> 251 * <module name="RightCurly"> 252 * <property name="option" value="alone_or_singleline"/> 253 * <property name="tokens" value="LITERAL_SWITCH"/> 254 * </module> 255 * </pre> 256 * <pre> 257 * class Test { 258 * 259 * public void method0() { 260 * int mode = 0; 261 * switch (mode) { 262 * case 1: 263 * int x = 1; 264 * break; 265 * default: 266 * x = 0; 267 * } // ok 268 * } 269 * 270 * public static void method7() { 271 * int mode = 0; 272 * int x; 273 * switch (mode) { case 1: x = 5; } // ok, RightCurly is on the same line as LeftCurly 274 * } 275 * 276 * public void method() { 277 * int mode = 0; 278 * int x; 279 * switch (mode) { 280 * case 1: 281 * x = 1; } // violation, right curly should be alone on line 282 * } 283 * } 284 * </pre> 285 * <p> 286 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 287 * </p> 288 * <p> 289 * Violation Message Keys: 290 * </p> 291 * <ul> 292 * <li> 293 * {@code line.alone} 294 * </li> 295 * <li> 296 * {@code line.break.before} 297 * </li> 298 * <li> 299 * {@code line.same} 300 * </li> 301 * </ul> 302 * 303 * @since 3.0 304 */ 305@StatelessCheck 306public class RightCurlyCheck extends AbstractCheck { 307 308 /** 309 * A key is pointing to the warning message text in "messages.properties" 310 * file. 311 */ 312 public static final String MSG_KEY_LINE_BREAK_BEFORE = "line.break.before"; 313 314 /** 315 * A key is pointing to the warning message text in "messages.properties" 316 * file. 317 */ 318 public static final String MSG_KEY_LINE_ALONE = "line.alone"; 319 320 /** 321 * A key is pointing to the warning message text in "messages.properties" 322 * file. 323 */ 324 public static final String MSG_KEY_LINE_SAME = "line.same"; 325 326 /** 327 * Specify the policy on placement of a right curly brace (<code>'}'</code>). 328 */ 329 private RightCurlyOption option = RightCurlyOption.SAME; 330 331 /** 332 * Setter to specify the policy on placement of a right curly brace (<code>'}'</code>). 333 * 334 * @param optionStr string to decode option from 335 * @throws IllegalArgumentException if unable to decode 336 */ 337 public void setOption(String optionStr) { 338 option = RightCurlyOption.valueOf(optionStr.trim().toUpperCase(Locale.ENGLISH)); 339 } 340 341 @Override 342 public int[] getDefaultTokens() { 343 return new int[] { 344 TokenTypes.LITERAL_TRY, 345 TokenTypes.LITERAL_CATCH, 346 TokenTypes.LITERAL_FINALLY, 347 TokenTypes.LITERAL_IF, 348 TokenTypes.LITERAL_ELSE, 349 }; 350 } 351 352 @Override 353 public int[] getAcceptableTokens() { 354 return new int[] { 355 TokenTypes.LITERAL_TRY, 356 TokenTypes.LITERAL_CATCH, 357 TokenTypes.LITERAL_FINALLY, 358 TokenTypes.LITERAL_IF, 359 TokenTypes.LITERAL_ELSE, 360 TokenTypes.CLASS_DEF, 361 TokenTypes.METHOD_DEF, 362 TokenTypes.CTOR_DEF, 363 TokenTypes.LITERAL_FOR, 364 TokenTypes.LITERAL_WHILE, 365 TokenTypes.LITERAL_DO, 366 TokenTypes.STATIC_INIT, 367 TokenTypes.INSTANCE_INIT, 368 TokenTypes.ANNOTATION_DEF, 369 TokenTypes.ENUM_DEF, 370 TokenTypes.INTERFACE_DEF, 371 TokenTypes.RECORD_DEF, 372 TokenTypes.COMPACT_CTOR_DEF, 373 TokenTypes.LITERAL_SWITCH, 374 }; 375 } 376 377 @Override 378 public int[] getRequiredTokens() { 379 return CommonUtil.EMPTY_INT_ARRAY; 380 } 381 382 @Override 383 public void visitToken(DetailAST ast) { 384 final Details details = Details.getDetails(ast); 385 final DetailAST rcurly = details.rcurly; 386 387 if (rcurly != null) { 388 final String violation = validate(details); 389 if (!violation.isEmpty()) { 390 log(rcurly, violation, "}", rcurly.getColumnNo() + 1); 391 } 392 } 393 } 394 395 /** 396 * Does general validation. 397 * 398 * @param details for validation. 399 * @return violation message or empty string 400 * if there was no violation during validation. 401 */ 402 private String validate(Details details) { 403 String violation = ""; 404 if (shouldHaveLineBreakBefore(option, details)) { 405 violation = MSG_KEY_LINE_BREAK_BEFORE; 406 } 407 else if (shouldBeOnSameLine(option, details)) { 408 violation = MSG_KEY_LINE_SAME; 409 } 410 else if (shouldBeAloneOnLine(option, details, getLine(details.rcurly.getLineNo() - 1))) { 411 violation = MSG_KEY_LINE_ALONE; 412 } 413 return violation; 414 } 415 416 /** 417 * Checks whether a right curly should have a line break before. 418 * 419 * @param bracePolicy option for placing the right curly brace. 420 * @param details details for validation. 421 * @return true if a right curly should have a line break before. 422 */ 423 private static boolean shouldHaveLineBreakBefore(RightCurlyOption bracePolicy, 424 Details details) { 425 return bracePolicy == RightCurlyOption.SAME 426 && !hasLineBreakBefore(details.rcurly) 427 && !TokenUtil.areOnSameLine(details.lcurly, details.rcurly); 428 } 429 430 /** 431 * Checks that a right curly should be on the same line as the next statement. 432 * 433 * @param bracePolicy option for placing the right curly brace 434 * @param details Details for validation 435 * @return true if a right curly should be alone on a line. 436 */ 437 private static boolean shouldBeOnSameLine(RightCurlyOption bracePolicy, Details details) { 438 return bracePolicy == RightCurlyOption.SAME 439 && !details.shouldCheckLastRcurly 440 && !TokenUtil.areOnSameLine(details.rcurly, details.nextToken); 441 } 442 443 /** 444 * Checks that a right curly should be alone on a line. 445 * 446 * @param bracePolicy option for placing the right curly brace 447 * @param details Details for validation 448 * @param targetSrcLine A string with contents of rcurly's line 449 * @return true if a right curly should be alone on a line. 450 */ 451 private static boolean shouldBeAloneOnLine(RightCurlyOption bracePolicy, 452 Details details, 453 String targetSrcLine) { 454 return bracePolicy == RightCurlyOption.ALONE 455 && shouldBeAloneOnLineWithAloneOption(details, targetSrcLine) 456 || (bracePolicy == RightCurlyOption.ALONE_OR_SINGLELINE 457 || details.shouldCheckLastRcurly) 458 && shouldBeAloneOnLineWithNotAloneOption(details, targetSrcLine); 459 } 460 461 /** 462 * Whether right curly should be alone on line when ALONE option is used. 463 * 464 * @param details details for validation. 465 * @param targetSrcLine A string with contents of rcurly's line 466 * @return true, if right curly should be alone on line when ALONE option is used. 467 */ 468 private static boolean shouldBeAloneOnLineWithAloneOption(Details details, 469 String targetSrcLine) { 470 return !isAloneOnLine(details, targetSrcLine); 471 } 472 473 /** 474 * Whether right curly should be alone on line when ALONE_OR_SINGLELINE or SAME option is used. 475 * 476 * @param details details for validation. 477 * @param targetSrcLine A string with contents of rcurly's line 478 * @return true, if right curly should be alone on line 479 * when ALONE_OR_SINGLELINE or SAME option is used. 480 */ 481 private static boolean shouldBeAloneOnLineWithNotAloneOption(Details details, 482 String targetSrcLine) { 483 return shouldBeAloneOnLineWithAloneOption(details, targetSrcLine) 484 && !isBlockAloneOnSingleLine(details); 485 } 486 487 /** 488 * Checks whether right curly is alone on a line. 489 * 490 * @param details for validation. 491 * @param targetSrcLine A string with contents of rcurly's line 492 * @return true if right curly is alone on a line. 493 */ 494 private static boolean isAloneOnLine(Details details, String targetSrcLine) { 495 final DetailAST rcurly = details.rcurly; 496 final DetailAST nextToken = details.nextToken; 497 return (nextToken == null || !TokenUtil.areOnSameLine(rcurly, nextToken) 498 || skipDoubleBraceInstInit(details)) 499 && CommonUtil.hasWhitespaceBefore(details.rcurly.getColumnNo(), 500 targetSrcLine); 501 } 502 503 /** 504 * This method determines if the double brace initialization should be skipped over by the 505 * check. Double brace initializations are treated differently. The corresponding inner 506 * rcurly is treated as if it was alone on line even when it may be followed by another 507 * rcurly and a semi, raising no violations. 508 * <i>Please do note though that the line should not contain anything other than the following 509 * right curly and the semi following it or else violations will be raised.</i> 510 * Only the kind of double brace initializations shown in the following example code will be 511 * skipped over:<br> 512 * <pre> 513 * {@code Map<String, String> map = new LinkedHashMap<>() {{ 514 * put("alpha", "man"); 515 * }}; // no violation} 516 * </pre> 517 * 518 * @param details {@link Details} object containing the details relevant to the rcurly 519 * @return if the double brace initialization rcurly should be skipped over by the check 520 */ 521 private static boolean skipDoubleBraceInstInit(Details details) { 522 boolean skipDoubleBraceInstInit = false; 523 final DetailAST tokenAfterNextToken = Details.getNextToken(details.nextToken); 524 if (tokenAfterNextToken != null) { 525 final DetailAST rcurly = details.rcurly; 526 skipDoubleBraceInstInit = rcurly.getParent().getParent() 527 .getType() == TokenTypes.INSTANCE_INIT 528 && details.nextToken.getType() == TokenTypes.RCURLY 529 && !TokenUtil.areOnSameLine(rcurly, Details.getNextToken(tokenAfterNextToken)); 530 } 531 return skipDoubleBraceInstInit; 532 } 533 534 /** 535 * Checks whether block has a single-line format and is alone on a line. 536 * 537 * @param details for validation. 538 * @return true if block has single-line format and is alone on a line. 539 */ 540 private static boolean isBlockAloneOnSingleLine(Details details) { 541 DetailAST nextToken = details.nextToken; 542 543 while (nextToken != null && nextToken.getType() == TokenTypes.LITERAL_ELSE) { 544 nextToken = Details.getNextToken(nextToken); 545 } 546 547 if (nextToken != null && nextToken.getType() == TokenTypes.DO_WHILE) { 548 final DetailAST doWhileSemi = nextToken.getParent(); 549 nextToken = Details.getNextToken(doWhileSemi); 550 } 551 552 return TokenUtil.areOnSameLine(details.lcurly, details.rcurly) 553 && (nextToken == null || !TokenUtil.areOnSameLine(details.rcurly, nextToken) 554 || isRightcurlyFollowedBySemicolon(details)); 555 } 556 557 /** 558 * Checks whether the right curly is followed by a semicolon. 559 * 560 * @param details details for validation. 561 * @return true if the right curly is followed by a semicolon. 562 */ 563 private static boolean isRightcurlyFollowedBySemicolon(Details details) { 564 return details.nextToken.getType() == TokenTypes.SEMI; 565 } 566 567 /** 568 * Checks if right curly has line break before. 569 * 570 * @param rightCurly right curly token. 571 * @return true, if right curly has line break before. 572 */ 573 private static boolean hasLineBreakBefore(DetailAST rightCurly) { 574 DetailAST previousToken = rightCurly.getPreviousSibling(); 575 if (previousToken == null) { 576 previousToken = rightCurly.getParent(); 577 } 578 return !TokenUtil.areOnSameLine(rightCurly, previousToken); 579 } 580 581 /** 582 * Structure that contains all details for validation. 583 */ 584 private static final class Details { 585 586 /** 587 * Token types that identify tokens that will never have SLIST in their AST. 588 */ 589 private static final int[] TOKENS_WITH_NO_CHILD_SLIST = { 590 TokenTypes.CLASS_DEF, 591 TokenTypes.ENUM_DEF, 592 TokenTypes.ANNOTATION_DEF, 593 TokenTypes.INTERFACE_DEF, 594 TokenTypes.RECORD_DEF, 595 }; 596 597 /** Right curly. */ 598 private final DetailAST rcurly; 599 /** Left curly. */ 600 private final DetailAST lcurly; 601 /** Next token. */ 602 private final DetailAST nextToken; 603 /** Should check last right curly. */ 604 private final boolean shouldCheckLastRcurly; 605 606 /** 607 * Constructor. 608 * 609 * @param lcurly the lcurly of the token whose details are being collected 610 * @param rcurly the rcurly of the token whose details are being collected 611 * @param nextToken the token after the token whose details are being collected 612 * @param shouldCheckLastRcurly boolean value to determine if to check last rcurly 613 */ 614 private Details(DetailAST lcurly, DetailAST rcurly, 615 DetailAST nextToken, boolean shouldCheckLastRcurly) { 616 this.lcurly = lcurly; 617 this.rcurly = rcurly; 618 this.nextToken = nextToken; 619 this.shouldCheckLastRcurly = shouldCheckLastRcurly; 620 } 621 622 /** 623 * Collects validation Details. 624 * 625 * @param ast a {@code DetailAST} value 626 * @return object containing all details to make a validation 627 */ 628 private static Details getDetails(DetailAST ast) { 629 final Details details; 630 switch (ast.getType()) { 631 case TokenTypes.LITERAL_TRY: 632 case TokenTypes.LITERAL_CATCH: 633 details = getDetailsForTryCatch(ast); 634 break; 635 case TokenTypes.LITERAL_IF: 636 details = getDetailsForIf(ast); 637 break; 638 case TokenTypes.LITERAL_DO: 639 details = getDetailsForDoLoops(ast); 640 break; 641 case TokenTypes.LITERAL_SWITCH: 642 details = getDetailsForSwitch(ast); 643 break; 644 default: 645 details = getDetailsForOthers(ast); 646 break; 647 } 648 return details; 649 } 650 651 /** 652 * Collects details about switch statements and expressions. 653 * 654 * @param switchNode switch statement or expression to gather details about 655 * @return new Details about given switch statement or expression 656 */ 657 private static Details getDetailsForSwitch(DetailAST switchNode) { 658 final DetailAST lcurly = switchNode.findFirstToken(TokenTypes.LCURLY); 659 final DetailAST rcurly; 660 DetailAST nextToken = null; 661 // skipping switch expression as check only handles statements 662 if (isSwitchExpression(switchNode)) { 663 rcurly = null; 664 } 665 else { 666 rcurly = switchNode.getLastChild(); 667 nextToken = getNextToken(switchNode); 668 } 669 return new Details(lcurly, rcurly, nextToken, true); 670 } 671 672 /** 673 * Check whether switch is expression or not. 674 * 675 * @param switchNode switch statement or expression to provide detail 676 * @return true if it is a switch expression 677 */ 678 private static boolean isSwitchExpression(DetailAST switchNode) { 679 DetailAST currentNode = switchNode; 680 boolean ans = false; 681 682 while (currentNode != null) { 683 if (currentNode.getType() == TokenTypes.EXPR) { 684 ans = true; 685 } 686 currentNode = currentNode.getParent(); 687 } 688 return ans; 689 } 690 691 /** 692 * Collects validation details for LITERAL_TRY, and LITERAL_CATCH. 693 * 694 * @param ast a {@code DetailAST} value 695 * @return object containing all details to make a validation 696 */ 697 private static Details getDetailsForTryCatch(DetailAST ast) { 698 final DetailAST lcurly; 699 DetailAST nextToken; 700 final int tokenType = ast.getType(); 701 if (tokenType == TokenTypes.LITERAL_TRY) { 702 if (ast.getFirstChild().getType() == TokenTypes.RESOURCE_SPECIFICATION) { 703 lcurly = ast.getFirstChild().getNextSibling(); 704 } 705 else { 706 lcurly = ast.getFirstChild(); 707 } 708 nextToken = lcurly.getNextSibling(); 709 } 710 else { 711 nextToken = ast.getNextSibling(); 712 lcurly = ast.getLastChild(); 713 } 714 715 final boolean shouldCheckLastRcurly; 716 if (nextToken == null) { 717 shouldCheckLastRcurly = true; 718 nextToken = getNextToken(ast); 719 } 720 else { 721 shouldCheckLastRcurly = false; 722 } 723 724 final DetailAST rcurly = lcurly.getLastChild(); 725 return new Details(lcurly, rcurly, nextToken, shouldCheckLastRcurly); 726 } 727 728 /** 729 * Collects validation details for LITERAL_IF. 730 * 731 * @param ast a {@code DetailAST} value 732 * @return object containing all details to make a validation 733 */ 734 private static Details getDetailsForIf(DetailAST ast) { 735 final boolean shouldCheckLastRcurly; 736 final DetailAST lcurly; 737 DetailAST nextToken = ast.findFirstToken(TokenTypes.LITERAL_ELSE); 738 739 if (nextToken == null) { 740 shouldCheckLastRcurly = true; 741 nextToken = getNextToken(ast); 742 lcurly = ast.getLastChild(); 743 } 744 else { 745 shouldCheckLastRcurly = false; 746 lcurly = nextToken.getPreviousSibling(); 747 } 748 749 DetailAST rcurly = null; 750 if (lcurly.getType() == TokenTypes.SLIST) { 751 rcurly = lcurly.getLastChild(); 752 } 753 return new Details(lcurly, rcurly, nextToken, shouldCheckLastRcurly); 754 } 755 756 /** 757 * Collects validation details for CLASS_DEF, RECORD_DEF, METHOD DEF, CTOR_DEF, STATIC_INIT, 758 * INSTANCE_INIT, ANNOTATION_DEF, ENUM_DEF, and COMPACT_CTOR_DEF. 759 * 760 * @param ast a {@code DetailAST} value 761 * @return an object containing all details to make a validation 762 */ 763 private static Details getDetailsForOthers(DetailAST ast) { 764 DetailAST rcurly = null; 765 final DetailAST lcurly; 766 final int tokenType = ast.getType(); 767 if (isTokenWithNoChildSlist(tokenType)) { 768 final DetailAST child = ast.getLastChild(); 769 lcurly = child; 770 rcurly = child.getLastChild(); 771 } 772 else { 773 lcurly = ast.findFirstToken(TokenTypes.SLIST); 774 if (lcurly != null) { 775 // SLIST could be absent if method is abstract 776 rcurly = lcurly.getLastChild(); 777 } 778 } 779 return new Details(lcurly, rcurly, getNextToken(ast), true); 780 } 781 782 /** 783 * Tests whether the provided tokenType will never have a SLIST as child in its AST. 784 * Like CLASS_DEF, ANNOTATION_DEF etc. 785 * 786 * @param tokenType the tokenType to test against. 787 * @return weather provided tokenType is definition token. 788 */ 789 private static boolean isTokenWithNoChildSlist(int tokenType) { 790 return Arrays.stream(TOKENS_WITH_NO_CHILD_SLIST).anyMatch(token -> token == tokenType); 791 } 792 793 /** 794 * Collects validation details for LITERAL_DO loops' tokens. 795 * 796 * @param ast a {@code DetailAST} value 797 * @return an object containing all details to make a validation 798 */ 799 private static Details getDetailsForDoLoops(DetailAST ast) { 800 final DetailAST lcurly = ast.findFirstToken(TokenTypes.SLIST); 801 final DetailAST nextToken = ast.findFirstToken(TokenTypes.DO_WHILE); 802 DetailAST rcurly = null; 803 if (lcurly != null) { 804 rcurly = lcurly.getLastChild(); 805 } 806 return new Details(lcurly, rcurly, nextToken, false); 807 } 808 809 /** 810 * Finds next token after the given one. 811 * 812 * @param ast the given node. 813 * @return the token which represents next lexical item. 814 */ 815 private static DetailAST getNextToken(DetailAST ast) { 816 DetailAST next = null; 817 DetailAST parent = ast; 818 while (next == null && parent != null) { 819 next = parent.getNextSibling(); 820 parent = parent.getParent(); 821 } 822 return next; 823 } 824 } 825}