001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2017 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 antlr.collections.AST;
029import com.puppycrawl.tools.checkstyle.api.DetailAST;
030import com.puppycrawl.tools.checkstyle.api.FullIdent;
031import com.puppycrawl.tools.checkstyle.api.TokenTypes;
032import com.puppycrawl.tools.checkstyle.checks.naming.AccessModifier;
033
034/**
035 * Contains utility methods for the checks.
036 *
037 * @author Oliver Burn
038 * @author <a href="mailto:simon@redhillconsulting.com.au">Simon Harris</a>
039 * @author o_sukhodolsky
040 */
041public final class CheckUtils {
042    // constants for parseDouble()
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 CheckUtils() {
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 = modifiers.branchContains(TokenTypes.LITERAL_STATIC)
102                    || modifiers.branchContains(TokenTypes.ABSTRACT);
103
104            if (!staticOrAbstract) {
105                final DetailAST nameNode = ast.findFirstToken(TokenTypes.IDENT);
106                final String name = nameNode.getText();
107
108                if ("equals".equals(name)) {
109                    // one parameter?
110                    final DetailAST paramsNode = ast.findFirstToken(TokenTypes.PARAMETERS);
111                    equalsMethod = paramsNode.getChildCount() == 1;
112                }
113            }
114        }
115        return equalsMethod;
116    }
117
118    /**
119     * Returns whether a token represents an ELSE as part of an ELSE / IF set.
120     * @param ast the token to check
121     * @return whether it is
122     */
123    public static boolean isElseIf(DetailAST ast) {
124        final DetailAST parentAST = ast.getParent();
125
126        return ast.getType() == TokenTypes.LITERAL_IF
127            && (isElse(parentAST) || isElseWithCurlyBraces(parentAST));
128    }
129
130    /**
131     * Returns whether a token represents an ELSE.
132     * @param ast the token to check
133     * @return whether the token represents an ELSE
134     */
135    private static boolean isElse(DetailAST ast) {
136        return ast.getType() == TokenTypes.LITERAL_ELSE;
137    }
138
139    /**
140     * Returns whether a token represents an SLIST as part of an ELSE
141     * statement.
142     * @param ast the token to check
143     * @return whether the toke does represent an SLIST as part of an ELSE
144     */
145    private static boolean isElseWithCurlyBraces(DetailAST ast) {
146        return ast.getType() == TokenTypes.SLIST
147            && ast.getChildCount() == 2
148            && isElse(ast.getParent());
149    }
150
151    /**
152     * Returns the value represented by the specified string of the specified
153     * type. Returns 0 for types other than float, double, int, and long.
154     * @param text the string to be parsed.
155     * @param type the token type of the text. Should be a constant of
156     * {@link TokenTypes}.
157     * @return the double value represented by the string argument.
158     */
159    public static double parseDouble(String text, int type) {
160        String txt = UNDERSCORE_PATTERN.matcher(text).replaceAll("");
161        double result = 0;
162        switch (type) {
163            case TokenTypes.NUM_FLOAT:
164            case TokenTypes.NUM_DOUBLE:
165                result = Double.parseDouble(txt);
166                break;
167            case TokenTypes.NUM_INT:
168            case TokenTypes.NUM_LONG:
169                int radix = BASE_10;
170                if (txt.startsWith("0x") || txt.startsWith("0X")) {
171                    radix = BASE_16;
172                    txt = txt.substring(2);
173                }
174                else if (txt.charAt(0) == '0') {
175                    radix = BASE_8;
176                    txt = txt.substring(1);
177                }
178                if (CommonUtils.endsWithChar(txt, 'L') || CommonUtils.endsWithChar(txt, 'l')) {
179                    txt = txt.substring(0, txt.length() - 1);
180                }
181                if (!txt.isEmpty()) {
182                    if (type == TokenTypes.NUM_INT) {
183                        result = parseInt(txt, radix);
184                    }
185                    else {
186                        result = parseLong(txt, radix);
187                    }
188                }
189                break;
190            default:
191                break;
192        }
193        return result;
194    }
195
196    /**
197     * Parses the string argument as a signed integer in the radix specified by
198     * the second argument. The characters in the string must all be digits of
199     * the specified radix. Handles negative values, which method
200     * java.lang.Integer.parseInt(String, int) does not.
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     * @return the integer represented by the string argument in the specified radix.
205     */
206    private static int parseInt(String text, int radix) {
207        int result = 0;
208        final int max = text.length();
209        for (int i = 0; i < max; i++) {
210            final int digit = Character.digit(text.charAt(i), radix);
211            result *= radix;
212            result += digit;
213        }
214        return result;
215    }
216
217    /**
218     * Parses the string argument as a signed long in the radix specified by
219     * the second argument. The characters in the string must all be digits of
220     * the specified radix. Handles negative values, which method
221     * java.lang.Integer.parseInt(String, int) does not.
222     * @param text the String containing the integer representation to be
223     *     parsed. Precondition: text contains a parsable int.
224     * @param radix the radix to be used while parsing text.
225     * @return the long represented by the string argument in the specified radix.
226     */
227    private static long parseLong(String text, int radix) {
228        long result = 0;
229        final int max = text.length();
230        for (int i = 0; i < max; i++) {
231            final int digit = Character.digit(text.charAt(i), radix);
232            result *= radix;
233            result += digit;
234        }
235        return result;
236    }
237
238    /**
239     * Finds sub-node for given node minimal (line, column) pair.
240     * @param node the root of tree for search.
241     * @return sub-node with minimal (line, column) pair.
242     */
243    public static DetailAST getFirstNode(final DetailAST node) {
244        DetailAST currentNode = node;
245        DetailAST child = node.getFirstChild();
246        while (child != null) {
247            final DetailAST newNode = getFirstNode(child);
248            if (newNode.getLineNo() < currentNode.getLineNo()
249                || newNode.getLineNo() == currentNode.getLineNo()
250                    && newNode.getColumnNo() < currentNode.getColumnNo()) {
251                currentNode = newNode;
252            }
253            child = child.getNextSibling();
254        }
255
256        return currentNode;
257    }
258
259    /**
260     * Retrieves the names of the type parameters to the node.
261     * @param node the parameterized AST node
262     * @return a list of type parameter names
263     */
264    public static List<String> getTypeParameterNames(final DetailAST node) {
265        final DetailAST typeParameters =
266            node.findFirstToken(TokenTypes.TYPE_PARAMETERS);
267
268        final List<String> typeParameterNames = new ArrayList<>();
269        if (typeParameters != null) {
270            final DetailAST typeParam =
271                typeParameters.findFirstToken(TokenTypes.TYPE_PARAMETER);
272            typeParameterNames.add(
273                    typeParam.findFirstToken(TokenTypes.IDENT).getText());
274
275            DetailAST sibling = typeParam.getNextSibling();
276            while (sibling != null) {
277                if (sibling.getType() == TokenTypes.TYPE_PARAMETER) {
278                    typeParameterNames.add(
279                            sibling.findFirstToken(TokenTypes.IDENT).getText());
280                }
281                sibling = sibling.getNextSibling();
282            }
283        }
284
285        return typeParameterNames;
286    }
287
288    /**
289     * Retrieves the type parameters to the node.
290     * @param node the parameterized AST node
291     * @return a list of type parameter names
292     */
293    public static List<DetailAST> getTypeParameters(final DetailAST node) {
294        final DetailAST typeParameters =
295            node.findFirstToken(TokenTypes.TYPE_PARAMETERS);
296
297        final List<DetailAST> typeParams = new ArrayList<>();
298        if (typeParameters != null) {
299            final DetailAST typeParam =
300                typeParameters.findFirstToken(TokenTypes.TYPE_PARAMETER);
301            typeParams.add(typeParam);
302
303            DetailAST sibling = typeParam.getNextSibling();
304            while (sibling != null) {
305                if (sibling.getType() == TokenTypes.TYPE_PARAMETER) {
306                    typeParams.add(sibling);
307                }
308                sibling = sibling.getNextSibling();
309            }
310        }
311
312        return typeParams;
313    }
314
315    /**
316     * Returns whether an AST represents a setter method.
317     * @param ast the AST to check with
318     * @return whether the AST represents a setter method
319     */
320    public static boolean isSetterMethod(final DetailAST ast) {
321        boolean setterMethod = false;
322
323        // Check have a method with exactly 7 children which are all that
324        // is allowed in a proper setter method which does not throw any
325        // exceptions.
326        if (ast.getType() == TokenTypes.METHOD_DEF
327                && ast.getChildCount() == SETTER_GETTER_MAX_CHILDREN) {
328
329            final DetailAST type = ast.findFirstToken(TokenTypes.TYPE);
330            final String name = type.getNextSibling().getText();
331            final boolean matchesSetterFormat = SETTER_PATTERN.matcher(name).matches();
332            final boolean voidReturnType = type.findFirstToken(TokenTypes.LITERAL_VOID) != null;
333
334            final DetailAST params = ast.findFirstToken(TokenTypes.PARAMETERS);
335            final boolean singleParam = params.getChildCount(TokenTypes.PARAMETER_DEF) == 1;
336
337            if (matchesSetterFormat && voidReturnType && singleParam) {
338                // Now verify that the body consists of:
339                // SLIST -> EXPR -> ASSIGN
340                // SEMI
341                // RCURLY
342                final DetailAST slist = ast.findFirstToken(TokenTypes.SLIST);
343
344                if (slist != null && slist.getChildCount() == SETTER_BODY_SIZE) {
345                    final DetailAST expr = slist.getFirstChild();
346                    setterMethod = expr.getFirstChild().getType() == TokenTypes.ASSIGN;
347                }
348            }
349        }
350        return setterMethod;
351    }
352
353    /**
354     * Returns whether an AST represents a getter method.
355     * @param ast the AST to check with
356     * @return whether the AST represents a getter method
357     */
358    public static boolean isGetterMethod(final DetailAST ast) {
359        boolean getterMethod = false;
360
361        // Check have a method with exactly 7 children which are all that
362        // is allowed in a proper getter method which does not throw any
363        // exceptions.
364        if (ast.getType() == TokenTypes.METHOD_DEF
365                && ast.getChildCount() == SETTER_GETTER_MAX_CHILDREN) {
366
367            final DetailAST type = ast.findFirstToken(TokenTypes.TYPE);
368            final String name = type.getNextSibling().getText();
369            final boolean matchesGetterFormat = GETTER_PATTERN.matcher(name).matches();
370            final boolean noVoidReturnType = type.findFirstToken(TokenTypes.LITERAL_VOID) == null;
371
372            final DetailAST params = ast.findFirstToken(TokenTypes.PARAMETERS);
373            final boolean noParams = params.getChildCount(TokenTypes.PARAMETER_DEF) == 0;
374
375            if (matchesGetterFormat && noVoidReturnType && noParams) {
376                // Now verify that the body consists of:
377                // SLIST -> RETURN
378                // RCURLY
379                final DetailAST slist = ast.findFirstToken(TokenTypes.SLIST);
380
381                if (slist != null && slist.getChildCount() == GETTER_BODY_SIZE) {
382                    final DetailAST expr = slist.getFirstChild();
383                    getterMethod = expr.getType() == TokenTypes.LITERAL_RETURN;
384                }
385            }
386        }
387        return getterMethod;
388    }
389
390    /**
391     * Checks whether a method is a not void one.
392     *
393     * @param methodDefAst the method node.
394     * @return true if method is a not void one.
395     */
396    public static boolean isNonVoidMethod(DetailAST methodDefAst) {
397        boolean returnValue = false;
398        if (methodDefAst.getType() == TokenTypes.METHOD_DEF) {
399            final DetailAST typeAST = methodDefAst.findFirstToken(TokenTypes.TYPE);
400            if (typeAST.findFirstToken(TokenTypes.LITERAL_VOID) == null) {
401                returnValue = true;
402            }
403        }
404        return returnValue;
405    }
406
407    /**
408     * Checks whether a parameter is a receiver.
409     *
410     * @param parameterDefAst the parameter node.
411     * @return true if the parameter is a receiver.
412     */
413    public static boolean isReceiverParameter(DetailAST parameterDefAst) {
414        boolean returnValue = false;
415        if (parameterDefAst.getType() == TokenTypes.PARAMETER_DEF
416                && parameterDefAst.findFirstToken(TokenTypes.IDENT) == null) {
417            returnValue = parameterDefAst.branchContains(TokenTypes.LITERAL_THIS);
418        }
419        return returnValue;
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 (AST token = modifiersToken.getFirstChild(); token != null;
436             token = token.getNextSibling()) {
437
438            final int tokenType = token.getType();
439            if (tokenType == TokenTypes.LITERAL_PUBLIC) {
440                accessModifier = AccessModifier.PUBLIC;
441            }
442            else if (tokenType == TokenTypes.LITERAL_PROTECTED) {
443                accessModifier = AccessModifier.PROTECTED;
444            }
445            else if (tokenType == TokenTypes.LITERAL_PRIVATE) {
446                accessModifier = AccessModifier.PRIVATE;
447            }
448        }
449        return accessModifier;
450    }
451
452    /**
453     * Create set of class names and short class names.
454     *
455     * @param classNames array of class names.
456     * @return set of class names and short class names.
457     */
458    public static Set<String> parseClassNames(String... classNames) {
459        final Set<String> illegalClassNames = new HashSet<>();
460        for (final String name : classNames) {
461            illegalClassNames.add(name);
462            final int lastDot = name.lastIndexOf('.');
463            if (lastDot != -1 && lastDot < name.length() - 1) {
464                final String shortName = name
465                        .substring(name.lastIndexOf('.') + 1);
466                illegalClassNames.add(shortName);
467            }
468        }
469        return illegalClassNames;
470    }
471}