001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2015 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.List;
023import java.util.regex.Pattern;
024
025import com.google.common.collect.Lists;
026import com.puppycrawl.tools.checkstyle.api.DetailAST;
027import com.puppycrawl.tools.checkstyle.api.FullIdent;
028import com.puppycrawl.tools.checkstyle.api.TokenTypes;
029
030/**
031 * Contains utility methods for the checks.
032 *
033 * @author Oliver Burn
034 * @author <a href="mailto:simon@redhillconsulting.com.au">Simon Harris</a>
035 * @author o_sukhodolsky
036 */
037public final class CheckUtils {
038    // constants for parseDouble()
039    /** Octal radix. */
040    private static final int BASE_8 = 8;
041
042    /** Decimal radix. */
043    private static final int BASE_10 = 10;
044
045    /** Hex radix. */
046    private static final int BASE_16 = 16;
047
048    /** Maximum children allowed in setter/getter. */
049    private static final int SETTER_GETTER_MAX_CHILDREN = 7;
050
051    /** Maximum nodes allowed in a body of setter. */
052    private static final int SETTER_BODY_SIZE = 3;
053
054    /** Maximum nodes allowed in a body of getter. */
055    private static final int GETTER_BODY_SIZE = 2;
056
057    /** Pattern matching underscore characters ('_'). */
058    private static final Pattern UNDERSCORE_PATTERN = Pattern.compile("_");
059
060    /** Pattern matching names of setter methods. */
061    private static final Pattern SETTER_PATTERN = Pattern.compile("^set[A-Z].*");
062
063    /** Pattern matching names of getter methods. */
064    private static final Pattern GETTER_PATTERN = Pattern.compile("^(is|get)[A-Z].*");
065
066    /** Prevent instances. */
067    private CheckUtils() {
068    }
069
070    /**
071     * Tests whether a method definition AST defines an equals covariant.
072     * @param ast the method definition AST to test.
073     *     Precondition: ast is a TokenTypes.METHOD_DEF node.
074     * @return true if ast defines an equals covariant.
075     */
076    public static boolean isEqualsMethod(DetailAST ast) {
077        boolean equalsMethod = false;
078
079        if (ast.getType() == TokenTypes.METHOD_DEF) {
080            final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS);
081            final boolean staticOrAbstract = modifiers.branchContains(TokenTypes.LITERAL_STATIC)
082                    || modifiers.branchContains(TokenTypes.ABSTRACT);
083
084            if (!staticOrAbstract) {
085                final DetailAST nameNode = ast.findFirstToken(TokenTypes.IDENT);
086                final String name = nameNode.getText();
087
088                if ("equals".equals(name)) {
089                    // one parameter?
090                    final DetailAST paramsNode = ast.findFirstToken(TokenTypes.PARAMETERS);
091                    equalsMethod = paramsNode.getChildCount() == 1;
092                }
093            }
094        }
095        return equalsMethod;
096    }
097
098    /**
099     * Returns whether a token represents an ELSE as part of an ELSE / IF set.
100     * @param ast the token to check
101     * @return whether it is
102     */
103    public static boolean isElseIf(DetailAST ast) {
104        final DetailAST parentAST = ast.getParent();
105
106        return ast.getType() == TokenTypes.LITERAL_IF
107            && (isElse(parentAST) || isElseWithCurlyBraces(parentAST));
108    }
109
110    /**
111     * Returns whether a token represents an ELSE.
112     * @param ast the token to check
113     * @return whether the token represents an ELSE
114     */
115    private static boolean isElse(DetailAST ast) {
116        return ast.getType() == TokenTypes.LITERAL_ELSE;
117    }
118
119    /**
120     * Returns whether a token represents an SLIST as part of an ELSE
121     * statement.
122     * @param ast the token to check
123     * @return whether the toke does represent an SLIST as part of an ELSE
124     */
125    private static boolean isElseWithCurlyBraces(DetailAST ast) {
126        return ast.getType() == TokenTypes.SLIST
127            && ast.getChildCount() == 2
128            && isElse(ast.getParent());
129    }
130
131    /**
132     * Creates {@code FullIdent} for given type node.
133     * @param typeAST a type node.
134     * @return {@code FullIdent} for given type.
135     */
136    public static FullIdent createFullType(DetailAST typeAST) {
137        final DetailAST arrayDeclaratorAST =
138            typeAST.findFirstToken(TokenTypes.ARRAY_DECLARATOR);
139        final FullIdent fullType;
140
141        if (arrayDeclaratorAST == null) {
142            fullType = createFullTypeNoArrays(typeAST);
143        }
144        else {
145            fullType = createFullTypeNoArrays(arrayDeclaratorAST);
146        }
147        return fullType;
148    }
149
150    /**
151     * @param typeAST a type node (no array)
152     * @return {@code FullIdent} for given type.
153     */
154    private static FullIdent createFullTypeNoArrays(DetailAST typeAST) {
155        return FullIdent.createFullIdent(typeAST.getFirstChild());
156    }
157
158    /**
159     * Returns the value represented by the specified string of the specified
160     * type. Returns 0 for types other than float, double, int, and long.
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        double result = 0;
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.charAt(0) == '0') {
182                    radix = BASE_8;
183                    txt = txt.substring(1);
184                }
185                if (CommonUtils.endsWithChar(txt, 'L') || CommonUtils.endsWithChar(txt, 'l')) {
186                    txt = txt.substring(0, txt.length() - 1);
187                }
188                if (!txt.isEmpty()) {
189                    if (type == TokenTypes.NUM_INT) {
190                        result = parseInt(txt, radix);
191                    }
192                    else {
193                        result = parseLong(txt, radix);
194                    }
195                }
196                break;
197            default:
198                break;
199        }
200        return result;
201    }
202
203    /**
204     * Parses the string argument as a signed integer in the radix specified by
205     * the second argument. The characters in the string must all be digits of
206     * the specified radix. Handles negative values, which method
207     * java.lang.Integer.parseInt(String, int) does not.
208     * @param text the String containing the integer representation to be
209     *     parsed. Precondition: text contains a parsable int.
210     * @param radix the radix to be used while parsing text.
211     * @return the integer represented by the string argument in the specified radix.
212     */
213    private static int parseInt(String text, int radix) {
214        int result = 0;
215        final int max = text.length();
216        for (int i = 0; i < max; i++) {
217            final int digit = Character.digit(text.charAt(i), radix);
218            result *= radix;
219            result += digit;
220        }
221        return result;
222    }
223
224    /**
225     * Parses the string argument as a signed long in the radix specified by
226     * the second argument. The characters in the string must all be digits of
227     * the specified radix. Handles negative values, which method
228     * java.lang.Integer.parseInt(String, int) does not.
229     * @param text the String containing the integer representation to be
230     *     parsed. Precondition: text contains a parsable int.
231     * @param radix the radix to be used while parsing text.
232     * @return the long represented by the string argument in the specified radix.
233     */
234    private static long parseLong(String text, int radix) {
235        long result = 0;
236        final int max = text.length();
237        for (int i = 0; i < max; i++) {
238            final int digit = Character.digit(text.charAt(i), radix);
239            result *= radix;
240            result += digit;
241        }
242        return result;
243    }
244
245    /**
246     * Finds sub-node for given node minimal (line, column) pair.
247     * @param node the root of tree for search.
248     * @return sub-node with minimal (line, column) pair.
249     */
250    public static DetailAST getFirstNode(final DetailAST node) {
251        DetailAST currentNode = node;
252        DetailAST child = node.getFirstChild();
253        while (child != null) {
254            final DetailAST newNode = getFirstNode(child);
255            if (newNode.getLineNo() < currentNode.getLineNo()
256                || newNode.getLineNo() == currentNode.getLineNo()
257                    && newNode.getColumnNo() < currentNode.getColumnNo()) {
258                currentNode = newNode;
259            }
260            child = child.getNextSibling();
261        }
262
263        return currentNode;
264    }
265
266    /**
267     * Retrieves the names of the type parameters to the node.
268     * @param node the parameterized AST node
269     * @return a list of type parameter names
270     */
271    public static List<String> getTypeParameterNames(final DetailAST node) {
272        final DetailAST typeParameters =
273            node.findFirstToken(TokenTypes.TYPE_PARAMETERS);
274
275        final List<String> typeParameterNames = Lists.newArrayList();
276        if (typeParameters != null) {
277            final DetailAST typeParam =
278                typeParameters.findFirstToken(TokenTypes.TYPE_PARAMETER);
279            typeParameterNames.add(
280                    typeParam.findFirstToken(TokenTypes.IDENT).getText());
281
282            DetailAST sibling = typeParam.getNextSibling();
283            while (sibling != null) {
284                if (sibling.getType() == TokenTypes.TYPE_PARAMETER) {
285                    typeParameterNames.add(
286                            sibling.findFirstToken(TokenTypes.IDENT).getText());
287                }
288                sibling = sibling.getNextSibling();
289            }
290        }
291
292        return typeParameterNames;
293    }
294
295    /**
296     * Retrieves the type parameters to the node.
297     * @param node the parameterized AST node
298     * @return a list of type parameter names
299     */
300    public static List<DetailAST> getTypeParameters(final DetailAST node) {
301        final DetailAST typeParameters =
302            node.findFirstToken(TokenTypes.TYPE_PARAMETERS);
303
304        final List<DetailAST> typeParams = Lists.newArrayList();
305        if (typeParameters != null) {
306            final DetailAST typeParam =
307                typeParameters.findFirstToken(TokenTypes.TYPE_PARAMETER);
308            typeParams.add(typeParam);
309
310            DetailAST sibling = typeParam.getNextSibling();
311            while (sibling != null) {
312                if (sibling.getType() == TokenTypes.TYPE_PARAMETER) {
313                    typeParams.add(sibling);
314                }
315                sibling = sibling.getNextSibling();
316            }
317        }
318
319        return typeParams;
320    }
321
322    /**
323     * Returns whether an AST represents a setter method.
324     * @param ast the AST to check with
325     * @return whether the AST represents a setter method
326     */
327    public static boolean isSetterMethod(final DetailAST ast) {
328        boolean setterMethod = false;
329
330        // Check have a method with exactly 7 children which are all that
331        // is allowed in a proper setter method which does not throw any
332        // exceptions.
333        if (ast.getType() == TokenTypes.METHOD_DEF
334                && ast.getChildCount() == SETTER_GETTER_MAX_CHILDREN) {
335
336            final DetailAST type = ast.findFirstToken(TokenTypes.TYPE);
337            final String name = type.getNextSibling().getText();
338            final boolean matchesSetterFormat = SETTER_PATTERN.matcher(name).matches();
339            final boolean voidReturnType = type.getChildCount(TokenTypes.LITERAL_VOID) > 0;
340
341            final DetailAST params = ast.findFirstToken(TokenTypes.PARAMETERS);
342            final boolean singleParam = params.getChildCount(TokenTypes.PARAMETER_DEF) == 1;
343
344            if (matchesSetterFormat && voidReturnType && singleParam) {
345                // Now verify that the body consists of:
346                // SLIST -> EXPR -> ASSIGN
347                // SEMI
348                // RCURLY
349                final DetailAST slist = ast.findFirstToken(TokenTypes.SLIST);
350
351                if (slist != null && slist.getChildCount() == SETTER_BODY_SIZE) {
352                    final DetailAST expr = slist.getFirstChild();
353                    setterMethod = expr.getFirstChild().getType() == TokenTypes.ASSIGN;
354                }
355            }
356        }
357        return setterMethod;
358    }
359
360    /**
361     * Returns whether an AST represents a getter method.
362     * @param ast the AST to check with
363     * @return whether the AST represents a getter method
364     */
365    public static boolean isGetterMethod(final DetailAST ast) {
366        boolean getterMethod = false;
367
368        // Check have a method with exactly 7 children which are all that
369        // is allowed in a proper getter method which does not throw any
370        // exceptions.
371        if (ast.getType() == TokenTypes.METHOD_DEF
372                && ast.getChildCount() == SETTER_GETTER_MAX_CHILDREN) {
373
374            final DetailAST type = ast.findFirstToken(TokenTypes.TYPE);
375            final String name = type.getNextSibling().getText();
376            final boolean matchesGetterFormat = GETTER_PATTERN.matcher(name).matches();
377            final boolean noVoidReturnType = type.getChildCount(TokenTypes.LITERAL_VOID) == 0;
378
379            final DetailAST params = ast.findFirstToken(TokenTypes.PARAMETERS);
380            final boolean noParams = params.getChildCount(TokenTypes.PARAMETER_DEF) == 0;
381
382            if (matchesGetterFormat && noVoidReturnType && noParams) {
383                // Now verify that the body consists of:
384                // SLIST -> RETURN
385                // RCURLY
386                final DetailAST slist = ast.findFirstToken(TokenTypes.SLIST);
387
388                if (slist != null && slist.getChildCount() == GETTER_BODY_SIZE) {
389                    final DetailAST expr = slist.getFirstChild();
390                    getterMethod = expr.getType() == TokenTypes.LITERAL_RETURN;
391                }
392            }
393        }
394        return getterMethod;
395    }
396
397    /**
398     * Checks whether a method is a not void one.
399     *
400     * @param methodDefAst the method node.
401     * @return true if method is a not void one.
402     */
403    public static boolean isNonVoidMethod(DetailAST methodDefAst) {
404        boolean returnValue = false;
405        if (methodDefAst.getType() == TokenTypes.METHOD_DEF) {
406            final DetailAST typeAST = methodDefAst.findFirstToken(TokenTypes.TYPE);
407            if (typeAST.findFirstToken(TokenTypes.LITERAL_VOID) == null) {
408                returnValue = true;
409            }
410        }
411        return returnValue;
412    }
413}