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