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.utils; 021 022import java.io.File; 023import java.util.ArrayList; 024import java.util.Arrays; 025import java.util.Collection; 026import java.util.List; 027import java.util.Set; 028import java.util.function.Predicate; 029import java.util.regex.Pattern; 030import java.util.stream.Collectors; 031import java.util.stream.Stream; 032 033import com.puppycrawl.tools.checkstyle.api.DetailAST; 034import com.puppycrawl.tools.checkstyle.api.FullIdent; 035import com.puppycrawl.tools.checkstyle.api.TokenTypes; 036import com.puppycrawl.tools.checkstyle.checks.naming.AccessModifierOption; 037 038/** 039 * Contains utility methods for the checks. 040 * 041 */ 042public final class CheckUtil { 043 044 // constants for parseDouble() 045 /** Binary radix. */ 046 private static final int BASE_2 = 2; 047 048 /** Octal radix. */ 049 private static final int BASE_8 = 8; 050 051 /** Decimal radix. */ 052 private static final int BASE_10 = 10; 053 054 /** Hex radix. */ 055 private static final int BASE_16 = 16; 056 057 /** Maximum children allowed in setter/getter. */ 058 private static final int SETTER_GETTER_MAX_CHILDREN = 7; 059 060 /** Maximum nodes allowed in a body of setter. */ 061 private static final int SETTER_BODY_SIZE = 3; 062 063 /** Maximum nodes allowed in a body of getter. */ 064 private static final int GETTER_BODY_SIZE = 2; 065 066 /** Pattern matching underscore characters ('_'). */ 067 private static final Pattern UNDERSCORE_PATTERN = Pattern.compile("_"); 068 069 /** Pattern matching names of setter methods. */ 070 private static final Pattern SETTER_PATTERN = Pattern.compile("^set[A-Z].*"); 071 072 /** Pattern matching names of getter methods. */ 073 private static final Pattern GETTER_PATTERN = Pattern.compile("^(is|get)[A-Z].*"); 074 075 /** Compiled pattern for all system newlines. */ 076 private static final Pattern ALL_NEW_LINES = Pattern.compile("\\R"); 077 078 /** Package separator. */ 079 private static final char PACKAGE_SEPARATOR = '.'; 080 081 /** Prevent instances. */ 082 private CheckUtil() { 083 } 084 085 /** 086 * Tests whether a method definition AST defines an equals covariant. 087 * 088 * @param ast the method definition AST to test. 089 * Precondition: ast is a TokenTypes.METHOD_DEF node. 090 * @return true if ast defines an equals covariant. 091 */ 092 public static boolean isEqualsMethod(DetailAST ast) { 093 boolean equalsMethod = false; 094 095 if (ast.getType() == TokenTypes.METHOD_DEF) { 096 final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS); 097 final boolean staticOrAbstract = 098 modifiers.findFirstToken(TokenTypes.LITERAL_STATIC) != null 099 || modifiers.findFirstToken(TokenTypes.ABSTRACT) != null; 100 101 if (!staticOrAbstract) { 102 final DetailAST nameNode = ast.findFirstToken(TokenTypes.IDENT); 103 final String name = nameNode.getText(); 104 105 if ("equals".equals(name)) { 106 // one parameter? 107 final DetailAST paramsNode = ast.findFirstToken(TokenTypes.PARAMETERS); 108 equalsMethod = paramsNode.getChildCount() == 1; 109 } 110 } 111 } 112 return equalsMethod; 113 } 114 115 /** 116 * Returns whether a token represents an ELSE as part of an ELSE / IF set. 117 * 118 * @param ast the token to check 119 * @return whether it is 120 */ 121 public static boolean isElseIf(DetailAST ast) { 122 final DetailAST parentAST = ast.getParent(); 123 124 return ast.getType() == TokenTypes.LITERAL_IF 125 && (isElse(parentAST) || isElseWithCurlyBraces(parentAST)); 126 } 127 128 /** 129 * Returns whether a token represents an ELSE. 130 * 131 * @param ast the token to check 132 * @return whether the token represents an ELSE 133 */ 134 private static boolean isElse(DetailAST ast) { 135 return ast.getType() == TokenTypes.LITERAL_ELSE; 136 } 137 138 /** 139 * Returns whether a token represents an SLIST as part of an ELSE 140 * statement. 141 * 142 * @param ast the token to check 143 * @return whether the toke does represent an SLIST as part of an ELSE 144 */ 145 private static boolean isElseWithCurlyBraces(DetailAST ast) { 146 return ast.getType() == TokenTypes.SLIST 147 && ast.getChildCount() == 2 148 && isElse(ast.getParent()); 149 } 150 151 /** 152 * Returns the value represented by the specified string of the specified 153 * type. Returns 0 for types other than float, double, int, and long. 154 * 155 * @param text the string to be parsed. 156 * @param type the token type of the text. Should be a constant of 157 * {@link TokenTypes}. 158 * @return the double value represented by the string argument. 159 */ 160 public static double parseDouble(String text, int type) { 161 String txt = UNDERSCORE_PATTERN.matcher(text).replaceAll(""); 162 final double result; 163 switch (type) { 164 case TokenTypes.NUM_FLOAT: 165 case TokenTypes.NUM_DOUBLE: 166 result = Double.parseDouble(txt); 167 break; 168 case TokenTypes.NUM_INT: 169 case TokenTypes.NUM_LONG: 170 int radix = BASE_10; 171 if (txt.startsWith("0x") || txt.startsWith("0X")) { 172 radix = BASE_16; 173 txt = txt.substring(2); 174 } 175 else if (txt.startsWith("0b") || txt.startsWith("0B")) { 176 radix = BASE_2; 177 txt = txt.substring(2); 178 } 179 else if (CommonUtil.startsWithChar(txt, '0')) { 180 radix = BASE_8; 181 txt = txt.substring(1); 182 } 183 result = parseNumber(txt, radix, type); 184 break; 185 default: 186 result = Double.NaN; 187 break; 188 } 189 return result; 190 } 191 192 /** 193 * Parses the string argument as an integer or a long in the radix specified by 194 * the second argument. The characters in the string must all be digits of 195 * the specified radix. 196 * 197 * @param text the String containing the integer representation to be 198 * parsed. Precondition: text contains a parsable int. 199 * @param radix the radix to be used while parsing text. 200 * @param type the token type of the text. Should be a constant of 201 * {@link TokenTypes}. 202 * @return the number represented by the string argument in the specified radix. 203 */ 204 private static double parseNumber(final String text, final int radix, final int type) { 205 String txt = text; 206 if (CommonUtil.endsWithChar(txt, 'L') || CommonUtil.endsWithChar(txt, 'l')) { 207 txt = txt.substring(0, txt.length() - 1); 208 } 209 final double result; 210 if (txt.isEmpty()) { 211 result = 0.0; 212 } 213 else { 214 final boolean negative = txt.charAt(0) == '-'; 215 if (type == TokenTypes.NUM_INT) { 216 if (negative) { 217 result = Integer.parseInt(txt, radix); 218 } 219 else { 220 result = Integer.parseUnsignedInt(txt, radix); 221 } 222 } 223 else { 224 if (negative) { 225 result = Long.parseLong(txt, radix); 226 } 227 else { 228 result = Long.parseUnsignedLong(txt, radix); 229 } 230 } 231 } 232 return result; 233 } 234 235 /** 236 * Finds sub-node for given node minimal (line, column) pair. 237 * 238 * @param node the root of tree for search. 239 * @return sub-node with minimal (line, column) pair. 240 */ 241 public static DetailAST getFirstNode(final DetailAST node) { 242 DetailAST currentNode = node; 243 DetailAST child = node.getFirstChild(); 244 while (child != null) { 245 final DetailAST newNode = getFirstNode(child); 246 if (isBeforeInSource(newNode, currentNode)) { 247 currentNode = newNode; 248 } 249 child = child.getNextSibling(); 250 } 251 252 return currentNode; 253 } 254 255 /** 256 * Retrieves whether ast1 is located before ast2. 257 * 258 * @param ast1 the first node. 259 * @param ast2 the second node. 260 * @return true, if ast1 is located before ast2. 261 */ 262 public static boolean isBeforeInSource(DetailAST ast1, DetailAST ast2) { 263 return ast1.getLineNo() < ast2.getLineNo() 264 || TokenUtil.areOnSameLine(ast1, ast2) 265 && ast1.getColumnNo() < ast2.getColumnNo(); 266 } 267 268 /** 269 * Retrieves the names of the type parameters to the node. 270 * 271 * @param node the parameterized AST node 272 * @return a list of type parameter names 273 */ 274 public static List<String> getTypeParameterNames(final DetailAST node) { 275 final DetailAST typeParameters = 276 node.findFirstToken(TokenTypes.TYPE_PARAMETERS); 277 278 final List<String> typeParameterNames = new ArrayList<>(); 279 if (typeParameters != null) { 280 final DetailAST typeParam = 281 typeParameters.findFirstToken(TokenTypes.TYPE_PARAMETER); 282 typeParameterNames.add( 283 typeParam.findFirstToken(TokenTypes.IDENT).getText()); 284 285 DetailAST sibling = typeParam.getNextSibling(); 286 while (sibling != null) { 287 if (sibling.getType() == TokenTypes.TYPE_PARAMETER) { 288 typeParameterNames.add( 289 sibling.findFirstToken(TokenTypes.IDENT).getText()); 290 } 291 sibling = sibling.getNextSibling(); 292 } 293 } 294 295 return typeParameterNames; 296 } 297 298 /** 299 * Retrieves the type parameters to the node. 300 * 301 * @param node the parameterized AST node 302 * @return a list of type parameter names 303 */ 304 public static List<DetailAST> getTypeParameters(final DetailAST node) { 305 final DetailAST typeParameters = 306 node.findFirstToken(TokenTypes.TYPE_PARAMETERS); 307 308 final List<DetailAST> typeParams = new ArrayList<>(); 309 if (typeParameters != null) { 310 final DetailAST typeParam = 311 typeParameters.findFirstToken(TokenTypes.TYPE_PARAMETER); 312 typeParams.add(typeParam); 313 314 DetailAST sibling = typeParam.getNextSibling(); 315 while (sibling != null) { 316 if (sibling.getType() == TokenTypes.TYPE_PARAMETER) { 317 typeParams.add(sibling); 318 } 319 sibling = sibling.getNextSibling(); 320 } 321 } 322 323 return typeParams; 324 } 325 326 /** 327 * Returns whether an AST represents a setter method. 328 * 329 * @param ast the AST to check with 330 * @return whether the AST represents a setter method 331 */ 332 public static boolean isSetterMethod(final DetailAST ast) { 333 boolean setterMethod = false; 334 335 // Check have a method with exactly 7 children which are all that 336 // is allowed in a proper setter method which does not throw any 337 // exceptions. 338 if (ast.getType() == TokenTypes.METHOD_DEF 339 && ast.getChildCount() == SETTER_GETTER_MAX_CHILDREN) { 340 final DetailAST type = ast.findFirstToken(TokenTypes.TYPE); 341 final String name = type.getNextSibling().getText(); 342 final boolean matchesSetterFormat = SETTER_PATTERN.matcher(name).matches(); 343 final boolean voidReturnType = type.findFirstToken(TokenTypes.LITERAL_VOID) != null; 344 345 final DetailAST params = ast.findFirstToken(TokenTypes.PARAMETERS); 346 final boolean singleParam = params.getChildCount(TokenTypes.PARAMETER_DEF) == 1; 347 348 if (matchesSetterFormat && voidReturnType && singleParam) { 349 // Now verify that the body consists of: 350 // SLIST -> EXPR -> ASSIGN 351 // SEMI 352 // RCURLY 353 final DetailAST slist = ast.findFirstToken(TokenTypes.SLIST); 354 355 if (slist != null && slist.getChildCount() == SETTER_BODY_SIZE) { 356 final DetailAST expr = slist.getFirstChild(); 357 setterMethod = expr.getFirstChild().getType() == TokenTypes.ASSIGN; 358 } 359 } 360 } 361 return setterMethod; 362 } 363 364 /** 365 * Returns whether an AST represents a getter method. 366 * 367 * @param ast the AST to check with 368 * @return whether the AST represents a getter method 369 */ 370 public static boolean isGetterMethod(final DetailAST ast) { 371 boolean getterMethod = false; 372 373 // Check have a method with exactly 7 children which are all that 374 // is allowed in a proper getter method which does not throw any 375 // exceptions. 376 if (ast.getType() == TokenTypes.METHOD_DEF 377 && ast.getChildCount() == SETTER_GETTER_MAX_CHILDREN) { 378 final DetailAST type = ast.findFirstToken(TokenTypes.TYPE); 379 final String name = type.getNextSibling().getText(); 380 final boolean matchesGetterFormat = GETTER_PATTERN.matcher(name).matches(); 381 final boolean noVoidReturnType = type.findFirstToken(TokenTypes.LITERAL_VOID) == null; 382 383 final DetailAST params = ast.findFirstToken(TokenTypes.PARAMETERS); 384 final boolean noParams = params.getChildCount(TokenTypes.PARAMETER_DEF) == 0; 385 386 if (matchesGetterFormat && noVoidReturnType && noParams) { 387 // Now verify that the body consists of: 388 // SLIST -> RETURN 389 // RCURLY 390 final DetailAST slist = ast.findFirstToken(TokenTypes.SLIST); 391 392 if (slist != null && slist.getChildCount() == GETTER_BODY_SIZE) { 393 final DetailAST expr = slist.getFirstChild(); 394 getterMethod = expr.getType() == TokenTypes.LITERAL_RETURN; 395 } 396 } 397 } 398 return getterMethod; 399 } 400 401 /** 402 * Checks whether a method is a not void one. 403 * 404 * @param methodDefAst the method node. 405 * @return true if method is a not void one. 406 */ 407 public static boolean isNonVoidMethod(DetailAST methodDefAst) { 408 boolean returnValue = false; 409 if (methodDefAst.getType() == TokenTypes.METHOD_DEF) { 410 final DetailAST typeAST = methodDefAst.findFirstToken(TokenTypes.TYPE); 411 if (typeAST.findFirstToken(TokenTypes.LITERAL_VOID) == null) { 412 returnValue = true; 413 } 414 } 415 return returnValue; 416 } 417 418 /** 419 * Checks whether a parameter is a receiver. 420 * 421 * @param parameterDefAst the parameter node. 422 * @return true if the parameter is a receiver. 423 */ 424 public static boolean isReceiverParameter(DetailAST parameterDefAst) { 425 return parameterDefAst.getType() == TokenTypes.PARAMETER_DEF 426 && parameterDefAst.findFirstToken(TokenTypes.IDENT) == null; 427 } 428 429 /** 430 * Returns the access modifier of the method/constructor at the specified AST. If 431 * the method is in an interface or annotation block, the access modifier is assumed 432 * to be public. 433 * 434 * @param ast the token of the method/constructor. 435 * @return the access modifier of the method/constructor. 436 */ 437 public static AccessModifierOption getAccessModifierFromModifiersToken(DetailAST ast) { 438 final DetailAST modsToken = ast.findFirstToken(TokenTypes.MODIFIERS); 439 AccessModifierOption accessModifier = 440 getAccessModifierFromModifiersTokenDirectly(modsToken); 441 442 if (accessModifier == AccessModifierOption.PACKAGE) { 443 if (ScopeUtil.isInEnumBlock(ast) && ast.getType() == TokenTypes.CTOR_DEF) { 444 accessModifier = AccessModifierOption.PRIVATE; 445 } 446 else if (ScopeUtil.isInInterfaceOrAnnotationBlock(ast)) { 447 accessModifier = AccessModifierOption.PUBLIC; 448 } 449 } 450 451 return accessModifier; 452 } 453 454 /** 455 * Returns {@link AccessModifierOption} based on the information about access modifier 456 * taken from the given token of type {@link TokenTypes#MODIFIERS}. 457 * 458 * @param modifiersToken token of type {@link TokenTypes#MODIFIERS}. 459 * @return {@link AccessModifierOption}. 460 * @throws IllegalArgumentException when expected non-null modifiersToken with type 'MODIFIERS' 461 */ 462 private static AccessModifierOption getAccessModifierFromModifiersTokenDirectly( 463 DetailAST modifiersToken) { 464 if (modifiersToken == null) { 465 throw new IllegalArgumentException("expected non-null AST-token with type 'MODIFIERS'"); 466 } 467 468 AccessModifierOption accessModifier = AccessModifierOption.PACKAGE; 469 for (DetailAST token = modifiersToken.getFirstChild(); token != null; 470 token = token.getNextSibling()) { 471 final int tokenType = token.getType(); 472 if (tokenType == TokenTypes.LITERAL_PUBLIC) { 473 accessModifier = AccessModifierOption.PUBLIC; 474 } 475 else if (tokenType == TokenTypes.LITERAL_PROTECTED) { 476 accessModifier = AccessModifierOption.PROTECTED; 477 } 478 else if (tokenType == TokenTypes.LITERAL_PRIVATE) { 479 accessModifier = AccessModifierOption.PRIVATE; 480 } 481 } 482 return accessModifier; 483 } 484 485 /** 486 * Returns the access modifier of the surrounding "block". 487 * 488 * @param node the node to return the access modifier for 489 * @return the access modifier of the surrounding block 490 */ 491 public static AccessModifierOption getSurroundingAccessModifier(DetailAST node) { 492 AccessModifierOption returnValue = null; 493 for (DetailAST token = node.getParent(); 494 returnValue == null && !TokenUtil.isRootNode(token); 495 token = token.getParent()) { 496 final int type = token.getType(); 497 if (type == TokenTypes.CLASS_DEF 498 || type == TokenTypes.INTERFACE_DEF 499 || type == TokenTypes.ANNOTATION_DEF 500 || type == TokenTypes.ENUM_DEF) { 501 returnValue = getAccessModifierFromModifiersToken(token); 502 } 503 else if (type == TokenTypes.LITERAL_NEW) { 504 break; 505 } 506 } 507 508 return returnValue; 509 } 510 511 /** 512 * Create set of class names and short class names. 513 * 514 * @param classNames array of class names. 515 * @return set of class names and short class names. 516 */ 517 public static Set<String> parseClassNames(String... classNames) { 518 return Arrays.stream(classNames) 519 .flatMap(className -> Stream.of(className, CommonUtil.baseClassName(className))) 520 .filter(Predicate.not(String::isEmpty)) 521 .collect(Collectors.toUnmodifiableSet()); 522 } 523 524 /** 525 * Strip initial newline and preceding whitespace on each line from text block content. 526 * In order to be consistent with how javac handles this task, we have modeled this 527 * implementation after the code from: 528 * github.com/openjdk/jdk14u/blob/master/src/java.base/share/classes/java/lang/String.java 529 * 530 * @param textBlockContent the actual content of the text block. 531 * @return string consistent with javac representation. 532 */ 533 public static String stripIndentAndInitialNewLineFromTextBlock(String textBlockContent) { 534 final String contentWithInitialNewLineRemoved = 535 ALL_NEW_LINES.matcher(textBlockContent).replaceFirst(""); 536 final List<String> lines = 537 Arrays.asList(ALL_NEW_LINES.split(contentWithInitialNewLineRemoved)); 538 final int indent = getSmallestIndent(lines); 539 final String suffix = ""; 540 541 return lines.stream() 542 .map(line -> stripIndentAndTrailingWhitespaceFromLine(line, indent)) 543 .collect(Collectors.joining(System.lineSeparator(), suffix, suffix)); 544 } 545 546 /** 547 * Helper method for stripIndentAndInitialNewLineFromTextBlock, strips correct indent 548 * from string, and trailing whitespace, or returns empty string if no text. 549 * 550 * @param line the string to strip indent and trailing whitespace from 551 * @param indent the amount of indent to remove 552 * @return modified string with removed indent and trailing whitespace, or empty string. 553 */ 554 private static String stripIndentAndTrailingWhitespaceFromLine(String line, int indent) { 555 final int lastNonWhitespace = lastIndexOfNonWhitespace(line); 556 String returnString = ""; 557 if (lastNonWhitespace > 0) { 558 returnString = line.substring(indent, lastNonWhitespace); 559 } 560 return returnString; 561 } 562 563 /** 564 * Helper method for stripIndentAndInitialNewLineFromTextBlock, to determine the smallest 565 * indent in a text block string literal. 566 * 567 * @param lines collection of actual text block content, split by line. 568 * @return number of spaces representing the smallest indent in this text block. 569 */ 570 private static int getSmallestIndent(Collection<String> lines) { 571 return lines.stream() 572 .mapToInt(CommonUtil::indexOfNonWhitespace) 573 .min() 574 .orElse(0); 575 } 576 577 /** 578 * Helper method to find the index of the last non-whitespace character in a string. 579 * 580 * @param line the string to find the last index of a non-whitespace character for. 581 * @return the index of the last non-whitespace character. 582 */ 583 private static int lastIndexOfNonWhitespace(String line) { 584 int length; 585 for (length = line.length(); length > 0; length--) { 586 if (!Character.isWhitespace(line.charAt(length - 1))) { 587 break; 588 } 589 } 590 return length; 591 } 592 593 /** 594 * Calculates and returns the type declaration name matching count. 595 * 596 * <p> 597 * Suppose our pattern class is {@code foo.a.b} and class to be matched is 598 * {@code foo.a.ball} then type declaration name matching count would be calculated by 599 * comparing every character, and updating main counter when we hit "." to prevent matching 600 * "a.b" with "a.ball". In this case type declaration name matching count 601 * would be equal to 6 and not 7 (b of ball is not counted). 602 * </p> 603 * 604 * @param patternClass class against which the given class has to be matched 605 * @param classToBeMatched class to be matched 606 * @return class name matching count 607 */ 608 public static int typeDeclarationNameMatchingCount(String patternClass, 609 String classToBeMatched) { 610 final int length = Math.min(classToBeMatched.length(), patternClass.length()); 611 int result = 0; 612 for (int i = 0; i < length && patternClass.charAt(i) == classToBeMatched.charAt(i); ++i) { 613 if (patternClass.charAt(i) == PACKAGE_SEPARATOR) { 614 result = i; 615 } 616 } 617 return result; 618 } 619 620 /** 621 * Get the qualified name of type declaration by combining {@code packageName}, 622 * {@code outerClassQualifiedName} and {@code className}. 623 * 624 * @param packageName packageName 625 * @param outerClassQualifiedName outerClassQualifiedName 626 * @param className className 627 * @return the qualified name of type declaration by combining {@code packageName}, 628 * {@code outerClassQualifiedName} and {@code className} 629 */ 630 public static String getQualifiedTypeDeclarationName(String packageName, 631 String outerClassQualifiedName, 632 String className) { 633 final String qualifiedClassName; 634 635 if (outerClassQualifiedName == null) { 636 if (packageName == null) { 637 qualifiedClassName = className; 638 } 639 else { 640 qualifiedClassName = packageName + PACKAGE_SEPARATOR + className; 641 } 642 } 643 else { 644 qualifiedClassName = outerClassQualifiedName + PACKAGE_SEPARATOR + className; 645 } 646 return qualifiedClassName; 647 } 648 649 /** 650 * Get name of package and super class of anon inner class by concatenating 651 * the identifier values under {@link TokenTypes#DOT}. 652 * 653 * @param ast ast to extract superclass or package name from 654 * @return qualified name 655 */ 656 public static String extractQualifiedName(DetailAST ast) { 657 return FullIdent.createFullIdent(ast).getText(); 658 } 659 660 /** 661 * Get the short name of super class of anonymous inner class. 662 * Example: 663 * <pre> 664 * TestClass.NestedClass obj = new Test().new NestedClass() {}; 665 * // Short name will be Test.NestedClass 666 * </pre> 667 * 668 * @param literalNewAst ast node of type {@link TokenTypes#LITERAL_NEW} 669 * @return short name of base class of anonymous inner class 670 */ 671 public static String getShortNameOfAnonInnerClass(DetailAST literalNewAst) { 672 DetailAST parentAst = literalNewAst.getParent(); 673 while (TokenUtil.isOfType(parentAst, TokenTypes.LITERAL_NEW, TokenTypes.DOT)) { 674 parentAst = parentAst.getParent(); 675 } 676 final DetailAST firstChild = parentAst.getFirstChild(); 677 return extractQualifiedName(firstChild); 678 } 679 680 /** 681 * Checks if the given file path is a package-info.java file. 682 * 683 * @param filePath path to the file. 684 * @return true if the package file. 685 */ 686 public static boolean isPackageInfo(String filePath) { 687 return "package-info.java".equals(new File(filePath).getName()); 688 } 689}