001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2019 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.AccessModifier;
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     * @param typeAST a type node.
077     * @return {@code FullIdent} for given type.
078     */
079    public static FullIdent createFullType(final DetailAST typeAST) {
080        DetailAST ast = typeAST;
081
082        // ignore array part of type
083        while (ast.findFirstToken(TokenTypes.ARRAY_DECLARATOR) != null) {
084            ast = ast.findFirstToken(TokenTypes.ARRAY_DECLARATOR);
085        }
086
087        return FullIdent.createFullIdent(ast.getFirstChild());
088    }
089
090    /**
091     * Tests whether a method definition AST defines an equals covariant.
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     * @param ast the token to check
122     * @return whether it is
123     */
124    public static boolean isElseIf(DetailAST ast) {
125        final DetailAST parentAST = ast.getParent();
126
127        return ast.getType() == TokenTypes.LITERAL_IF
128            && (isElse(parentAST) || isElseWithCurlyBraces(parentAST));
129    }
130
131    /**
132     * Returns whether a token represents an ELSE.
133     * @param ast the token to check
134     * @return whether the token represents an ELSE
135     */
136    private static boolean isElse(DetailAST ast) {
137        return ast.getType() == TokenTypes.LITERAL_ELSE;
138    }
139
140    /**
141     * Returns whether a token represents an SLIST as part of an ELSE
142     * statement.
143     * @param ast the token to check
144     * @return whether the toke does represent an SLIST as part of an ELSE
145     */
146    private static boolean isElseWithCurlyBraces(DetailAST ast) {
147        return ast.getType() == TokenTypes.SLIST
148            && ast.getChildCount() == 2
149            && isElse(ast.getParent());
150    }
151
152    /**
153     * Returns the value represented by the specified string of the specified
154     * type. Returns 0 for types other than float, double, int, and long.
155     * @param text the string to be parsed.
156     * @param type the token type of the text. Should be a constant of
157     *     {@link TokenTypes}.
158     * @return the double value represented by the string argument.
159     */
160    public static double parseDouble(String text, int type) {
161        String txt = UNDERSCORE_PATTERN.matcher(text).replaceAll("");
162        final double result;
163        switch (type) {
164            case TokenTypes.NUM_FLOAT:
165            case TokenTypes.NUM_DOUBLE:
166                result = Double.parseDouble(txt);
167                break;
168            case TokenTypes.NUM_INT:
169            case TokenTypes.NUM_LONG:
170                int radix = BASE_10;
171                if (txt.startsWith("0x") || txt.startsWith("0X")) {
172                    radix = BASE_16;
173                    txt = txt.substring(2);
174                }
175                else if (txt.startsWith("0b") || txt.startsWith("0B")) {
176                    radix = BASE_2;
177                    txt = txt.substring(2);
178                }
179                else if (CommonUtil.startsWithChar(txt, '0')) {
180                    radix = BASE_8;
181                    txt = txt.substring(1);
182                }
183                result = parseNumber(txt, radix, type);
184                break;
185            default:
186                result = Double.NaN;
187                break;
188        }
189        return result;
190    }
191
192    /**
193     * Parses the string argument as an integer or a long in the radix specified by
194     * the second argument. The characters in the string must all be digits of
195     * the specified radix.
196     * @param text the String containing the integer representation to be
197     *     parsed. Precondition: text contains a parsable int.
198     * @param radix the radix to be used while parsing text.
199     * @param type the token type of the text. Should be a constant of
200     *     {@link TokenTypes}.
201     * @return the number represented by the string argument in the specified radix.
202     */
203    private static double parseNumber(final String text, final int radix, final int type) {
204        String txt = text;
205        if (CommonUtil.endsWithChar(txt, 'L') || CommonUtil.endsWithChar(txt, 'l')) {
206            txt = txt.substring(0, txt.length() - 1);
207        }
208        final double result;
209        if (txt.isEmpty()) {
210            result = 0.0;
211        }
212        else {
213            final boolean negative = txt.charAt(0) == '-';
214            if (type == TokenTypes.NUM_INT) {
215                if (negative) {
216                    result = Integer.parseInt(txt, radix);
217                }
218                else {
219                    result = Integer.parseUnsignedInt(txt, radix);
220                }
221            }
222            else {
223                if (negative) {
224                    result = Long.parseLong(txt, radix);
225                }
226                else {
227                    result = Long.parseUnsignedLong(txt, radix);
228                }
229            }
230        }
231        return result;
232    }
233
234    /**
235     * Finds sub-node for given node minimal (line, column) pair.
236     * @param node the root of tree for search.
237     * @return sub-node with minimal (line, column) pair.
238     */
239    public static DetailAST getFirstNode(final DetailAST node) {
240        DetailAST currentNode = node;
241        DetailAST child = node.getFirstChild();
242        while (child != null) {
243            final DetailAST newNode = getFirstNode(child);
244            if (isBeforeInSource(newNode, currentNode)) {
245                currentNode = newNode;
246            }
247            child = child.getNextSibling();
248        }
249
250        return currentNode;
251    }
252
253    /**
254     * Retrieves whether ast1 is located before ast2.
255     * @param ast1 the first node.
256     * @param ast2 the second node.
257     * @return true, if ast1 is located before ast2.
258     */
259    public static boolean isBeforeInSource(DetailAST ast1, DetailAST ast2) {
260        return ast1.getLineNo() < ast2.getLineNo()
261            || ast1.getLineNo() == ast2.getLineNo()
262                && ast1.getColumnNo() < ast2.getColumnNo();
263    }
264
265    /**
266     * Retrieves the names of the type parameters to the node.
267     * @param node the parameterized AST node
268     * @return a list of type parameter names
269     */
270    public static List<String> getTypeParameterNames(final DetailAST node) {
271        final DetailAST typeParameters =
272            node.findFirstToken(TokenTypes.TYPE_PARAMETERS);
273
274        final List<String> typeParameterNames = new ArrayList<>();
275        if (typeParameters != null) {
276            final DetailAST typeParam =
277                typeParameters.findFirstToken(TokenTypes.TYPE_PARAMETER);
278            typeParameterNames.add(
279                    typeParam.findFirstToken(TokenTypes.IDENT).getText());
280
281            DetailAST sibling = typeParam.getNextSibling();
282            while (sibling != null) {
283                if (sibling.getType() == TokenTypes.TYPE_PARAMETER) {
284                    typeParameterNames.add(
285                            sibling.findFirstToken(TokenTypes.IDENT).getText());
286                }
287                sibling = sibling.getNextSibling();
288            }
289        }
290
291        return typeParameterNames;
292    }
293
294    /**
295     * Retrieves the type parameters to the node.
296     * @param node the parameterized AST node
297     * @return a list of type parameter names
298     */
299    public static List<DetailAST> getTypeParameters(final DetailAST node) {
300        final DetailAST typeParameters =
301            node.findFirstToken(TokenTypes.TYPE_PARAMETERS);
302
303        final List<DetailAST> typeParams = new ArrayList<>();
304        if (typeParameters != null) {
305            final DetailAST typeParam =
306                typeParameters.findFirstToken(TokenTypes.TYPE_PARAMETER);
307            typeParams.add(typeParam);
308
309            DetailAST sibling = typeParam.getNextSibling();
310            while (sibling != null) {
311                if (sibling.getType() == TokenTypes.TYPE_PARAMETER) {
312                    typeParams.add(sibling);
313                }
314                sibling = sibling.getNextSibling();
315            }
316        }
317
318        return typeParams;
319    }
320
321    /**
322     * Returns whether an AST represents a setter method.
323     * @param ast the AST to check with
324     * @return whether the AST represents a setter method
325     */
326    public static boolean isSetterMethod(final DetailAST ast) {
327        boolean setterMethod = false;
328
329        // Check have a method with exactly 7 children which are all that
330        // is allowed in a proper setter method which does not throw any
331        // exceptions.
332        if (ast.getType() == TokenTypes.METHOD_DEF
333                && ast.getChildCount() == SETTER_GETTER_MAX_CHILDREN) {
334            final DetailAST type = ast.findFirstToken(TokenTypes.TYPE);
335            final String name = type.getNextSibling().getText();
336            final boolean matchesSetterFormat = SETTER_PATTERN.matcher(name).matches();
337            final boolean voidReturnType = type.findFirstToken(TokenTypes.LITERAL_VOID) != null;
338
339            final DetailAST params = ast.findFirstToken(TokenTypes.PARAMETERS);
340            final boolean singleParam = params.getChildCount(TokenTypes.PARAMETER_DEF) == 1;
341
342            if (matchesSetterFormat && voidReturnType && singleParam) {
343                // Now verify that the body consists of:
344                // SLIST -> EXPR -> ASSIGN
345                // SEMI
346                // RCURLY
347                final DetailAST slist = ast.findFirstToken(TokenTypes.SLIST);
348
349                if (slist != null && slist.getChildCount() == SETTER_BODY_SIZE) {
350                    final DetailAST expr = slist.getFirstChild();
351                    setterMethod = expr.getFirstChild().getType() == TokenTypes.ASSIGN;
352                }
353            }
354        }
355        return setterMethod;
356    }
357
358    /**
359     * Returns whether an AST represents a getter method.
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 {@link AccessModifier} based on the information about access modifier
424     * taken from the given token of type {@link TokenTypes#MODIFIERS}.
425     * @param modifiersToken token of type {@link TokenTypes#MODIFIERS}.
426     * @return {@link AccessModifier}.
427     */
428    public static AccessModifier getAccessModifierFromModifiersToken(DetailAST modifiersToken) {
429        if (modifiersToken == null || modifiersToken.getType() != TokenTypes.MODIFIERS) {
430            throw new IllegalArgumentException("expected non-null AST-token with type 'MODIFIERS'");
431        }
432
433        // default access modifier
434        AccessModifier accessModifier = AccessModifier.PACKAGE;
435        for (DetailAST token = modifiersToken.getFirstChild(); token != null;
436             token = token.getNextSibling()) {
437            final int tokenType = token.getType();
438            if (tokenType == TokenTypes.LITERAL_PUBLIC) {
439                accessModifier = AccessModifier.PUBLIC;
440            }
441            else if (tokenType == TokenTypes.LITERAL_PROTECTED) {
442                accessModifier = AccessModifier.PROTECTED;
443            }
444            else if (tokenType == TokenTypes.LITERAL_PRIVATE) {
445                accessModifier = AccessModifier.PRIVATE;
446            }
447        }
448        return accessModifier;
449    }
450
451    /**
452     * Create set of class names and short class names.
453     *
454     * @param classNames array of class names.
455     * @return set of class names and short class names.
456     */
457    public static Set<String> parseClassNames(String... classNames) {
458        final Set<String> illegalClassNames = new HashSet<>();
459        for (final String name : classNames) {
460            illegalClassNames.add(name);
461            final int lastDot = name.lastIndexOf('.');
462            if (lastDot != -1 && lastDot < name.length() - 1) {
463                final String shortName = name
464                        .substring(name.lastIndexOf('.') + 1);
465                illegalClassNames.add(shortName);
466            }
467        }
468        return illegalClassNames;
469    }
470
471}