001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2026 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.utils; 021 022import java.nio.file.Path; 023import java.util.ArrayList; 024import java.util.Arrays; 025import java.util.Collection; 026import java.util.HashSet; 027import java.util.List; 028import java.util.Optional; 029import java.util.Set; 030import java.util.function.Predicate; 031import java.util.regex.Pattern; 032import java.util.stream.Collectors; 033import java.util.stream.Stream; 034 035import com.puppycrawl.tools.checkstyle.api.DetailAST; 036import com.puppycrawl.tools.checkstyle.api.FullIdent; 037import com.puppycrawl.tools.checkstyle.api.TokenTypes; 038import com.puppycrawl.tools.checkstyle.checks.naming.AccessModifierOption; 039 040/** 041 * Contains utility methods for the checks. 042 * 043 */ 044public final class CheckUtil { 045 046 // constants for parseDouble() 047 /** Binary radix. */ 048 private static final int BASE_2 = 2; 049 050 /** Octal radix. */ 051 private static final int BASE_8 = 8; 052 053 /** Decimal radix. */ 054 private static final int BASE_10 = 10; 055 056 /** Hex radix. */ 057 private static final int BASE_16 = 16; 058 059 /** Pattern matching underscore characters ('_'). */ 060 private static final Pattern UNDERSCORE_PATTERN = Pattern.compile("_"); 061 062 /** Compiled pattern for all system newlines. */ 063 private static final Pattern ALL_NEW_LINES = Pattern.compile("\\R"); 064 065 /** Package separator. */ 066 private static final char PACKAGE_SEPARATOR = '.'; 067 068 /** Prevent instances. */ 069 private CheckUtil() { 070 } 071 072 /** 073 * Tests whether a method definition AST defines an equals covariant. 074 * 075 * @param ast the method definition AST to test. 076 * Precondition: ast is a TokenTypes.METHOD_DEF node. 077 * @return true if ast defines an equals covariant. 078 */ 079 public static boolean isEqualsMethod(DetailAST ast) { 080 boolean equalsMethod = false; 081 082 if (ast.getType() == TokenTypes.METHOD_DEF) { 083 final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS); 084 final boolean staticOrAbstract = 085 modifiers.findFirstToken(TokenTypes.LITERAL_STATIC) != null 086 || modifiers.findFirstToken(TokenTypes.ABSTRACT) != null; 087 088 if (!staticOrAbstract) { 089 final DetailAST nameNode = ast.findFirstToken(TokenTypes.IDENT); 090 final String name = nameNode.getText(); 091 092 if ("equals".equals(name)) { 093 // one parameter? 094 final DetailAST paramsNode = ast.findFirstToken(TokenTypes.PARAMETERS); 095 equalsMethod = paramsNode.getChildCount() == 1; 096 } 097 } 098 } 099 return equalsMethod; 100 } 101 102 /** 103 * Returns the value represented by the specified string of the specified 104 * type. Returns 0 for types other than float, double, int, and long. 105 * 106 * @param text the string to be parsed. 107 * @param type the token type of the text. Should be a constant of 108 * {@link TokenTypes}. 109 * @return the double value represented by the string argument. 110 */ 111 public static double parseDouble(String text, int type) { 112 String txt = UNDERSCORE_PATTERN.matcher(text).replaceAll(""); 113 114 return switch (type) { 115 case TokenTypes.NUM_FLOAT, TokenTypes.NUM_DOUBLE -> Double.parseDouble(txt); 116 117 case TokenTypes.NUM_INT, TokenTypes.NUM_LONG -> { 118 int radix = BASE_10; 119 if (txt.startsWith("0x") || txt.startsWith("0X")) { 120 radix = BASE_16; 121 txt = txt.substring(2); 122 } 123 else if (txt.startsWith("0b") || txt.startsWith("0B")) { 124 radix = BASE_2; 125 txt = txt.substring(2); 126 } 127 else if (txt.startsWith("0")) { 128 radix = BASE_8; 129 } 130 yield parseNumber(txt, radix, type); 131 } 132 133 default -> Double.NaN; 134 }; 135 } 136 137 /** 138 * Parses the string argument as an integer or a long in the radix specified by 139 * the second argument. The characters in the string must all be digits of 140 * the specified radix. 141 * 142 * @param text the String containing the integer representation to be 143 * parsed. Precondition: text contains a parsable int. 144 * @param radix the radix to be used while parsing text. 145 * @param type the token type of the text. Should be a constant of 146 * {@link TokenTypes}. 147 * @return the number represented by the string argument in the specified radix. 148 */ 149 private static double parseNumber(final String text, final int radix, final int type) { 150 String txt = text; 151 if (txt.endsWith("L") || txt.endsWith("l")) { 152 txt = txt.substring(0, txt.length() - 1); 153 } 154 final double result; 155 156 final boolean negative = txt.charAt(0) == '-'; 157 if (type == TokenTypes.NUM_INT) { 158 if (negative) { 159 result = Integer.parseInt(txt, radix); 160 } 161 else { 162 result = Integer.parseUnsignedInt(txt, radix); 163 } 164 } 165 else { 166 if (negative) { 167 result = Long.parseLong(txt, radix); 168 } 169 else { 170 result = Long.parseUnsignedLong(txt, radix); 171 } 172 } 173 174 return result; 175 } 176 177 /** 178 * Finds sub-node for given node minimal (line, column) pair. 179 * 180 * @param node the root of tree for search. 181 * @return sub-node with minimal (line, column) pair. 182 */ 183 public static DetailAST getFirstNode(final DetailAST node) { 184 DetailAST currentNode = node; 185 DetailAST child = node.getFirstChild(); 186 while (child != null) { 187 final DetailAST newNode = getFirstNode(child); 188 if (isBeforeInSource(newNode, currentNode)) { 189 currentNode = newNode; 190 } 191 child = child.getNextSibling(); 192 } 193 194 return currentNode; 195 } 196 197 /** 198 * Retrieves whether ast1 is located before ast2. 199 * 200 * @param ast1 the first node. 201 * @param ast2 the second node. 202 * @return true, if ast1 is located before ast2. 203 */ 204 public static boolean isBeforeInSource(DetailAST ast1, DetailAST ast2) { 205 return ast1.getLineNo() < ast2.getLineNo() 206 || TokenUtil.areOnSameLine(ast1, ast2) 207 && ast1.getColumnNo() < ast2.getColumnNo(); 208 } 209 210 /** 211 * Retrieves the names of the type parameters to the node. 212 * 213 * @param node the parameterized AST node 214 * @return a list of type parameter names 215 */ 216 public static List<String> getTypeParameterNames(final DetailAST node) { 217 final DetailAST typeParameters = 218 node.findFirstToken(TokenTypes.TYPE_PARAMETERS); 219 220 final List<String> typeParameterNames = new ArrayList<>(); 221 if (typeParameters != null) { 222 final DetailAST typeParam = 223 typeParameters.findFirstToken(TokenTypes.TYPE_PARAMETER); 224 typeParameterNames.add( 225 typeParam.findFirstToken(TokenTypes.IDENT).getText()); 226 227 DetailAST sibling = typeParam.getNextSibling(); 228 while (sibling != null) { 229 if (sibling.getType() == TokenTypes.TYPE_PARAMETER) { 230 typeParameterNames.add( 231 sibling.findFirstToken(TokenTypes.IDENT).getText()); 232 } 233 sibling = sibling.getNextSibling(); 234 } 235 } 236 237 return typeParameterNames; 238 } 239 240 /** 241 * Retrieves the type parameters to the node. 242 * 243 * @param node the parameterized AST node 244 * @return a list of type parameter names 245 */ 246 public static List<DetailAST> getTypeParameters(final DetailAST node) { 247 final DetailAST typeParameters = 248 node.findFirstToken(TokenTypes.TYPE_PARAMETERS); 249 250 final List<DetailAST> typeParams = new ArrayList<>(); 251 if (typeParameters != null) { 252 final DetailAST typeParam = 253 typeParameters.findFirstToken(TokenTypes.TYPE_PARAMETER); 254 typeParams.add(typeParam); 255 256 DetailAST sibling = typeParam.getNextSibling(); 257 while (sibling != null) { 258 if (sibling.getType() == TokenTypes.TYPE_PARAMETER) { 259 typeParams.add(sibling); 260 } 261 sibling = sibling.getNextSibling(); 262 } 263 } 264 265 return typeParams; 266 } 267 268 /** 269 * Checks whether a method is a not void one. 270 * 271 * @param methodDefAst the method node. 272 * @return true if method is a not void one. 273 */ 274 public static boolean isNonVoidMethod(DetailAST methodDefAst) { 275 boolean returnValue = false; 276 if (methodDefAst.getType() == TokenTypes.METHOD_DEF) { 277 final DetailAST typeAST = methodDefAst.findFirstToken(TokenTypes.TYPE); 278 if (typeAST.findFirstToken(TokenTypes.LITERAL_VOID) == null) { 279 returnValue = true; 280 } 281 } 282 return returnValue; 283 } 284 285 /** 286 * Checks whether a parameter is a receiver. 287 * 288 * <p>A receiver parameter is a special parameter that 289 * represents the object for which the method is invoked. 290 * It is denoted by the reserved keyword {@code this} 291 * in the method declaration. Check 292 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#PARAMETER_DEF"> 293 * PARAMETER_DEF</a> 294 * </p> 295 * 296 * @param parameterDefAst the parameter node. 297 * @return true if the parameter is a receiver. 298 * @see <a href="https://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.4.1"> 299 * ReceiverParameter</a> 300 */ 301 public static boolean isReceiverParameter(DetailAST parameterDefAst) { 302 return parameterDefAst.findFirstToken(TokenTypes.IDENT) == null; 303 } 304 305 /** 306 * Returns the access modifier of the method/constructor at the specified AST. If 307 * the method is in an interface or annotation block, the access modifier is assumed 308 * to be public. 309 * 310 * @param ast the token of the method/constructor. 311 * @return the access modifier of the method/constructor. 312 */ 313 public static AccessModifierOption getAccessModifierFromModifiersToken(DetailAST ast) { 314 AccessModifierOption accessModifier; 315 if (ast.getType() == TokenTypes.ENUM_CONSTANT_DEF) { 316 accessModifier = AccessModifierOption.PUBLIC; 317 } 318 else { 319 final DetailAST modsToken = ast.findFirstToken(TokenTypes.MODIFIERS); 320 accessModifier = getAccessModifierFromModifiersTokenDirectly(modsToken); 321 } 322 323 if (accessModifier == AccessModifierOption.PACKAGE) { 324 if (ScopeUtil.isInEnumBlock(ast) && ast.getType() == TokenTypes.CTOR_DEF) { 325 accessModifier = AccessModifierOption.PRIVATE; 326 } 327 else if (ScopeUtil.isInInterfaceOrAnnotationBlock(ast)) { 328 accessModifier = AccessModifierOption.PUBLIC; 329 } 330 } 331 332 return accessModifier; 333 } 334 335 /** 336 * Returns {@link AccessModifierOption} based on the information about access modifier 337 * taken from the given token of type {@link TokenTypes#MODIFIERS}. 338 * 339 * @param modifiersToken token of type {@link TokenTypes#MODIFIERS}. 340 * @return {@link AccessModifierOption}. 341 * @throws IllegalArgumentException when expected non-null modifiersToken with type 'MODIFIERS' 342 */ 343 private static AccessModifierOption getAccessModifierFromModifiersTokenDirectly( 344 DetailAST modifiersToken) { 345 if (modifiersToken == null) { 346 throw new IllegalArgumentException("expected non-null AST-token with type 'MODIFIERS'"); 347 } 348 349 AccessModifierOption accessModifier = AccessModifierOption.PACKAGE; 350 for (DetailAST token = modifiersToken.getFirstChild(); token != null; 351 token = token.getNextSibling()) { 352 final int tokenType = token.getType(); 353 if (tokenType == TokenTypes.LITERAL_PUBLIC) { 354 accessModifier = AccessModifierOption.PUBLIC; 355 } 356 else if (tokenType == TokenTypes.LITERAL_PROTECTED) { 357 accessModifier = AccessModifierOption.PROTECTED; 358 } 359 else if (tokenType == TokenTypes.LITERAL_PRIVATE) { 360 accessModifier = AccessModifierOption.PRIVATE; 361 } 362 } 363 return accessModifier; 364 } 365 366 /** 367 * Returns the access modifier of the surrounding "block". 368 * 369 * @param node the node to return the access modifier for 370 * @return the access modifier of the surrounding block 371 */ 372 public static Optional<AccessModifierOption> getSurroundingAccessModifier(DetailAST node) { 373 Optional<AccessModifierOption> returnValue = Optional.empty(); 374 for (DetailAST token = node; 375 returnValue.isEmpty() && !TokenUtil.isRootNode(token); 376 token = token.getParent()) { 377 final int type = token.getType(); 378 if (type == TokenTypes.CLASS_DEF 379 || type == TokenTypes.INTERFACE_DEF 380 || type == TokenTypes.ANNOTATION_DEF 381 || type == TokenTypes.ENUM_DEF) { 382 returnValue = Optional.ofNullable(getAccessModifierFromModifiersToken(token)); 383 } 384 else if (type == TokenTypes.LITERAL_NEW) { 385 break; 386 } 387 } 388 389 return returnValue; 390 } 391 392 /** 393 * Create set of class names and short class names. 394 * 395 * @param classNames array of class names. 396 * @return set of class names and short class names. 397 */ 398 public static Set<String> parseClassNames(String... classNames) { 399 return Arrays.stream(classNames) 400 .flatMap(className -> Stream.of(className, CommonUtil.baseClassName(className))) 401 .filter(Predicate.not(String::isEmpty)) 402 .collect(Collectors.toUnmodifiableSet()); 403 } 404 405 /** 406 * Strip initial newline and preceding whitespace on each line from text block content. 407 * In order to be consistent with how javac handles this task, we have modeled this 408 * implementation after the code from: 409 * github.com/openjdk/jdk14u/blob/master/src/java.base/share/classes/java/lang/String.java 410 * 411 * @param textBlockContent the actual content of the text block. 412 * @return string consistent with javac representation. 413 */ 414 public static String stripIndentAndInitialNewLineFromTextBlock(String textBlockContent) { 415 final String contentWithInitialNewLineRemoved = 416 ALL_NEW_LINES.matcher(textBlockContent).replaceFirst(""); 417 final List<String> lines = 418 Arrays.asList(ALL_NEW_LINES.split(contentWithInitialNewLineRemoved)); 419 final int indent = getSmallestIndent(lines); 420 final String suffix = ""; 421 422 return lines.stream() 423 .map(line -> stripIndentAndTrailingWhitespaceFromLine(line, indent)) 424 .collect(Collectors.joining(System.lineSeparator(), suffix, suffix)); 425 } 426 427 /** 428 * Helper method for stripIndentAndInitialNewLineFromTextBlock, strips correct indent 429 * from string, and trailing whitespace, or returns empty string if no text. 430 * 431 * @param line the string to strip indent and trailing whitespace from 432 * @param indent the amount of indent to remove 433 * @return modified string with removed indent and trailing whitespace, or empty string. 434 */ 435 private static String stripIndentAndTrailingWhitespaceFromLine(String line, int indent) { 436 final int lastNonWhitespace = lastIndexOfNonWhitespace(line); 437 String returnString = ""; 438 if (lastNonWhitespace > 0) { 439 returnString = line.substring(indent, lastNonWhitespace); 440 } 441 return returnString; 442 } 443 444 /** 445 * Helper method for stripIndentAndInitialNewLineFromTextBlock, to determine the smallest 446 * indent in a text block string literal. 447 * 448 * @param lines collection of actual text block content, split by line. 449 * @return number of spaces representing the smallest indent in this text block. 450 */ 451 private static int getSmallestIndent(Collection<String> lines) { 452 return lines.stream() 453 .mapToInt(CommonUtil::indexOfNonWhitespace) 454 .min() 455 .orElse(0); 456 } 457 458 /** 459 * Helper method to find the index of the last non-whitespace character in a string. 460 * 461 * @param line the string to find the last index of a non-whitespace character for. 462 * @return the index of the last non-whitespace character. 463 */ 464 private static int lastIndexOfNonWhitespace(String line) { 465 int length; 466 for (length = line.length(); length > 0; length--) { 467 if (!Character.isWhitespace(line.charAt(length - 1))) { 468 break; 469 } 470 } 471 return length; 472 } 473 474 /** 475 * Calculates and returns the type declaration name matching count. 476 * 477 * <p> 478 * Suppose our pattern class is {@code foo.a.b} and class to be matched is 479 * {@code foo.a.ball} then type declaration name matching count would be calculated by 480 * comparing every character, and updating main counter when we hit "." to prevent matching 481 * "a.b" with "a.ball". In this case type declaration name matching count 482 * would be equal to 6 and not 7 (b of ball is not counted). 483 * </p> 484 * 485 * @param patternClass class against which the given class has to be matched 486 * @param classToBeMatched class to be matched 487 * @return class name matching count 488 */ 489 public static int typeDeclarationNameMatchingCount(String patternClass, 490 String classToBeMatched) { 491 final int length = Math.min(classToBeMatched.length(), patternClass.length()); 492 int result = 0; 493 for (int i = 0; i < length && patternClass.charAt(i) == classToBeMatched.charAt(i); ++i) { 494 if (patternClass.charAt(i) == PACKAGE_SEPARATOR) { 495 result = i; 496 } 497 } 498 return result; 499 } 500 501 /** 502 * Get the qualified name of type declaration by combining {@code packageName}, 503 * {@code outerClassQualifiedName} and {@code className}. 504 * 505 * @param packageName packageName 506 * @param outerClassQualifiedName outerClassQualifiedName 507 * @param className className 508 * @return the qualified name of type declaration by combining {@code packageName}, 509 * {@code outerClassQualifiedName} and {@code className} 510 */ 511 public static String getQualifiedTypeDeclarationName(String packageName, 512 String outerClassQualifiedName, 513 String className) { 514 final String qualifiedClassName; 515 516 if (outerClassQualifiedName == null) { 517 if (packageName == null) { 518 qualifiedClassName = className; 519 } 520 else { 521 qualifiedClassName = packageName + PACKAGE_SEPARATOR + className; 522 } 523 } 524 else { 525 qualifiedClassName = outerClassQualifiedName + PACKAGE_SEPARATOR + className; 526 } 527 return qualifiedClassName; 528 } 529 530 /** 531 * Get name of package and super class of anon inner class by concatenating 532 * the identifier values under {@link TokenTypes#DOT}. 533 * 534 * @param ast ast to extract superclass or package name from 535 * @return qualified name 536 */ 537 public static String extractQualifiedName(DetailAST ast) { 538 return FullIdent.createFullIdent(ast).getText(); 539 } 540 541 /** 542 * Get the short name of super class of anonymous inner class. 543 * Example: 544 * <pre> 545 * TestClass.NestedClass obj = new Test().new NestedClass() {}; 546 * // Short name will be Test.NestedClass 547 * </pre> 548 * 549 * @param literalNewAst ast node of type {@link TokenTypes#LITERAL_NEW} 550 * @return short name of base class of anonymous inner class 551 */ 552 public static String getShortNameOfAnonInnerClass(DetailAST literalNewAst) { 553 DetailAST parentAst = literalNewAst; 554 while (TokenUtil.isOfType(parentAst, TokenTypes.LITERAL_NEW, TokenTypes.DOT)) { 555 parentAst = parentAst.getParent(); 556 } 557 final DetailAST firstChild = parentAst.getFirstChild(); 558 return extractQualifiedName(firstChild); 559 } 560 561 /** 562 * Checks if the given file path is a package-info.java file. 563 * 564 * @param filePath path to the file. 565 * @return true if the package file. 566 */ 567 public static boolean isPackageInfo(String filePath) { 568 final Path filename = Path.of(filePath).getFileName(); 569 return filename != null && "package-info.java".equals(filename.toString()); 570 } 571 572 /** 573 * Checks if a given subtree is terminated by return, throw, break, continue, or yield. 574 * 575 * @param ast root of given subtree 576 * @return true if the subtree is terminated. 577 */ 578 public static boolean isTerminated(final DetailAST ast) { 579 return isTerminated(ast, true, true, new HashSet<>()); 580 } 581 582 /** 583 * Checks if a given subtree terminated by return, throw, yield or, 584 * if allowed break, continue. 585 * When analyzing fall-through cases in switch statements, a Set of String labels 586 * is used to keep track of the labels encountered in the enclosing switch statements. 587 * 588 * @param ast root of given subtree 589 * @param useBreak should we consider break as terminator 590 * @param useContinue should we consider continue as terminator 591 * @param labelsForCurrentSwitchScope the Set labels for the current scope of the switch 592 * @return true if the subtree is terminated. 593 */ 594 private static boolean isTerminated(final DetailAST ast, boolean useBreak, boolean useContinue, 595 Set<String> labelsForCurrentSwitchScope) { 596 597 return switch (ast.getType()) { 598 case TokenTypes.LITERAL_RETURN, TokenTypes.LITERAL_YIELD, 599 TokenTypes.LITERAL_THROW -> true; 600 case TokenTypes.LITERAL_BREAK -> useBreak 601 || hasLabel(ast, labelsForCurrentSwitchScope); 602 case TokenTypes.LITERAL_CONTINUE -> useContinue 603 || hasLabel(ast, labelsForCurrentSwitchScope); 604 case TokenTypes.SLIST -> checkSlist(ast, useBreak, useContinue, 605 labelsForCurrentSwitchScope); 606 case TokenTypes.LITERAL_IF -> checkIf(ast, useBreak, useContinue, 607 labelsForCurrentSwitchScope); 608 case TokenTypes.LITERAL_FOR, TokenTypes.LITERAL_WHILE, TokenTypes.LITERAL_DO -> 609 checkLoop(ast, labelsForCurrentSwitchScope); 610 case TokenTypes.LITERAL_TRY -> checkTry(ast, useBreak, useContinue, 611 labelsForCurrentSwitchScope); 612 case TokenTypes.LITERAL_SWITCH -> checkSwitch(ast, useContinue, 613 labelsForCurrentSwitchScope); 614 case TokenTypes.LITERAL_SYNCHRONIZED -> 615 checkSynchronized(ast, useBreak, useContinue, 616 labelsForCurrentSwitchScope); 617 case TokenTypes.LABELED_STAT -> { 618 labelsForCurrentSwitchScope.add(ast.getFirstChild().getText()); 619 yield isTerminated(ast.getLastChild(), useBreak, useContinue, 620 labelsForCurrentSwitchScope); 621 } 622 default -> false; 623 }; 624 } 625 626 /** 627 * Checks if given break or continue ast has outer label. 628 * 629 * @param statement break or continue node 630 * @param labelsForCurrentSwitchScope the Set labels for the current scope of the switch 631 * @return true if local label used 632 */ 633 private static boolean hasLabel(DetailAST statement, Set<String> labelsForCurrentSwitchScope) { 634 return Optional.ofNullable(statement) 635 .map(DetailAST::getFirstChild) 636 .filter(child -> child.getType() == TokenTypes.IDENT) 637 .map(DetailAST::getText) 638 .filter(label -> !labelsForCurrentSwitchScope.contains(label)) 639 .isPresent(); 640 } 641 642 /** 643 * Checks if a given SLIST terminated by return, throw or, 644 * if allowed break, continue. 645 * 646 * @param slistAst SLIST to check 647 * @param useBreak should we consider break as terminator 648 * @param useContinue should we consider continue as terminator 649 * @param labels label names 650 * @return true if SLIST is terminated. 651 */ 652 private static boolean checkSlist(final DetailAST slistAst, boolean useBreak, 653 boolean useContinue, Set<String> labels) { 654 DetailAST lastStmt = slistAst.getLastChild(); 655 656 if (lastStmt.getType() == TokenTypes.RCURLY) { 657 lastStmt = lastStmt.getPreviousSibling(); 658 } 659 660 while (TokenUtil.isOfType(lastStmt, TokenTypes.SINGLE_LINE_COMMENT, 661 TokenTypes.BLOCK_COMMENT_BEGIN)) { 662 lastStmt = lastStmt.getPreviousSibling(); 663 } 664 665 return lastStmt != null 666 && isTerminated(lastStmt, useBreak, useContinue, labels); 667 } 668 669 /** 670 * Checks if a given IF terminated by return, throw or, 671 * if allowed break, continue. 672 * 673 * @param ast IF to check 674 * @param useBreak should we consider break as terminator 675 * @param useContinue should we consider continue as terminator 676 * @param labels label names 677 * @return true if IF is terminated. 678 */ 679 private static boolean checkIf(final DetailAST ast, boolean useBreak, 680 boolean useContinue, Set<String> labels) { 681 final DetailAST thenStmt = getNextNonCommentAst(ast.findFirstToken(TokenTypes.RPAREN)); 682 683 final DetailAST elseStmt = getNextNonCommentAst(thenStmt); 684 685 return elseStmt != null 686 && isTerminated(thenStmt, useBreak, useContinue, labels) 687 && isTerminated(elseStmt.getLastChild(), useBreak, useContinue, labels); 688 } 689 690 /** 691 * This method will skip the comment content while finding the next ast of current ast. 692 * 693 * @param ast current ast 694 * @return next ast after skipping comment 695 */ 696 public static DetailAST getNextNonCommentAst(DetailAST ast) { 697 DetailAST nextSibling = ast.getNextSibling(); 698 while (TokenUtil.isOfType(nextSibling, TokenTypes.SINGLE_LINE_COMMENT, 699 TokenTypes.BLOCK_COMMENT_BEGIN)) { 700 nextSibling = nextSibling.getNextSibling(); 701 } 702 return nextSibling; 703 } 704 705 /** 706 * Checks if a given loop terminated by return, throw or, 707 * if allowed break, continue. 708 * 709 * @param ast loop to check 710 * @param labels label names 711 * @return true if loop is terminated. 712 */ 713 private static boolean checkLoop(final DetailAST ast, Set<String> labels) { 714 final DetailAST loopBody; 715 if (ast.getType() == TokenTypes.LITERAL_DO) { 716 final DetailAST lparen = ast.findFirstToken(TokenTypes.DO_WHILE); 717 loopBody = lparen.getPreviousSibling(); 718 } 719 else { 720 final DetailAST rparen = ast.findFirstToken(TokenTypes.RPAREN); 721 loopBody = rparen.getNextSibling(); 722 } 723 return isTerminated(loopBody, false, false, labels); 724 } 725 726 /** 727 * Checks if a given try/catch/finally block terminated by return, throw or, 728 * if allowed break, continue. 729 * 730 * @param ast loop to check 731 * @param useBreak should we consider break as terminator 732 * @param useContinue should we consider continue as terminator 733 * @param labels label names 734 * @return true if try/catch/finally block is terminated 735 */ 736 private static boolean checkTry(final DetailAST ast, boolean useBreak, 737 boolean useContinue, Set<String> labels) { 738 final DetailAST finalStmt = ast.getLastChild(); 739 boolean isTerminated = finalStmt.getType() == TokenTypes.LITERAL_FINALLY 740 && isTerminated(finalStmt.findFirstToken(TokenTypes.SLIST), 741 useBreak, useContinue, labels); 742 743 if (!isTerminated) { 744 DetailAST firstChild = ast.getFirstChild(); 745 746 if (firstChild.getType() == TokenTypes.RESOURCE_SPECIFICATION) { 747 firstChild = firstChild.getNextSibling(); 748 } 749 750 isTerminated = isTerminated(firstChild, 751 useBreak, useContinue, labels); 752 753 DetailAST catchStmt = ast.findFirstToken(TokenTypes.LITERAL_CATCH); 754 while (catchStmt != null 755 && isTerminated 756 && catchStmt.getType() == TokenTypes.LITERAL_CATCH) { 757 final DetailAST catchBody = 758 catchStmt.findFirstToken(TokenTypes.SLIST); 759 isTerminated = isTerminated(catchBody, useBreak, useContinue, labels); 760 catchStmt = catchStmt.getNextSibling(); 761 } 762 } 763 return isTerminated; 764 } 765 766 /** 767 * Checks if a given switch terminated by return, throw or, 768 * if allowed break, continue. 769 * 770 * @param literalSwitchAst loop to check 771 * @param useContinue should we consider continue as terminator 772 * @param labels label names 773 * @return true if switch is terminated 774 */ 775 private static boolean checkSwitch(DetailAST literalSwitchAst, 776 boolean useContinue, Set<String> labels) { 777 DetailAST caseGroup = literalSwitchAst.findFirstToken(TokenTypes.CASE_GROUP); 778 boolean isTerminated = caseGroup != null; 779 while (isTerminated && caseGroup.getType() != TokenTypes.RCURLY) { 780 final DetailAST caseBody = 781 caseGroup.findFirstToken(TokenTypes.SLIST); 782 isTerminated = caseBody != null 783 && isTerminated(caseBody, false, useContinue, labels); 784 caseGroup = caseGroup.getNextSibling(); 785 } 786 return isTerminated; 787 } 788 789 /** 790 * Checks if a given synchronized block terminated by return, throw or, 791 * if allowed break, continue. 792 * 793 * @param synchronizedAst synchronized block to check. 794 * @param useBreak should we consider break as terminator 795 * @param useContinue should we consider continue as terminator 796 * @param labels label names 797 * @return true if synchronized block is terminated 798 */ 799 private static boolean checkSynchronized(final DetailAST synchronizedAst, boolean useBreak, 800 boolean useContinue, Set<String> labels) { 801 return isTerminated( 802 synchronizedAst.findFirstToken(TokenTypes.SLIST), useBreak, useContinue, labels); 803 } 804}