001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2018 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
043    // constants for parseDouble()
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    /** Prevent instances. */
072    private CheckUtils() {
073    }
074
075    /**
076     * Creates {@code FullIdent} for given type node.
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     * @param ast the method definition AST to test.
094     *     Precondition: ast is a TokenTypes.METHOD_DEF node.
095     * @return true if ast defines an equals covariant.
096     */
097    public static boolean isEqualsMethod(DetailAST ast) {
098        boolean equalsMethod = false;
099
100        if (ast.getType() == TokenTypes.METHOD_DEF) {
101            final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS);
102            final boolean staticOrAbstract =
103                    modifiers.findFirstToken(TokenTypes.LITERAL_STATIC) != null
104                    || modifiers.findFirstToken(TokenTypes.ABSTRACT) != null;
105
106            if (!staticOrAbstract) {
107                final DetailAST nameNode = ast.findFirstToken(TokenTypes.IDENT);
108                final String name = nameNode.getText();
109
110                if ("equals".equals(name)) {
111                    // one parameter?
112                    final DetailAST paramsNode = ast.findFirstToken(TokenTypes.PARAMETERS);
113                    equalsMethod = paramsNode.getChildCount() == 1;
114                }
115            }
116        }
117        return equalsMethod;
118    }
119
120    /**
121     * Returns whether a token represents an ELSE as part of an ELSE / IF set.
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     * @param ast the token to check
135     * @return whether the token represents an ELSE
136     */
137    private static boolean isElse(DetailAST ast) {
138        return ast.getType() == TokenTypes.LITERAL_ELSE;
139    }
140
141    /**
142     * Returns whether a token represents an SLIST as part of an ELSE
143     * statement.
144     * @param ast the token to check
145     * @return whether the toke does represent an SLIST as part of an ELSE
146     */
147    private static boolean isElseWithCurlyBraces(DetailAST ast) {
148        return ast.getType() == TokenTypes.SLIST
149            && ast.getChildCount() == 2
150            && isElse(ast.getParent());
151    }
152
153    /**
154     * Returns the value represented by the specified string of the specified
155     * type. Returns 0 for types other than float, double, int, and long.
156     * @param text the string to be parsed.
157     * @param type the token type of the text. Should be a constant of
158     * {@link TokenTypes}.
159     * @return the double value represented by the string argument.
160     */
161    public static double parseDouble(String text, int type) {
162        String txt = UNDERSCORE_PATTERN.matcher(text).replaceAll("");
163        double result = 0;
164        switch (type) {
165            case TokenTypes.NUM_FLOAT:
166            case TokenTypes.NUM_DOUBLE:
167                result = Double.parseDouble(txt);
168                break;
169            case TokenTypes.NUM_INT:
170            case TokenTypes.NUM_LONG:
171                int radix = BASE_10;
172                if (txt.startsWith("0x") || txt.startsWith("0X")) {
173                    radix = BASE_16;
174                    txt = txt.substring(2);
175                }
176                else if (txt.charAt(0) == '0') {
177                    radix = BASE_8;
178                    txt = txt.substring(1);
179                }
180                if (CommonUtils.endsWithChar(txt, 'L') || CommonUtils.endsWithChar(txt, 'l')) {
181                    txt = txt.substring(0, txt.length() - 1);
182                }
183                if (!txt.isEmpty()) {
184                    if (type == TokenTypes.NUM_INT) {
185                        result = parseInt(txt, radix);
186                    }
187                    else {
188                        result = parseLong(txt, radix);
189                    }
190                }
191                break;
192            default:
193                break;
194        }
195        return result;
196    }
197
198    /**
199     * Parses the string argument as a signed integer in the radix specified by
200     * the second argument. The characters in the string must all be digits of
201     * the specified radix. Handles negative values, which method
202     * java.lang.Integer.parseInt(String, int) does not.
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     * @return the integer represented by the string argument in the specified radix.
207     */
208    private static int parseInt(String text, int radix) {
209        int result = 0;
210        final int max = text.length();
211        for (int i = 0; i < max; i++) {
212            final int digit = Character.digit(text.charAt(i), radix);
213            result *= radix;
214            result += digit;
215        }
216        return result;
217    }
218
219    /**
220     * Parses the string argument as a signed long in the radix specified by
221     * the second argument. The characters in the string must all be digits of
222     * the specified radix. Handles negative values, which method
223     * java.lang.Integer.parseInt(String, int) does not.
224     * @param text the String containing the integer representation to be
225     *     parsed. Precondition: text contains a parsable int.
226     * @param radix the radix to be used while parsing text.
227     * @return the long represented by the string argument in the specified radix.
228     */
229    private static long parseLong(String text, int radix) {
230        long result = 0;
231        final int max = text.length();
232        for (int i = 0; i < max; i++) {
233            final int digit = Character.digit(text.charAt(i), radix);
234            result *= radix;
235            result += digit;
236        }
237        return result;
238    }
239
240    /**
241     * Finds sub-node for given node minimal (line, column) pair.
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 (newNode.getLineNo() < currentNode.getLineNo()
251                || newNode.getLineNo() == currentNode.getLineNo()
252                    && newNode.getColumnNo() < currentNode.getColumnNo()) {
253                currentNode = newNode;
254            }
255            child = child.getNextSibling();
256        }
257
258        return currentNode;
259    }
260
261    /**
262     * Retrieves the names of the type parameters to the node.
263     * @param node the parameterized AST node
264     * @return a list of type parameter names
265     */
266    public static List<String> getTypeParameterNames(final DetailAST node) {
267        final DetailAST typeParameters =
268            node.findFirstToken(TokenTypes.TYPE_PARAMETERS);
269
270        final List<String> typeParameterNames = new ArrayList<>();
271        if (typeParameters != null) {
272            final DetailAST typeParam =
273                typeParameters.findFirstToken(TokenTypes.TYPE_PARAMETER);
274            typeParameterNames.add(
275                    typeParam.findFirstToken(TokenTypes.IDENT).getText());
276
277            DetailAST sibling = typeParam.getNextSibling();
278            while (sibling != null) {
279                if (sibling.getType() == TokenTypes.TYPE_PARAMETER) {
280                    typeParameterNames.add(
281                            sibling.findFirstToken(TokenTypes.IDENT).getText());
282                }
283                sibling = sibling.getNextSibling();
284            }
285        }
286
287        return typeParameterNames;
288    }
289
290    /**
291     * Retrieves the type parameters to the node.
292     * @param node the parameterized AST node
293     * @return a list of type parameter names
294     */
295    public static List<DetailAST> getTypeParameters(final DetailAST node) {
296        final DetailAST typeParameters =
297            node.findFirstToken(TokenTypes.TYPE_PARAMETERS);
298
299        final List<DetailAST> typeParams = new ArrayList<>();
300        if (typeParameters != null) {
301            final DetailAST typeParam =
302                typeParameters.findFirstToken(TokenTypes.TYPE_PARAMETER);
303            typeParams.add(typeParam);
304
305            DetailAST sibling = typeParam.getNextSibling();
306            while (sibling != null) {
307                if (sibling.getType() == TokenTypes.TYPE_PARAMETER) {
308                    typeParams.add(sibling);
309                }
310                sibling = sibling.getNextSibling();
311            }
312        }
313
314        return typeParams;
315    }
316
317    /**
318     * Returns whether an AST represents a setter method.
319     * @param ast the AST to check with
320     * @return whether the AST represents a setter method
321     */
322    public static boolean isSetterMethod(final DetailAST ast) {
323        boolean setterMethod = false;
324
325        // Check have a method with exactly 7 children which are all that
326        // is allowed in a proper setter method which does not throw any
327        // exceptions.
328        if (ast.getType() == TokenTypes.METHOD_DEF
329                && ast.getChildCount() == SETTER_GETTER_MAX_CHILDREN) {
330            final DetailAST type = ast.findFirstToken(TokenTypes.TYPE);
331            final String name = type.getNextSibling().getText();
332            final boolean matchesSetterFormat = SETTER_PATTERN.matcher(name).matches();
333            final boolean voidReturnType = type.findFirstToken(TokenTypes.LITERAL_VOID) != null;
334
335            final DetailAST params = ast.findFirstToken(TokenTypes.PARAMETERS);
336            final boolean singleParam = params.getChildCount(TokenTypes.PARAMETER_DEF) == 1;
337
338            if (matchesSetterFormat && voidReturnType && singleParam) {
339                // Now verify that the body consists of:
340                // SLIST -> EXPR -> ASSIGN
341                // SEMI
342                // RCURLY
343                final DetailAST slist = ast.findFirstToken(TokenTypes.SLIST);
344
345                if (slist != null && slist.getChildCount() == SETTER_BODY_SIZE) {
346                    final DetailAST expr = slist.getFirstChild();
347                    setterMethod = expr.getFirstChild().getType() == TokenTypes.ASSIGN;
348                }
349            }
350        }
351        return setterMethod;
352    }
353
354    /**
355     * Returns whether an AST represents a getter method.
356     * @param ast the AST to check with
357     * @return whether the AST represents a getter method
358     */
359    public static boolean isGetterMethod(final DetailAST ast) {
360        boolean getterMethod = false;
361
362        // Check have a method with exactly 7 children which are all that
363        // is allowed in a proper getter method which does not throw any
364        // exceptions.
365        if (ast.getType() == TokenTypes.METHOD_DEF
366                && ast.getChildCount() == SETTER_GETTER_MAX_CHILDREN) {
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        return parameterDefAst.getType() == TokenTypes.PARAMETER_DEF
415                && parameterDefAst.findFirstToken(TokenTypes.IDENT) == null;
416    }
417
418    /**
419     * Returns {@link AccessModifier} based on the information about access modifier
420     * taken from the given token of type {@link TokenTypes#MODIFIERS}.
421     * @param modifiersToken token of type {@link TokenTypes#MODIFIERS}.
422     * @return {@link AccessModifier}.
423     */
424    public static AccessModifier getAccessModifierFromModifiersToken(DetailAST modifiersToken) {
425        if (modifiersToken == null || modifiersToken.getType() != TokenTypes.MODIFIERS) {
426            throw new IllegalArgumentException("expected non-null AST-token with type 'MODIFIERS'");
427        }
428
429        // default access modifier
430        AccessModifier accessModifier = AccessModifier.PACKAGE;
431        for (AST token = modifiersToken.getFirstChild(); token != null;
432             token = token.getNextSibling()) {
433            final int tokenType = token.getType();
434            if (tokenType == TokenTypes.LITERAL_PUBLIC) {
435                accessModifier = AccessModifier.PUBLIC;
436            }
437            else if (tokenType == TokenTypes.LITERAL_PROTECTED) {
438                accessModifier = AccessModifier.PROTECTED;
439            }
440            else if (tokenType == TokenTypes.LITERAL_PRIVATE) {
441                accessModifier = AccessModifier.PRIVATE;
442            }
443        }
444        return accessModifier;
445    }
446
447    /**
448     * Create set of class names and short class names.
449     *
450     * @param classNames array of class names.
451     * @return set of class names and short class names.
452     */
453    public static Set<String> parseClassNames(String... classNames) {
454        final Set<String> illegalClassNames = new HashSet<>();
455        for (final String name : classNames) {
456            illegalClassNames.add(name);
457            final int lastDot = name.lastIndexOf('.');
458            if (lastDot != -1 && lastDot < name.length() - 1) {
459                final String shortName = name
460                        .substring(name.lastIndexOf('.') + 1);
461                illegalClassNames.add(shortName);
462            }
463        }
464        return illegalClassNames;
465    }
466
467}