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