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.List;
024import java.util.regex.Pattern;
025
026import antlr.collections.AST;
027import com.puppycrawl.tools.checkstyle.api.DetailAST;
028import com.puppycrawl.tools.checkstyle.api.FullIdent;
029import com.puppycrawl.tools.checkstyle.api.TokenTypes;
030import com.puppycrawl.tools.checkstyle.checks.naming.AccessModifier;
031
032/**
033 * Contains utility methods for the checks.
034 *
035 * @author Oliver Burn
036 * @author <a href="mailto:simon@redhillconsulting.com.au">Simon Harris</a>
037 * @author o_sukhodolsky
038 */
039public final class CheckUtils {
040    // constants for parseDouble()
041    /** Octal radix. */
042    private static final int BASE_8 = 8;
043
044    /** Decimal radix. */
045    private static final int BASE_10 = 10;
046
047    /** Hex radix. */
048    private static final int BASE_16 = 16;
049
050    /** Maximum children allowed in setter/getter. */
051    private static final int SETTER_GETTER_MAX_CHILDREN = 7;
052
053    /** Maximum nodes allowed in a body of setter. */
054    private static final int SETTER_BODY_SIZE = 3;
055
056    /** Maximum nodes allowed in a body of getter. */
057    private static final int GETTER_BODY_SIZE = 2;
058
059    /** Pattern matching underscore characters ('_'). */
060    private static final Pattern UNDERSCORE_PATTERN = Pattern.compile("_");
061
062    /** Pattern matching names of setter methods. */
063    private static final Pattern SETTER_PATTERN = Pattern.compile("^set[A-Z].*");
064
065    /** Pattern matching names of getter methods. */
066    private static final Pattern GETTER_PATTERN = Pattern.compile("^(is|get)[A-Z].*");
067
068    /** Prevent instances. */
069    private CheckUtils() {
070    }
071
072    /**
073     * Creates {@code FullIdent} for given type node.
074     * @param typeAST a type node.
075     * @return {@code FullIdent} for given type.
076     */
077    public static FullIdent createFullType(DetailAST typeAST) {
078        final DetailAST arrayDeclaratorAST =
079            typeAST.findFirstToken(TokenTypes.ARRAY_DECLARATOR);
080        final FullIdent fullType;
081
082        if (arrayDeclaratorAST == null) {
083            fullType = createFullTypeNoArrays(typeAST);
084        }
085        else {
086            fullType = createFullTypeNoArrays(arrayDeclaratorAST);
087        }
088        return fullType;
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 = modifiers.branchContains(TokenTypes.LITERAL_STATIC)
103                    || modifiers.branchContains(TokenTypes.ABSTRACT);
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 FullIndent for given type.
154     * @param typeAST a type node (no array)
155     * @return {@code FullIdent} for given type.
156     */
157    private static FullIdent createFullTypeNoArrays(DetailAST typeAST) {
158        return FullIdent.createFullIdent(typeAST.getFirstChild());
159    }
160
161    /**
162     * Returns the value represented by the specified string of the specified
163     * type. Returns 0 for types other than float, double, int, and long.
164     * @param text the string to be parsed.
165     * @param type the token type of the text. Should be a constant of
166     * {@link TokenTypes}.
167     * @return the double value represented by the string argument.
168     */
169    public static double parseDouble(String text, int type) {
170        String txt = UNDERSCORE_PATTERN.matcher(text).replaceAll("");
171        double result = 0;
172        switch (type) {
173            case TokenTypes.NUM_FLOAT:
174            case TokenTypes.NUM_DOUBLE:
175                result = Double.parseDouble(txt);
176                break;
177            case TokenTypes.NUM_INT:
178            case TokenTypes.NUM_LONG:
179                int radix = BASE_10;
180                if (txt.startsWith("0x") || txt.startsWith("0X")) {
181                    radix = BASE_16;
182                    txt = txt.substring(2);
183                }
184                else if (txt.charAt(0) == '0') {
185                    radix = BASE_8;
186                    txt = txt.substring(1);
187                }
188                if (CommonUtils.endsWithChar(txt, 'L') || CommonUtils.endsWithChar(txt, 'l')) {
189                    txt = txt.substring(0, txt.length() - 1);
190                }
191                if (!txt.isEmpty()) {
192                    if (type == TokenTypes.NUM_INT) {
193                        result = parseInt(txt, radix);
194                    }
195                    else {
196                        result = parseLong(txt, radix);
197                    }
198                }
199                break;
200            default:
201                break;
202        }
203        return result;
204    }
205
206    /**
207     * Parses the string argument as a signed integer in the radix specified by
208     * the second argument. The characters in the string must all be digits of
209     * the specified radix. Handles negative values, which method
210     * java.lang.Integer.parseInt(String, int) does not.
211     * @param text the String containing the integer representation to be
212     *     parsed. Precondition: text contains a parsable int.
213     * @param radix the radix to be used while parsing text.
214     * @return the integer represented by the string argument in the specified radix.
215     */
216    private static int parseInt(String text, int radix) {
217        int result = 0;
218        final int max = text.length();
219        for (int i = 0; i < max; i++) {
220            final int digit = Character.digit(text.charAt(i), radix);
221            result *= radix;
222            result += digit;
223        }
224        return result;
225    }
226
227    /**
228     * Parses the string argument as a signed long in the radix specified by
229     * the second argument. The characters in the string must all be digits of
230     * the specified radix. Handles negative values, which method
231     * java.lang.Integer.parseInt(String, int) does not.
232     * @param text the String containing the integer representation to be
233     *     parsed. Precondition: text contains a parsable int.
234     * @param radix the radix to be used while parsing text.
235     * @return the long represented by the string argument in the specified radix.
236     */
237    private static long parseLong(String text, int radix) {
238        long result = 0;
239        final int max = text.length();
240        for (int i = 0; i < max; i++) {
241            final int digit = Character.digit(text.charAt(i), radix);
242            result *= radix;
243            result += digit;
244        }
245        return result;
246    }
247
248    /**
249     * Finds sub-node for given node minimal (line, column) pair.
250     * @param node the root of tree for search.
251     * @return sub-node with minimal (line, column) pair.
252     */
253    public static DetailAST getFirstNode(final DetailAST node) {
254        DetailAST currentNode = node;
255        DetailAST child = node.getFirstChild();
256        while (child != null) {
257            final DetailAST newNode = getFirstNode(child);
258            if (newNode.getLineNo() < currentNode.getLineNo()
259                || newNode.getLineNo() == currentNode.getLineNo()
260                    && newNode.getColumnNo() < currentNode.getColumnNo()) {
261                currentNode = newNode;
262            }
263            child = child.getNextSibling();
264        }
265
266        return currentNode;
267    }
268
269    /**
270     * Retrieves the names of the type parameters to the node.
271     * @param node the parameterized AST node
272     * @return a list of type parameter names
273     */
274    public static List<String> getTypeParameterNames(final DetailAST node) {
275        final DetailAST typeParameters =
276            node.findFirstToken(TokenTypes.TYPE_PARAMETERS);
277
278        final List<String> typeParameterNames = new ArrayList<>();
279        if (typeParameters != null) {
280            final DetailAST typeParam =
281                typeParameters.findFirstToken(TokenTypes.TYPE_PARAMETER);
282            typeParameterNames.add(
283                    typeParam.findFirstToken(TokenTypes.IDENT).getText());
284
285            DetailAST sibling = typeParam.getNextSibling();
286            while (sibling != null) {
287                if (sibling.getType() == TokenTypes.TYPE_PARAMETER) {
288                    typeParameterNames.add(
289                            sibling.findFirstToken(TokenTypes.IDENT).getText());
290                }
291                sibling = sibling.getNextSibling();
292            }
293        }
294
295        return typeParameterNames;
296    }
297
298    /**
299     * Retrieves the type parameters to the node.
300     * @param node the parameterized AST node
301     * @return a list of type parameter names
302     */
303    public static List<DetailAST> getTypeParameters(final DetailAST node) {
304        final DetailAST typeParameters =
305            node.findFirstToken(TokenTypes.TYPE_PARAMETERS);
306
307        final List<DetailAST> typeParams = new ArrayList<>();
308        if (typeParameters != null) {
309            final DetailAST typeParam =
310                typeParameters.findFirstToken(TokenTypes.TYPE_PARAMETER);
311            typeParams.add(typeParam);
312
313            DetailAST sibling = typeParam.getNextSibling();
314            while (sibling != null) {
315                if (sibling.getType() == TokenTypes.TYPE_PARAMETER) {
316                    typeParams.add(sibling);
317                }
318                sibling = sibling.getNextSibling();
319            }
320        }
321
322        return typeParams;
323    }
324
325    /**
326     * Returns whether an AST represents a setter method.
327     * @param ast the AST to check with
328     * @return whether the AST represents a setter method
329     */
330    public static boolean isSetterMethod(final DetailAST ast) {
331        boolean setterMethod = false;
332
333        // Check have a method with exactly 7 children which are all that
334        // is allowed in a proper setter method which does not throw any
335        // exceptions.
336        if (ast.getType() == TokenTypes.METHOD_DEF
337                && ast.getChildCount() == SETTER_GETTER_MAX_CHILDREN) {
338
339            final DetailAST type = ast.findFirstToken(TokenTypes.TYPE);
340            final String name = type.getNextSibling().getText();
341            final boolean matchesSetterFormat = SETTER_PATTERN.matcher(name).matches();
342            final boolean voidReturnType = type.getChildCount(TokenTypes.LITERAL_VOID) > 0;
343
344            final DetailAST params = ast.findFirstToken(TokenTypes.PARAMETERS);
345            final boolean singleParam = params.getChildCount(TokenTypes.PARAMETER_DEF) == 1;
346
347            if (matchesSetterFormat && voidReturnType && singleParam) {
348                // Now verify that the body consists of:
349                // SLIST -> EXPR -> ASSIGN
350                // SEMI
351                // RCURLY
352                final DetailAST slist = ast.findFirstToken(TokenTypes.SLIST);
353
354                if (slist != null && slist.getChildCount() == SETTER_BODY_SIZE) {
355                    final DetailAST expr = slist.getFirstChild();
356                    setterMethod = expr.getFirstChild().getType() == TokenTypes.ASSIGN;
357                }
358            }
359        }
360        return setterMethod;
361    }
362
363    /**
364     * Returns whether an AST represents a getter method.
365     * @param ast the AST to check with
366     * @return whether the AST represents a getter method
367     */
368    public static boolean isGetterMethod(final DetailAST ast) {
369        boolean getterMethod = false;
370
371        // Check have a method with exactly 7 children which are all that
372        // is allowed in a proper getter method which does not throw any
373        // exceptions.
374        if (ast.getType() == TokenTypes.METHOD_DEF
375                && ast.getChildCount() == SETTER_GETTER_MAX_CHILDREN) {
376
377            final DetailAST type = ast.findFirstToken(TokenTypes.TYPE);
378            final String name = type.getNextSibling().getText();
379            final boolean matchesGetterFormat = GETTER_PATTERN.matcher(name).matches();
380            final boolean noVoidReturnType = type.getChildCount(TokenTypes.LITERAL_VOID) == 0;
381
382            final DetailAST params = ast.findFirstToken(TokenTypes.PARAMETERS);
383            final boolean noParams = params.getChildCount(TokenTypes.PARAMETER_DEF) == 0;
384
385            if (matchesGetterFormat && noVoidReturnType && noParams) {
386                // Now verify that the body consists of:
387                // SLIST -> RETURN
388                // RCURLY
389                final DetailAST slist = ast.findFirstToken(TokenTypes.SLIST);
390
391                if (slist != null && slist.getChildCount() == GETTER_BODY_SIZE) {
392                    final DetailAST expr = slist.getFirstChild();
393                    getterMethod = expr.getType() == TokenTypes.LITERAL_RETURN;
394                }
395            }
396        }
397        return getterMethod;
398    }
399
400    /**
401     * Checks whether a method is a not void one.
402     *
403     * @param methodDefAst the method node.
404     * @return true if method is a not void one.
405     */
406    public static boolean isNonVoidMethod(DetailAST methodDefAst) {
407        boolean returnValue = false;
408        if (methodDefAst.getType() == TokenTypes.METHOD_DEF) {
409            final DetailAST typeAST = methodDefAst.findFirstToken(TokenTypes.TYPE);
410            if (typeAST.findFirstToken(TokenTypes.LITERAL_VOID) == null) {
411                returnValue = true;
412            }
413        }
414        return returnValue;
415    }
416
417    /**
418     * Checks whether a parameter is a receiver.
419     *
420     * @param parameterDefAst the parameter node.
421     * @return true if the parameter is a receiver.
422     */
423    public static boolean isReceiverParameter(DetailAST parameterDefAst) {
424        boolean returnValue = false;
425        if (parameterDefAst.getType() == TokenTypes.PARAMETER_DEF
426                && parameterDefAst.findFirstToken(TokenTypes.IDENT) == null) {
427            returnValue = parameterDefAst.branchContains(TokenTypes.LITERAL_THIS);
428        }
429        return returnValue;
430    }
431
432    /**
433     * Returns {@link AccessModifier} based on the information about access modifier
434     * taken from the given token of type {@link TokenTypes#MODIFIERS}.
435     * @param modifiersToken token of type {@link TokenTypes#MODIFIERS}.
436     * @return {@link AccessModifier}.
437     */
438    public static AccessModifier getAccessModifierFromModifiersToken(DetailAST modifiersToken) {
439        if (modifiersToken == null || modifiersToken.getType() != TokenTypes.MODIFIERS) {
440            throw new IllegalArgumentException("expected non-null AST-token with type 'MODIFIERS'");
441        }
442
443        // default access modifier
444        AccessModifier accessModifier = AccessModifier.PACKAGE;
445        for (AST token = modifiersToken.getFirstChild(); token != null;
446             token = token.getNextSibling()) {
447
448            final int tokenType = token.getType();
449            if (tokenType == TokenTypes.LITERAL_PUBLIC) {
450                accessModifier = AccessModifier.PUBLIC;
451            }
452            else if (tokenType == TokenTypes.LITERAL_PROTECTED) {
453                accessModifier = AccessModifier.PROTECTED;
454            }
455            else if (tokenType == TokenTypes.LITERAL_PRIVATE) {
456                accessModifier = AccessModifier.PRIVATE;
457            }
458        }
459        return accessModifier;
460    }
461}