001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2022 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}