001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2021 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    /** Prevent instances. */
076    private CheckUtil() {
077    }
078
079    /**
080     * Creates {@code FullIdent} for given type node.
081     *
082     * @param typeAST a type node.
083     * @return {@code FullIdent} for given type.
084     */
085    public static FullIdent createFullType(final DetailAST typeAST) {
086        return FullIdent.createFullIdent(typeAST.getFirstChild());
087    }
088
089    /**
090     * Tests whether a method definition AST defines an equals covariant.
091     *
092     * @param ast the method definition AST to test.
093     *     Precondition: ast is a TokenTypes.METHOD_DEF node.
094     * @return true if ast defines an equals covariant.
095     */
096    public static boolean isEqualsMethod(DetailAST ast) {
097        boolean equalsMethod = false;
098
099        if (ast.getType() == TokenTypes.METHOD_DEF) {
100            final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS);
101            final boolean staticOrAbstract =
102                    modifiers.findFirstToken(TokenTypes.LITERAL_STATIC) != null
103                    || modifiers.findFirstToken(TokenTypes.ABSTRACT) != null;
104
105            if (!staticOrAbstract) {
106                final DetailAST nameNode = ast.findFirstToken(TokenTypes.IDENT);
107                final String name = nameNode.getText();
108
109                if ("equals".equals(name)) {
110                    // one parameter?
111                    final DetailAST paramsNode = ast.findFirstToken(TokenTypes.PARAMETERS);
112                    equalsMethod = paramsNode.getChildCount() == 1;
113                }
114            }
115        }
116        return equalsMethod;
117    }
118
119    /**
120     * Returns whether a token represents an ELSE as part of an ELSE / IF set.
121     *
122     * @param ast the token to check
123     * @return whether it is
124     */
125    public static boolean isElseIf(DetailAST ast) {
126        final DetailAST parentAST = ast.getParent();
127
128        return ast.getType() == TokenTypes.LITERAL_IF
129            && (isElse(parentAST) || isElseWithCurlyBraces(parentAST));
130    }
131
132    /**
133     * Returns whether a token represents an ELSE.
134     *
135     * @param ast the token to check
136     * @return whether the token represents an ELSE
137     */
138    private static boolean isElse(DetailAST ast) {
139        return ast.getType() == TokenTypes.LITERAL_ELSE;
140    }
141
142    /**
143     * Returns whether a token represents an SLIST as part of an ELSE
144     * statement.
145     *
146     * @param ast the token to check
147     * @return whether the toke does represent an SLIST as part of an ELSE
148     */
149    private static boolean isElseWithCurlyBraces(DetailAST ast) {
150        return ast.getType() == TokenTypes.SLIST
151            && ast.getChildCount() == 2
152            && isElse(ast.getParent());
153    }
154
155    /**
156     * Returns the value represented by the specified string of the specified
157     * type. Returns 0 for types other than float, double, int, and long.
158     *
159     * @param text the string to be parsed.
160     * @param type the token type of the text. Should be a constant of
161     *     {@link TokenTypes}.
162     * @return the double value represented by the string argument.
163     */
164    public static double parseDouble(String text, int type) {
165        String txt = UNDERSCORE_PATTERN.matcher(text).replaceAll("");
166        final double result;
167        switch (type) {
168            case TokenTypes.NUM_FLOAT:
169            case TokenTypes.NUM_DOUBLE:
170                result = Double.parseDouble(txt);
171                break;
172            case TokenTypes.NUM_INT:
173            case TokenTypes.NUM_LONG:
174                int radix = BASE_10;
175                if (txt.startsWith("0x") || txt.startsWith("0X")) {
176                    radix = BASE_16;
177                    txt = txt.substring(2);
178                }
179                else if (txt.startsWith("0b") || txt.startsWith("0B")) {
180                    radix = BASE_2;
181                    txt = txt.substring(2);
182                }
183                else if (CommonUtil.startsWithChar(txt, '0')) {
184                    radix = BASE_8;
185                    txt = txt.substring(1);
186                }
187                result = parseNumber(txt, radix, type);
188                break;
189            default:
190                result = Double.NaN;
191                break;
192        }
193        return result;
194    }
195
196    /**
197     * Parses the string argument as an integer or a long in the radix specified by
198     * the second argument. The characters in the string must all be digits of
199     * the specified radix.
200     *
201     * @param text the String containing the integer representation to be
202     *     parsed. Precondition: text contains a parsable int.
203     * @param radix the radix to be used while parsing text.
204     * @param type the token type of the text. Should be a constant of
205     *     {@link TokenTypes}.
206     * @return the number represented by the string argument in the specified radix.
207     */
208    private static double parseNumber(final String text, final int radix, final int type) {
209        String txt = text;
210        if (CommonUtil.endsWithChar(txt, 'L') || CommonUtil.endsWithChar(txt, 'l')) {
211            txt = txt.substring(0, txt.length() - 1);
212        }
213        final double result;
214        if (txt.isEmpty()) {
215            result = 0.0;
216        }
217        else {
218            final boolean negative = txt.charAt(0) == '-';
219            if (type == TokenTypes.NUM_INT) {
220                if (negative) {
221                    result = Integer.parseInt(txt, radix);
222                }
223                else {
224                    result = Integer.parseUnsignedInt(txt, radix);
225                }
226            }
227            else {
228                if (negative) {
229                    result = Long.parseLong(txt, radix);
230                }
231                else {
232                    result = Long.parseUnsignedLong(txt, radix);
233                }
234            }
235        }
236        return result;
237    }
238
239    /**
240     * Finds sub-node for given node minimal (line, column) pair.
241     *
242     * @param node the root of tree for search.
243     * @return sub-node with minimal (line, column) pair.
244     */
245    public static DetailAST getFirstNode(final DetailAST node) {
246        DetailAST currentNode = node;
247        DetailAST child = node.getFirstChild();
248        while (child != null) {
249            final DetailAST newNode = getFirstNode(child);
250            if (isBeforeInSource(newNode, currentNode)) {
251                currentNode = newNode;
252            }
253            child = child.getNextSibling();
254        }
255
256        return currentNode;
257    }
258
259    /**
260     * Retrieves whether ast1 is located before ast2.
261     *
262     * @param ast1 the first node.
263     * @param ast2 the second node.
264     * @return true, if ast1 is located before ast2.
265     */
266    public static boolean isBeforeInSource(DetailAST ast1, DetailAST ast2) {
267        return ast1.getLineNo() < ast2.getLineNo()
268            || TokenUtil.areOnSameLine(ast1, ast2)
269                && ast1.getColumnNo() < ast2.getColumnNo();
270    }
271
272    /**
273     * Retrieves the names of the type parameters to the node.
274     *
275     * @param node the parameterized AST node
276     * @return a list of type parameter names
277     */
278    public static List<String> getTypeParameterNames(final DetailAST node) {
279        final DetailAST typeParameters =
280            node.findFirstToken(TokenTypes.TYPE_PARAMETERS);
281
282        final List<String> typeParameterNames = new ArrayList<>();
283        if (typeParameters != null) {
284            final DetailAST typeParam =
285                typeParameters.findFirstToken(TokenTypes.TYPE_PARAMETER);
286            typeParameterNames.add(
287                    typeParam.findFirstToken(TokenTypes.IDENT).getText());
288
289            DetailAST sibling = typeParam.getNextSibling();
290            while (sibling != null) {
291                if (sibling.getType() == TokenTypes.TYPE_PARAMETER) {
292                    typeParameterNames.add(
293                            sibling.findFirstToken(TokenTypes.IDENT).getText());
294                }
295                sibling = sibling.getNextSibling();
296            }
297        }
298
299        return typeParameterNames;
300    }
301
302    /**
303     * Retrieves the type parameters to the node.
304     *
305     * @param node the parameterized AST node
306     * @return a list of type parameter names
307     */
308    public static List<DetailAST> getTypeParameters(final DetailAST node) {
309        final DetailAST typeParameters =
310            node.findFirstToken(TokenTypes.TYPE_PARAMETERS);
311
312        final List<DetailAST> typeParams = new ArrayList<>();
313        if (typeParameters != null) {
314            final DetailAST typeParam =
315                typeParameters.findFirstToken(TokenTypes.TYPE_PARAMETER);
316            typeParams.add(typeParam);
317
318            DetailAST sibling = typeParam.getNextSibling();
319            while (sibling != null) {
320                if (sibling.getType() == TokenTypes.TYPE_PARAMETER) {
321                    typeParams.add(sibling);
322                }
323                sibling = sibling.getNextSibling();
324            }
325        }
326
327        return typeParams;
328    }
329
330    /**
331     * Returns whether an AST represents a setter method.
332     *
333     * @param ast the AST to check with
334     * @return whether the AST represents a setter method
335     */
336    public static boolean isSetterMethod(final DetailAST ast) {
337        boolean setterMethod = false;
338
339        // Check have a method with exactly 7 children which are all that
340        // is allowed in a proper setter method which does not throw any
341        // exceptions.
342        if (ast.getType() == TokenTypes.METHOD_DEF
343                && ast.getChildCount() == SETTER_GETTER_MAX_CHILDREN) {
344            final DetailAST type = ast.findFirstToken(TokenTypes.TYPE);
345            final String name = type.getNextSibling().getText();
346            final boolean matchesSetterFormat = SETTER_PATTERN.matcher(name).matches();
347            final boolean voidReturnType = type.findFirstToken(TokenTypes.LITERAL_VOID) != null;
348
349            final DetailAST params = ast.findFirstToken(TokenTypes.PARAMETERS);
350            final boolean singleParam = params.getChildCount(TokenTypes.PARAMETER_DEF) == 1;
351
352            if (matchesSetterFormat && voidReturnType && singleParam) {
353                // Now verify that the body consists of:
354                // SLIST -> EXPR -> ASSIGN
355                // SEMI
356                // RCURLY
357                final DetailAST slist = ast.findFirstToken(TokenTypes.SLIST);
358
359                if (slist != null && slist.getChildCount() == SETTER_BODY_SIZE) {
360                    final DetailAST expr = slist.getFirstChild();
361                    setterMethod = expr.getFirstChild().getType() == TokenTypes.ASSIGN;
362                }
363            }
364        }
365        return setterMethod;
366    }
367
368    /**
369     * Returns whether an AST represents a getter method.
370     *
371     * @param ast the AST to check with
372     * @return whether the AST represents a getter method
373     */
374    public static boolean isGetterMethod(final DetailAST ast) {
375        boolean getterMethod = false;
376
377        // Check have a method with exactly 7 children which are all that
378        // is allowed in a proper getter method which does not throw any
379        // exceptions.
380        if (ast.getType() == TokenTypes.METHOD_DEF
381                && ast.getChildCount() == SETTER_GETTER_MAX_CHILDREN) {
382            final DetailAST type = ast.findFirstToken(TokenTypes.TYPE);
383            final String name = type.getNextSibling().getText();
384            final boolean matchesGetterFormat = GETTER_PATTERN.matcher(name).matches();
385            final boolean noVoidReturnType = type.findFirstToken(TokenTypes.LITERAL_VOID) == null;
386
387            final DetailAST params = ast.findFirstToken(TokenTypes.PARAMETERS);
388            final boolean noParams = params.getChildCount(TokenTypes.PARAMETER_DEF) == 0;
389
390            if (matchesGetterFormat && noVoidReturnType && noParams) {
391                // Now verify that the body consists of:
392                // SLIST -> RETURN
393                // RCURLY
394                final DetailAST slist = ast.findFirstToken(TokenTypes.SLIST);
395
396                if (slist != null && slist.getChildCount() == GETTER_BODY_SIZE) {
397                    final DetailAST expr = slist.getFirstChild();
398                    getterMethod = expr.getType() == TokenTypes.LITERAL_RETURN;
399                }
400            }
401        }
402        return getterMethod;
403    }
404
405    /**
406     * Checks whether a method is a not void one.
407     *
408     * @param methodDefAst the method node.
409     * @return true if method is a not void one.
410     */
411    public static boolean isNonVoidMethod(DetailAST methodDefAst) {
412        boolean returnValue = false;
413        if (methodDefAst.getType() == TokenTypes.METHOD_DEF) {
414            final DetailAST typeAST = methodDefAst.findFirstToken(TokenTypes.TYPE);
415            if (typeAST.findFirstToken(TokenTypes.LITERAL_VOID) == null) {
416                returnValue = true;
417            }
418        }
419        return returnValue;
420    }
421
422    /**
423     * Checks whether a parameter is a receiver.
424     *
425     * @param parameterDefAst the parameter node.
426     * @return true if the parameter is a receiver.
427     */
428    public static boolean isReceiverParameter(DetailAST parameterDefAst) {
429        return parameterDefAst.getType() == TokenTypes.PARAMETER_DEF
430                && parameterDefAst.findFirstToken(TokenTypes.IDENT) == null;
431    }
432
433    /**
434     * Returns the access modifier of the method/constructor at the specified AST. If
435     * the method is in an interface or annotation block, the access modifier is assumed
436     * to be public.
437     *
438     * @param ast the token of the method/constructor.
439     * @return the access modifier of the method/constructor.
440     */
441    public static AccessModifierOption getAccessModifierFromModifiersToken(DetailAST ast) {
442        final AccessModifierOption accessModifier;
443
444        if (ScopeUtil.isInInterfaceOrAnnotationBlock(ast)) {
445            accessModifier = AccessModifierOption.PUBLIC;
446        }
447        else {
448            final DetailAST modsToken = ast.findFirstToken(TokenTypes.MODIFIERS);
449            accessModifier = getAccessModifierFromModifiersTokenDirectly(modsToken);
450        }
451
452        return accessModifier;
453    }
454
455    /**
456     * Returns {@link AccessModifierOption} based on the information about access modifier
457     * taken from the given token of type {@link TokenTypes#MODIFIERS}.
458     *
459     * @param modifiersToken token of type {@link TokenTypes#MODIFIERS}.
460     * @return {@link AccessModifierOption}.
461     * @throws IllegalArgumentException when expected non-null modifiersToken with type 'MODIFIERS'
462     */
463    private static AccessModifierOption getAccessModifierFromModifiersTokenDirectly(
464            DetailAST modifiersToken) {
465        if (modifiersToken == null) {
466            throw new IllegalArgumentException("expected non-null AST-token with type 'MODIFIERS'");
467        }
468
469        // default access modifier
470        AccessModifierOption accessModifier = AccessModifierOption.PACKAGE;
471        for (DetailAST token = modifiersToken.getFirstChild(); token != null;
472             token = token.getNextSibling()) {
473            final int tokenType = token.getType();
474            if (tokenType == TokenTypes.LITERAL_PUBLIC) {
475                accessModifier = AccessModifierOption.PUBLIC;
476            }
477            else if (tokenType == TokenTypes.LITERAL_PROTECTED) {
478                accessModifier = AccessModifierOption.PROTECTED;
479            }
480            else if (tokenType == TokenTypes.LITERAL_PRIVATE) {
481                accessModifier = AccessModifierOption.PRIVATE;
482            }
483        }
484        return accessModifier;
485    }
486
487    /**
488     * Returns the access modifier of the surrounding "block".
489     *
490     * @param node the node to return the access modifier for
491     * @return the access modifier of the surrounding block
492     */
493    public static AccessModifierOption getSurroundingAccessModifier(DetailAST node) {
494        AccessModifierOption returnValue = null;
495        for (DetailAST token = node.getParent();
496             returnValue == null && !TokenUtil.isRootNode(token);
497             token = token.getParent()) {
498            final int type = token.getType();
499            if (type == TokenTypes.CLASS_DEF
500                || type == TokenTypes.INTERFACE_DEF
501                || type == TokenTypes.ANNOTATION_DEF
502                || type == TokenTypes.ENUM_DEF) {
503                final DetailAST mods =
504                    token.findFirstToken(TokenTypes.MODIFIERS);
505                returnValue = getAccessModifierFromModifiersTokenDirectly(mods);
506            }
507            else if (type == TokenTypes.LITERAL_NEW) {
508                break;
509            }
510        }
511
512        return returnValue;
513    }
514
515    /**
516     * Create set of class names and short class names.
517     *
518     * @param classNames array of class names.
519     * @return set of class names and short class names.
520     */
521    public static Set<String> parseClassNames(String... classNames) {
522        final Set<String> illegalClassNames = new HashSet<>();
523        for (final String name : classNames) {
524            illegalClassNames.add(name);
525            final int lastDot = name.lastIndexOf('.');
526            if (lastDot != -1 && lastDot < name.length() - 1) {
527                final String shortName = name
528                        .substring(name.lastIndexOf('.') + 1);
529                illegalClassNames.add(shortName);
530            }
531        }
532        return illegalClassNames;
533    }
534
535    /**
536     * Strip initial newline and preceding whitespace on each line from text block content.
537     * In order to be consistent with how javac handles this task, we have modeled this
538     * implementation after the code from:
539     * github.com/openjdk/jdk14u/blob/master/src/java.base/share/classes/java/lang/String.java
540     *
541     * @param textBlockContent the actual content of the text block.
542     * @return string consistent with javac representation.
543     */
544    public static String stripIndentAndInitialNewLineFromTextBlock(String textBlockContent) {
545        final String contentWithInitialNewLineRemoved =
546            ALL_NEW_LINES.matcher(textBlockContent).replaceFirst("");
547        final List<String> lines =
548            Arrays.asList(ALL_NEW_LINES.split(contentWithInitialNewLineRemoved));
549        final int indent = getSmallestIndent(lines);
550        final String suffix = "";
551
552        return lines.stream()
553                .map(line -> stripIndentAndTrailingWhitespaceFromLine(line, indent))
554                .collect(Collectors.joining(System.lineSeparator(), suffix, suffix));
555    }
556
557    /**
558     * Helper method for stripIndentAndInitialNewLineFromTextBlock, strips correct indent
559     * from string, and trailing whitespace, or returns empty string if no text.
560     *
561     * @param line the string to strip indent and trailing whitespace from
562     * @param indent the amount of indent to remove
563     * @return modified string with removed indent and trailing whitespace, or empty string.
564     */
565    private static String stripIndentAndTrailingWhitespaceFromLine(String line, int indent) {
566        final int lastNonWhitespace = lastIndexOfNonWhitespace(line);
567        String returnString = "";
568        if (lastNonWhitespace > 0) {
569            returnString = line.substring(indent, lastNonWhitespace);
570        }
571        return returnString;
572    }
573
574    /**
575     * Helper method for stripIndentAndInitialNewLineFromTextBlock, to determine the smallest
576     * indent in a text block string literal.
577     *
578     * @param lines list of actual text block content, split by line.
579     * @return number of spaces representing the smallest indent in this text block.
580     */
581    private static int getSmallestIndent(List<String> lines) {
582        return lines.stream()
583            .mapToInt(CommonUtil::indexOfNonWhitespace)
584            .min()
585            .orElse(0);
586    }
587
588    /**
589     * Helper method to find the index of the last non-whitespace character in a string.
590     *
591     * @param line the string to find the last index of a non-whitespace character for.
592     * @return the index of the last non-whitespace character.
593     */
594    private static int lastIndexOfNonWhitespace(String line) {
595        int length;
596        for (length = line.length(); length > 0; length--) {
597            if (!Character.isWhitespace(line.charAt(length - 1))) {
598                break;
599            }
600        }
601        return length;
602    }
603}