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.TokenTypes;
032import com.puppycrawl.tools.checkstyle.checks.naming.AccessModifierOption;
033
034/**
035 * Contains utility methods for the checks.
036 *
037 */
038public final class CheckUtil {
039
040    // constants for parseDouble()
041    /** Binary radix. */
042    private static final int BASE_2 = 2;
043
044    /** Octal radix. */
045    private static final int BASE_8 = 8;
046
047    /** Decimal radix. */
048    private static final int BASE_10 = 10;
049
050    /** Hex radix. */
051    private static final int BASE_16 = 16;
052
053    /** Maximum children allowed in setter/getter. */
054    private static final int SETTER_GETTER_MAX_CHILDREN = 7;
055
056    /** Maximum nodes allowed in a body of setter. */
057    private static final int SETTER_BODY_SIZE = 3;
058
059    /** Maximum nodes allowed in a body of getter. */
060    private static final int GETTER_BODY_SIZE = 2;
061
062    /** Pattern matching underscore characters ('_'). */
063    private static final Pattern UNDERSCORE_PATTERN = Pattern.compile("_");
064
065    /** Pattern matching names of setter methods. */
066    private static final Pattern SETTER_PATTERN = Pattern.compile("^set[A-Z].*");
067
068    /** Pattern matching names of getter methods. */
069    private static final Pattern GETTER_PATTERN = Pattern.compile("^(is|get)[A-Z].*");
070
071    /** Compiled pattern for all system newlines. */
072    private static final Pattern ALL_NEW_LINES = Pattern.compile("\\R");
073
074    /** Prevent instances. */
075    private CheckUtil() {
076    }
077
078    /**
079     * Tests whether a method definition AST defines an equals covariant.
080     *
081     * @param ast the method definition AST to test.
082     *     Precondition: ast is a TokenTypes.METHOD_DEF node.
083     * @return true if ast defines an equals covariant.
084     */
085    public static boolean isEqualsMethod(DetailAST ast) {
086        boolean equalsMethod = false;
087
088        if (ast.getType() == TokenTypes.METHOD_DEF) {
089            final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS);
090            final boolean staticOrAbstract =
091                    modifiers.findFirstToken(TokenTypes.LITERAL_STATIC) != null
092                    || modifiers.findFirstToken(TokenTypes.ABSTRACT) != null;
093
094            if (!staticOrAbstract) {
095                final DetailAST nameNode = ast.findFirstToken(TokenTypes.IDENT);
096                final String name = nameNode.getText();
097
098                if ("equals".equals(name)) {
099                    // one parameter?
100                    final DetailAST paramsNode = ast.findFirstToken(TokenTypes.PARAMETERS);
101                    equalsMethod = paramsNode.getChildCount() == 1;
102                }
103            }
104        }
105        return equalsMethod;
106    }
107
108    /**
109     * Returns whether a token represents an ELSE as part of an ELSE / IF set.
110     *
111     * @param ast the token to check
112     * @return whether it is
113     */
114    public static boolean isElseIf(DetailAST ast) {
115        final DetailAST parentAST = ast.getParent();
116
117        return ast.getType() == TokenTypes.LITERAL_IF
118            && (isElse(parentAST) || isElseWithCurlyBraces(parentAST));
119    }
120
121    /**
122     * Returns whether a token represents an ELSE.
123     *
124     * @param ast the token to check
125     * @return whether the token represents an ELSE
126     */
127    private static boolean isElse(DetailAST ast) {
128        return ast.getType() == TokenTypes.LITERAL_ELSE;
129    }
130
131    /**
132     * Returns whether a token represents an SLIST as part of an ELSE
133     * statement.
134     *
135     * @param ast the token to check
136     * @return whether the toke does represent an SLIST as part of an ELSE
137     */
138    private static boolean isElseWithCurlyBraces(DetailAST ast) {
139        return ast.getType() == TokenTypes.SLIST
140            && ast.getChildCount() == 2
141            && isElse(ast.getParent());
142    }
143
144    /**
145     * Returns the value represented by the specified string of the specified
146     * type. Returns 0 for types other than float, double, int, and long.
147     *
148     * @param text the string to be parsed.
149     * @param type the token type of the text. Should be a constant of
150     *     {@link TokenTypes}.
151     * @return the double value represented by the string argument.
152     */
153    public static double parseDouble(String text, int type) {
154        String txt = UNDERSCORE_PATTERN.matcher(text).replaceAll("");
155        final double result;
156        switch (type) {
157            case TokenTypes.NUM_FLOAT:
158            case TokenTypes.NUM_DOUBLE:
159                result = Double.parseDouble(txt);
160                break;
161            case TokenTypes.NUM_INT:
162            case TokenTypes.NUM_LONG:
163                int radix = BASE_10;
164                if (txt.startsWith("0x") || txt.startsWith("0X")) {
165                    radix = BASE_16;
166                    txt = txt.substring(2);
167                }
168                else if (txt.startsWith("0b") || txt.startsWith("0B")) {
169                    radix = BASE_2;
170                    txt = txt.substring(2);
171                }
172                else if (CommonUtil.startsWithChar(txt, '0')) {
173                    radix = BASE_8;
174                    txt = txt.substring(1);
175                }
176                result = parseNumber(txt, radix, type);
177                break;
178            default:
179                result = Double.NaN;
180                break;
181        }
182        return result;
183    }
184
185    /**
186     * Parses the string argument as an integer or a long in the radix specified by
187     * the second argument. The characters in the string must all be digits of
188     * the specified radix.
189     *
190     * @param text the String containing the integer representation to be
191     *     parsed. Precondition: text contains a parsable int.
192     * @param radix the radix to be used while parsing text.
193     * @param type the token type of the text. Should be a constant of
194     *     {@link TokenTypes}.
195     * @return the number represented by the string argument in the specified radix.
196     */
197    private static double parseNumber(final String text, final int radix, final int type) {
198        String txt = text;
199        if (CommonUtil.endsWithChar(txt, 'L') || CommonUtil.endsWithChar(txt, 'l')) {
200            txt = txt.substring(0, txt.length() - 1);
201        }
202        final double result;
203        if (txt.isEmpty()) {
204            result = 0.0;
205        }
206        else {
207            final boolean negative = txt.charAt(0) == '-';
208            if (type == TokenTypes.NUM_INT) {
209                if (negative) {
210                    result = Integer.parseInt(txt, radix);
211                }
212                else {
213                    result = Integer.parseUnsignedInt(txt, radix);
214                }
215            }
216            else {
217                if (negative) {
218                    result = Long.parseLong(txt, radix);
219                }
220                else {
221                    result = Long.parseUnsignedLong(txt, radix);
222                }
223            }
224        }
225        return result;
226    }
227
228    /**
229     * Finds sub-node for given node minimal (line, column) pair.
230     *
231     * @param node the root of tree for search.
232     * @return sub-node with minimal (line, column) pair.
233     */
234    public static DetailAST getFirstNode(final DetailAST node) {
235        DetailAST currentNode = node;
236        DetailAST child = node.getFirstChild();
237        while (child != null) {
238            final DetailAST newNode = getFirstNode(child);
239            if (isBeforeInSource(newNode, currentNode)) {
240                currentNode = newNode;
241            }
242            child = child.getNextSibling();
243        }
244
245        return currentNode;
246    }
247
248    /**
249     * Retrieves whether ast1 is located before ast2.
250     *
251     * @param ast1 the first node.
252     * @param ast2 the second node.
253     * @return true, if ast1 is located before ast2.
254     */
255    public static boolean isBeforeInSource(DetailAST ast1, DetailAST ast2) {
256        return ast1.getLineNo() < ast2.getLineNo()
257            || TokenUtil.areOnSameLine(ast1, ast2)
258                && ast1.getColumnNo() < ast2.getColumnNo();
259    }
260
261    /**
262     * Retrieves the names of the type parameters to the node.
263     *
264     * @param node the parameterized AST node
265     * @return a list of type parameter names
266     */
267    public static List<String> getTypeParameterNames(final DetailAST node) {
268        final DetailAST typeParameters =
269            node.findFirstToken(TokenTypes.TYPE_PARAMETERS);
270
271        final List<String> typeParameterNames = new ArrayList<>();
272        if (typeParameters != null) {
273            final DetailAST typeParam =
274                typeParameters.findFirstToken(TokenTypes.TYPE_PARAMETER);
275            typeParameterNames.add(
276                    typeParam.findFirstToken(TokenTypes.IDENT).getText());
277
278            DetailAST sibling = typeParam.getNextSibling();
279            while (sibling != null) {
280                if (sibling.getType() == TokenTypes.TYPE_PARAMETER) {
281                    typeParameterNames.add(
282                            sibling.findFirstToken(TokenTypes.IDENT).getText());
283                }
284                sibling = sibling.getNextSibling();
285            }
286        }
287
288        return typeParameterNames;
289    }
290
291    /**
292     * Retrieves the type parameters to the node.
293     *
294     * @param node the parameterized AST node
295     * @return a list of type parameter names
296     */
297    public static List<DetailAST> getTypeParameters(final DetailAST node) {
298        final DetailAST typeParameters =
299            node.findFirstToken(TokenTypes.TYPE_PARAMETERS);
300
301        final List<DetailAST> typeParams = new ArrayList<>();
302        if (typeParameters != null) {
303            final DetailAST typeParam =
304                typeParameters.findFirstToken(TokenTypes.TYPE_PARAMETER);
305            typeParams.add(typeParam);
306
307            DetailAST sibling = typeParam.getNextSibling();
308            while (sibling != null) {
309                if (sibling.getType() == TokenTypes.TYPE_PARAMETER) {
310                    typeParams.add(sibling);
311                }
312                sibling = sibling.getNextSibling();
313            }
314        }
315
316        return typeParams;
317    }
318
319    /**
320     * Returns whether an AST represents a setter method.
321     *
322     * @param ast the AST to check with
323     * @return whether the AST represents a setter method
324     */
325    public static boolean isSetterMethod(final DetailAST ast) {
326        boolean setterMethod = false;
327
328        // Check have a method with exactly 7 children which are all that
329        // is allowed in a proper setter method which does not throw any
330        // exceptions.
331        if (ast.getType() == TokenTypes.METHOD_DEF
332                && ast.getChildCount() == SETTER_GETTER_MAX_CHILDREN) {
333            final DetailAST type = ast.findFirstToken(TokenTypes.TYPE);
334            final String name = type.getNextSibling().getText();
335            final boolean matchesSetterFormat = SETTER_PATTERN.matcher(name).matches();
336            final boolean voidReturnType = type.findFirstToken(TokenTypes.LITERAL_VOID) != null;
337
338            final DetailAST params = ast.findFirstToken(TokenTypes.PARAMETERS);
339            final boolean singleParam = params.getChildCount(TokenTypes.PARAMETER_DEF) == 1;
340
341            if (matchesSetterFormat && voidReturnType && singleParam) {
342                // Now verify that the body consists of:
343                // SLIST -> EXPR -> ASSIGN
344                // SEMI
345                // RCURLY
346                final DetailAST slist = ast.findFirstToken(TokenTypes.SLIST);
347
348                if (slist != null && slist.getChildCount() == SETTER_BODY_SIZE) {
349                    final DetailAST expr = slist.getFirstChild();
350                    setterMethod = expr.getFirstChild().getType() == TokenTypes.ASSIGN;
351                }
352            }
353        }
354        return setterMethod;
355    }
356
357    /**
358     * Returns whether an AST represents a getter method.
359     *
360     * @param ast the AST to check with
361     * @return whether the AST represents a getter method
362     */
363    public static boolean isGetterMethod(final DetailAST ast) {
364        boolean getterMethod = false;
365
366        // Check have a method with exactly 7 children which are all that
367        // is allowed in a proper getter method which does not throw any
368        // exceptions.
369        if (ast.getType() == TokenTypes.METHOD_DEF
370                && ast.getChildCount() == SETTER_GETTER_MAX_CHILDREN) {
371            final DetailAST type = ast.findFirstToken(TokenTypes.TYPE);
372            final String name = type.getNextSibling().getText();
373            final boolean matchesGetterFormat = GETTER_PATTERN.matcher(name).matches();
374            final boolean noVoidReturnType = type.findFirstToken(TokenTypes.LITERAL_VOID) == null;
375
376            final DetailAST params = ast.findFirstToken(TokenTypes.PARAMETERS);
377            final boolean noParams = params.getChildCount(TokenTypes.PARAMETER_DEF) == 0;
378
379            if (matchesGetterFormat && noVoidReturnType && noParams) {
380                // Now verify that the body consists of:
381                // SLIST -> RETURN
382                // RCURLY
383                final DetailAST slist = ast.findFirstToken(TokenTypes.SLIST);
384
385                if (slist != null && slist.getChildCount() == GETTER_BODY_SIZE) {
386                    final DetailAST expr = slist.getFirstChild();
387                    getterMethod = expr.getType() == TokenTypes.LITERAL_RETURN;
388                }
389            }
390        }
391        return getterMethod;
392    }
393
394    /**
395     * Checks whether a method is a not void one.
396     *
397     * @param methodDefAst the method node.
398     * @return true if method is a not void one.
399     */
400    public static boolean isNonVoidMethod(DetailAST methodDefAst) {
401        boolean returnValue = false;
402        if (methodDefAst.getType() == TokenTypes.METHOD_DEF) {
403            final DetailAST typeAST = methodDefAst.findFirstToken(TokenTypes.TYPE);
404            if (typeAST.findFirstToken(TokenTypes.LITERAL_VOID) == null) {
405                returnValue = true;
406            }
407        }
408        return returnValue;
409    }
410
411    /**
412     * Checks whether a parameter is a receiver.
413     *
414     * @param parameterDefAst the parameter node.
415     * @return true if the parameter is a receiver.
416     */
417    public static boolean isReceiverParameter(DetailAST parameterDefAst) {
418        return parameterDefAst.getType() == TokenTypes.PARAMETER_DEF
419                && parameterDefAst.findFirstToken(TokenTypes.IDENT) == null;
420    }
421
422    /**
423     * Returns the access modifier of the method/constructor at the specified AST. If
424     * the method is in an interface or annotation block, the access modifier is assumed
425     * to be public.
426     *
427     * @param ast the token of the method/constructor.
428     * @return the access modifier of the method/constructor.
429     */
430    public static AccessModifierOption getAccessModifierFromModifiersToken(DetailAST ast) {
431        final DetailAST modsToken = ast.findFirstToken(TokenTypes.MODIFIERS);
432        AccessModifierOption accessModifier =
433                getAccessModifierFromModifiersTokenDirectly(modsToken);
434
435        if (accessModifier == AccessModifierOption.PACKAGE) {
436            if (ScopeUtil.isInEnumBlock(ast) && ast.getType() == TokenTypes.CTOR_DEF) {
437                accessModifier = AccessModifierOption.PRIVATE;
438            }
439            else if (ScopeUtil.isInInterfaceOrAnnotationBlock(ast)) {
440                accessModifier = AccessModifierOption.PUBLIC;
441            }
442        }
443
444        return accessModifier;
445    }
446
447    /**
448     * Returns {@link AccessModifierOption} based on the information about access modifier
449     * taken from the given token of type {@link TokenTypes#MODIFIERS}.
450     *
451     * @param modifiersToken token of type {@link TokenTypes#MODIFIERS}.
452     * @return {@link AccessModifierOption}.
453     * @throws IllegalArgumentException when expected non-null modifiersToken with type 'MODIFIERS'
454     */
455    private static AccessModifierOption getAccessModifierFromModifiersTokenDirectly(
456            DetailAST modifiersToken) {
457        if (modifiersToken == null) {
458            throw new IllegalArgumentException("expected non-null AST-token with type 'MODIFIERS'");
459        }
460
461        AccessModifierOption accessModifier = AccessModifierOption.PACKAGE;
462        for (DetailAST token = modifiersToken.getFirstChild(); token != null;
463             token = token.getNextSibling()) {
464            final int tokenType = token.getType();
465            if (tokenType == TokenTypes.LITERAL_PUBLIC) {
466                accessModifier = AccessModifierOption.PUBLIC;
467            }
468            else if (tokenType == TokenTypes.LITERAL_PROTECTED) {
469                accessModifier = AccessModifierOption.PROTECTED;
470            }
471            else if (tokenType == TokenTypes.LITERAL_PRIVATE) {
472                accessModifier = AccessModifierOption.PRIVATE;
473            }
474        }
475        return accessModifier;
476    }
477
478    /**
479     * Returns the access modifier of the surrounding "block".
480     *
481     * @param node the node to return the access modifier for
482     * @return the access modifier of the surrounding block
483     */
484    public static AccessModifierOption getSurroundingAccessModifier(DetailAST node) {
485        AccessModifierOption returnValue = null;
486        for (DetailAST token = node.getParent();
487             returnValue == null && !TokenUtil.isRootNode(token);
488             token = token.getParent()) {
489            final int type = token.getType();
490            if (type == TokenTypes.CLASS_DEF
491                || type == TokenTypes.INTERFACE_DEF
492                || type == TokenTypes.ANNOTATION_DEF
493                || type == TokenTypes.ENUM_DEF) {
494                returnValue = getAccessModifierFromModifiersToken(token);
495            }
496            else if (type == TokenTypes.LITERAL_NEW) {
497                break;
498            }
499        }
500
501        return returnValue;
502    }
503
504    /**
505     * Create set of class names and short class names.
506     *
507     * @param classNames array of class names.
508     * @return set of class names and short class names.
509     */
510    public static Set<String> parseClassNames(String... classNames) {
511        final Set<String> illegalClassNames = new HashSet<>();
512        for (final String name : classNames) {
513            illegalClassNames.add(name);
514            final int lastDot = name.lastIndexOf('.');
515            if (lastDot != -1 && lastDot < name.length() - 1) {
516                final String shortName = name
517                        .substring(name.lastIndexOf('.') + 1);
518                illegalClassNames.add(shortName);
519            }
520        }
521        return illegalClassNames;
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 list 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(List<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}