001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2024 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    /** Pattern matching underscore characters ('_'). */
058    private static final Pattern UNDERSCORE_PATTERN = Pattern.compile("_");
059
060    /** Compiled pattern for all system newlines. */
061    private static final Pattern ALL_NEW_LINES = Pattern.compile("\\R");
062
063    /** Package separator. */
064    private static final char PACKAGE_SEPARATOR = '.';
065
066    /** Prevent instances. */
067    private CheckUtil() {
068    }
069
070    /**
071     * Tests whether a method definition AST defines an equals covariant.
072     *
073     * @param ast the method definition AST to test.
074     *     Precondition: ast is a TokenTypes.METHOD_DEF node.
075     * @return true if ast defines an equals covariant.
076     */
077    public static boolean isEqualsMethod(DetailAST ast) {
078        boolean equalsMethod = false;
079
080        if (ast.getType() == TokenTypes.METHOD_DEF) {
081            final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS);
082            final boolean staticOrAbstract =
083                    modifiers.findFirstToken(TokenTypes.LITERAL_STATIC) != null
084                    || modifiers.findFirstToken(TokenTypes.ABSTRACT) != null;
085
086            if (!staticOrAbstract) {
087                final DetailAST nameNode = ast.findFirstToken(TokenTypes.IDENT);
088                final String name = nameNode.getText();
089
090                if ("equals".equals(name)) {
091                    // one parameter?
092                    final DetailAST paramsNode = ast.findFirstToken(TokenTypes.PARAMETERS);
093                    equalsMethod = paramsNode.getChildCount() == 1;
094                }
095            }
096        }
097        return equalsMethod;
098    }
099
100    /**
101     * Returns the value represented by the specified string of the specified
102     * type. Returns 0 for types other than float, double, int, and long.
103     *
104     * @param text the string to be parsed.
105     * @param type the token type of the text. Should be a constant of
106     *     {@link TokenTypes}.
107     * @return the double value represented by the string argument.
108     */
109    public static double parseDouble(String text, int type) {
110        String txt = UNDERSCORE_PATTERN.matcher(text).replaceAll("");
111        final double result;
112        switch (type) {
113            case TokenTypes.NUM_FLOAT:
114            case TokenTypes.NUM_DOUBLE:
115                result = Double.parseDouble(txt);
116                break;
117            case TokenTypes.NUM_INT:
118            case TokenTypes.NUM_LONG:
119                int radix = BASE_10;
120                if (txt.startsWith("0x") || txt.startsWith("0X")) {
121                    radix = BASE_16;
122                    txt = txt.substring(2);
123                }
124                else if (txt.startsWith("0b") || txt.startsWith("0B")) {
125                    radix = BASE_2;
126                    txt = txt.substring(2);
127                }
128                else if (txt.startsWith("0")) {
129                    radix = BASE_8;
130                }
131                result = parseNumber(txt, radix, type);
132                break;
133            default:
134                result = Double.NaN;
135                break;
136        }
137        return result;
138    }
139
140    /**
141     * Parses the string argument as an integer or a long in the radix specified by
142     * the second argument. The characters in the string must all be digits of
143     * the specified radix.
144     *
145     * @param text the String containing the integer representation to be
146     *     parsed. Precondition: text contains a parsable int.
147     * @param radix the radix to be used while parsing text.
148     * @param type the token type of the text. Should be a constant of
149     *     {@link TokenTypes}.
150     * @return the number represented by the string argument in the specified radix.
151     */
152    private static double parseNumber(final String text, final int radix, final int type) {
153        String txt = text;
154        if (txt.endsWith("L") || txt.endsWith("l")) {
155            txt = txt.substring(0, txt.length() - 1);
156        }
157        final double result;
158
159        final boolean negative = txt.charAt(0) == '-';
160        if (type == TokenTypes.NUM_INT) {
161            if (negative) {
162                result = Integer.parseInt(txt, radix);
163            }
164            else {
165                result = Integer.parseUnsignedInt(txt, radix);
166            }
167        }
168        else {
169            if (negative) {
170                result = Long.parseLong(txt, radix);
171            }
172            else {
173                result = Long.parseUnsignedLong(txt, radix);
174            }
175        }
176
177        return result;
178    }
179
180    /**
181     * Finds sub-node for given node minimal (line, column) pair.
182     *
183     * @param node the root of tree for search.
184     * @return sub-node with minimal (line, column) pair.
185     */
186    public static DetailAST getFirstNode(final DetailAST node) {
187        DetailAST currentNode = node;
188        DetailAST child = node.getFirstChild();
189        while (child != null) {
190            final DetailAST newNode = getFirstNode(child);
191            if (isBeforeInSource(newNode, currentNode)) {
192                currentNode = newNode;
193            }
194            child = child.getNextSibling();
195        }
196
197        return currentNode;
198    }
199
200    /**
201     * Retrieves whether ast1 is located before ast2.
202     *
203     * @param ast1 the first node.
204     * @param ast2 the second node.
205     * @return true, if ast1 is located before ast2.
206     */
207    public static boolean isBeforeInSource(DetailAST ast1, DetailAST ast2) {
208        return ast1.getLineNo() < ast2.getLineNo()
209            || TokenUtil.areOnSameLine(ast1, ast2)
210                && ast1.getColumnNo() < ast2.getColumnNo();
211    }
212
213    /**
214     * Retrieves the names of the type parameters to the node.
215     *
216     * @param node the parameterized AST node
217     * @return a list of type parameter names
218     */
219    public static List<String> getTypeParameterNames(final DetailAST node) {
220        final DetailAST typeParameters =
221            node.findFirstToken(TokenTypes.TYPE_PARAMETERS);
222
223        final List<String> typeParameterNames = new ArrayList<>();
224        if (typeParameters != null) {
225            final DetailAST typeParam =
226                typeParameters.findFirstToken(TokenTypes.TYPE_PARAMETER);
227            typeParameterNames.add(
228                    typeParam.findFirstToken(TokenTypes.IDENT).getText());
229
230            DetailAST sibling = typeParam.getNextSibling();
231            while (sibling != null) {
232                if (sibling.getType() == TokenTypes.TYPE_PARAMETER) {
233                    typeParameterNames.add(
234                            sibling.findFirstToken(TokenTypes.IDENT).getText());
235                }
236                sibling = sibling.getNextSibling();
237            }
238        }
239
240        return typeParameterNames;
241    }
242
243    /**
244     * Retrieves the type parameters to the node.
245     *
246     * @param node the parameterized AST node
247     * @return a list of type parameter names
248     */
249    public static List<DetailAST> getTypeParameters(final DetailAST node) {
250        final DetailAST typeParameters =
251            node.findFirstToken(TokenTypes.TYPE_PARAMETERS);
252
253        final List<DetailAST> typeParams = new ArrayList<>();
254        if (typeParameters != null) {
255            final DetailAST typeParam =
256                typeParameters.findFirstToken(TokenTypes.TYPE_PARAMETER);
257            typeParams.add(typeParam);
258
259            DetailAST sibling = typeParam.getNextSibling();
260            while (sibling != null) {
261                if (sibling.getType() == TokenTypes.TYPE_PARAMETER) {
262                    typeParams.add(sibling);
263                }
264                sibling = sibling.getNextSibling();
265            }
266        }
267
268        return typeParams;
269    }
270
271    /**
272     * Checks whether a method is a not void one.
273     *
274     * @param methodDefAst the method node.
275     * @return true if method is a not void one.
276     */
277    public static boolean isNonVoidMethod(DetailAST methodDefAst) {
278        boolean returnValue = false;
279        if (methodDefAst.getType() == TokenTypes.METHOD_DEF) {
280            final DetailAST typeAST = methodDefAst.findFirstToken(TokenTypes.TYPE);
281            if (typeAST.findFirstToken(TokenTypes.LITERAL_VOID) == null) {
282                returnValue = true;
283            }
284        }
285        return returnValue;
286    }
287
288    /**
289     * Checks whether a parameter is a receiver.
290     *
291     * <p>A receiver parameter is a special parameter that
292     * represents the object for which the method is invoked.
293     * It is denoted by the reserved keyword {@code this}
294     * in the method declaration. Check
295     * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#PARAMETER_DEF">
296     * PARAMETER_DEF</a>
297     * </p>
298     *
299     * @param parameterDefAst the parameter node.
300     * @return true if the parameter is a receiver.
301     * @see <a href="https://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.4.1">
302     *     ReceiverParameter</a>
303     */
304    public static boolean isReceiverParameter(DetailAST parameterDefAst) {
305        return parameterDefAst.findFirstToken(TokenTypes.IDENT) == null;
306    }
307
308    /**
309     * Returns the access modifier of the method/constructor at the specified AST. If
310     * the method is in an interface or annotation block, the access modifier is assumed
311     * to be public.
312     *
313     * @param ast the token of the method/constructor.
314     * @return the access modifier of the method/constructor.
315     */
316    public static AccessModifierOption getAccessModifierFromModifiersToken(DetailAST ast) {
317        final DetailAST modsToken = ast.findFirstToken(TokenTypes.MODIFIERS);
318        AccessModifierOption accessModifier =
319                getAccessModifierFromModifiersTokenDirectly(modsToken);
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 {@link TokenTypes#MODIFIERS}.
338     * @return {@link 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 AccessModifierOption getSurroundingAccessModifier(DetailAST node) {
371        AccessModifierOption returnValue = null;
372        for (DetailAST token = node;
373             returnValue == null && !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 = 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 i = 0; i < length && patternClass.charAt(i) == classToBeMatched.charAt(i); ++i) {
492            if (patternClass.charAt(i) == PACKAGE_SEPARATOR) {
493                result = i;
494            }
495        }
496        return result;
497    }
498
499    /**
500     * Get the qualified name of type declaration by combining {@code packageName},
501     * {@code outerClassQualifiedName} and {@code className}.
502     *
503     * @param packageName packageName
504     * @param outerClassQualifiedName outerClassQualifiedName
505     * @param className className
506     * @return the qualified name of type declaration by combining {@code packageName},
507     *         {@code outerClassQualifiedName} and {@code className}
508     */
509    public static String getQualifiedTypeDeclarationName(String packageName,
510                                                         String outerClassQualifiedName,
511                                                         String className) {
512        final String qualifiedClassName;
513
514        if (outerClassQualifiedName == null) {
515            if (packageName == null) {
516                qualifiedClassName = className;
517            }
518            else {
519                qualifiedClassName = packageName + PACKAGE_SEPARATOR + className;
520            }
521        }
522        else {
523            qualifiedClassName = outerClassQualifiedName + PACKAGE_SEPARATOR + className;
524        }
525        return qualifiedClassName;
526    }
527
528    /**
529     * Get name of package and super class of anon inner class by concatenating
530     * the identifier values under {@link TokenTypes#DOT}.
531     *
532     * @param ast ast to extract superclass or package name from
533     * @return qualified name
534     */
535    public static String extractQualifiedName(DetailAST ast) {
536        return FullIdent.createFullIdent(ast).getText();
537    }
538
539    /**
540     * Get the short name of super class of anonymous inner class.
541     * Example:
542     * <pre>
543     * TestClass.NestedClass obj = new Test().new NestedClass() {};
544     * // Short name will be Test.NestedClass
545     * </pre>
546     *
547     * @param literalNewAst ast node of type {@link TokenTypes#LITERAL_NEW}
548     * @return short name of base class of anonymous inner class
549     */
550    public static String getShortNameOfAnonInnerClass(DetailAST literalNewAst) {
551        DetailAST parentAst = literalNewAst;
552        while (TokenUtil.isOfType(parentAst, TokenTypes.LITERAL_NEW, TokenTypes.DOT)) {
553            parentAst = parentAst.getParent();
554        }
555        final DetailAST firstChild = parentAst.getFirstChild();
556        return extractQualifiedName(firstChild);
557    }
558
559    /**
560     * Checks if the given file path is a package-info.java file.
561     *
562     * @param filePath path to the file.
563     * @return true if the package file.
564     */
565    public static boolean isPackageInfo(String filePath) {
566        return "package-info.java".equals(new File(filePath).getName());
567    }
568}