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