Coverage Report - com.puppycrawl.tools.checkstyle.utils.CheckUtils
 
Classes in this File Line Coverage Branch Coverage Complexity
CheckUtils
100%
161/161
100%
135/135
4.278
 
 1  
 ////////////////////////////////////////////////////////////////////////////////
 2  
 // checkstyle: Checks Java source code for adherence to a set of rules.
 3  
 // Copyright (C) 2001-2017 the original author or authors.
 4  
 //
 5  
 // This library is free software; you can redistribute it and/or
 6  
 // modify it under the terms of the GNU Lesser General Public
 7  
 // License as published by the Free Software Foundation; either
 8  
 // version 2.1 of the License, or (at your option) any later version.
 9  
 //
 10  
 // This library is distributed in the hope that it will be useful,
 11  
 // but WITHOUT ANY WARRANTY; without even the implied warranty of
 12  
 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 13  
 // Lesser General Public License for more details.
 14  
 //
 15  
 // You should have received a copy of the GNU Lesser General Public
 16  
 // License along with this library; if not, write to the Free Software
 17  
 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 18  
 ////////////////////////////////////////////////////////////////////////////////
 19  
 
 20  
 package com.puppycrawl.tools.checkstyle.utils;
 21  
 
 22  
 import java.util.ArrayList;
 23  
 import java.util.HashSet;
 24  
 import java.util.List;
 25  
 import java.util.Set;
 26  
 import java.util.regex.Pattern;
 27  
 
 28  
 import antlr.collections.AST;
 29  
 import com.puppycrawl.tools.checkstyle.api.DetailAST;
 30  
 import com.puppycrawl.tools.checkstyle.api.FullIdent;
 31  
 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
 32  
 import com.puppycrawl.tools.checkstyle.checks.naming.AccessModifier;
 33  
 
 34  
 /**
 35  
  * Contains utility methods for the checks.
 36  
  *
 37  
  * @author Oliver Burn
 38  
  * @author <a href="mailto:simon@redhillconsulting.com.au">Simon Harris</a>
 39  
  * @author o_sukhodolsky
 40  
  */
 41  
 public final class CheckUtils {
 42  
     // constants for parseDouble()
 43  
     /** Octal radix. */
 44  
     private static final int BASE_8 = 8;
 45  
 
 46  
     /** Decimal radix. */
 47  
     private static final int BASE_10 = 10;
 48  
 
 49  
     /** Hex radix. */
 50  
     private static final int BASE_16 = 16;
 51  
 
 52  
     /** Maximum children allowed in setter/getter. */
 53  
     private static final int SETTER_GETTER_MAX_CHILDREN = 7;
 54  
 
 55  
     /** Maximum nodes allowed in a body of setter. */
 56  
     private static final int SETTER_BODY_SIZE = 3;
 57  
 
 58  
     /** Maximum nodes allowed in a body of getter. */
 59  
     private static final int GETTER_BODY_SIZE = 2;
 60  
 
 61  
     /** Pattern matching underscore characters ('_'). */
 62  2
     private static final Pattern UNDERSCORE_PATTERN = Pattern.compile("_");
 63  
 
 64  
     /** Pattern matching names of setter methods. */
 65  2
     private static final Pattern SETTER_PATTERN = Pattern.compile("^set[A-Z].*");
 66  
 
 67  
     /** Pattern matching names of getter methods. */
 68  2
     private static final Pattern GETTER_PATTERN = Pattern.compile("^(is|get)[A-Z].*");
 69  
 
 70  
     /** Prevent instances. */
 71  1
     private CheckUtils() {
 72  1
     }
 73  
 
 74  
     /**
 75  
      * Creates {@code FullIdent} for given type node.
 76  
      * @param typeAST a type node.
 77  
      * @return {@code FullIdent} for given type.
 78  
      */
 79  
     public static FullIdent createFullType(final DetailAST typeAST) {
 80  168
         DetailAST ast = typeAST;
 81  
 
 82  
         // ignore array part of type
 83  189
         while (ast.findFirstToken(TokenTypes.ARRAY_DECLARATOR) != null) {
 84  21
             ast = ast.findFirstToken(TokenTypes.ARRAY_DECLARATOR);
 85  
         }
 86  
 
 87  168
         return FullIdent.createFullIdent(ast.getFirstChild());
 88  
     }
 89  
 
 90  
     /**
 91  
      * Tests whether a method definition AST defines an equals covariant.
 92  
      * @param ast the method definition AST to test.
 93  
      *     Precondition: ast is a TokenTypes.METHOD_DEF node.
 94  
      * @return true if ast defines an equals covariant.
 95  
      */
 96  
     public static boolean isEqualsMethod(DetailAST ast) {
 97  110
         boolean equalsMethod = false;
 98  
 
 99  110
         if (ast.getType() == TokenTypes.METHOD_DEF) {
 100  106
             final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS);
 101  106
             final boolean staticOrAbstract =
 102  106
                     modifiers.findFirstToken(TokenTypes.LITERAL_STATIC) != null
 103  103
                     || modifiers.findFirstToken(TokenTypes.ABSTRACT) != null;
 104  
 
 105  106
             if (!staticOrAbstract) {
 106  98
                 final DetailAST nameNode = ast.findFirstToken(TokenTypes.IDENT);
 107  98
                 final String name = nameNode.getText();
 108  
 
 109  98
                 if ("equals".equals(name)) {
 110  
                     // one parameter?
 111  56
                     final DetailAST paramsNode = ast.findFirstToken(TokenTypes.PARAMETERS);
 112  56
                     equalsMethod = paramsNode.getChildCount() == 1;
 113  
                 }
 114  
             }
 115  
         }
 116  110
         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  75
         final DetailAST parentAST = ast.getParent();
 126  
 
 127  150
         return ast.getType() == TokenTypes.LITERAL_IF
 128  74
             && (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  119
         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  144
         return ast.getType() == TokenTypes.SLIST
 148  71
             && ast.getChildCount() == 2
 149  45
             && 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  760
         String txt = UNDERSCORE_PATTERN.matcher(text).replaceAll("");
 162  760
         double result = 0;
 163  760
         switch (type) {
 164  
             case TokenTypes.NUM_FLOAT:
 165  
             case TokenTypes.NUM_DOUBLE:
 166  100
                 result = Double.parseDouble(txt);
 167  100
                 break;
 168  
             case TokenTypes.NUM_INT:
 169  
             case TokenTypes.NUM_LONG:
 170  659
                 int radix = BASE_10;
 171  659
                 if (txt.startsWith("0x") || txt.startsWith("0X")) {
 172  97
                     radix = BASE_16;
 173  97
                     txt = txt.substring(2);
 174  
                 }
 175  562
                 else if (txt.charAt(0) == '0') {
 176  102
                     radix = BASE_8;
 177  102
                     txt = txt.substring(1);
 178  
                 }
 179  659
                 if (CommonUtils.endsWithChar(txt, 'L') || CommonUtils.endsWithChar(txt, 'l')) {
 180  115
                     txt = txt.substring(0, txt.length() - 1);
 181  
                 }
 182  659
                 if (!txt.isEmpty()) {
 183  630
                     if (type == TokenTypes.NUM_INT) {
 184  531
                         result = parseInt(txt, radix);
 185  
                     }
 186  
                     else {
 187  99
                         result = parseLong(txt, radix);
 188  
                     }
 189  
                 }
 190  
                 break;
 191  
             default:
 192  
                 break;
 193  
         }
 194  760
         return result;
 195  
     }
 196  
 
 197  
     /**
 198  
      * Parses the string argument as a signed integer in the radix specified by
 199  
      * the second argument. The characters in the string must all be digits of
 200  
      * the specified radix. Handles negative values, which method
 201  
      * java.lang.Integer.parseInt(String, int) does not.
 202  
      * @param text the String containing the integer representation to be
 203  
      *     parsed. Precondition: text contains a parsable int.
 204  
      * @param radix the radix to be used while parsing text.
 205  
      * @return the integer represented by the string argument in the specified radix.
 206  
      */
 207  
     private static int parseInt(String text, int radix) {
 208  531
         int result = 0;
 209  531
         final int max = text.length();
 210  1589
         for (int i = 0; i < max; i++) {
 211  1058
             final int digit = Character.digit(text.charAt(i), radix);
 212  1058
             result *= radix;
 213  1058
             result += digit;
 214  
         }
 215  531
         return result;
 216  
     }
 217  
 
 218  
     /**
 219  
      * Parses the string argument as a signed long in the radix specified by
 220  
      * the second argument. The characters in the string must all be digits of
 221  
      * the specified radix. Handles negative values, which method
 222  
      * java.lang.Integer.parseInt(String, int) does not.
 223  
      * @param text the String containing the integer representation to be
 224  
      *     parsed. Precondition: text contains a parsable int.
 225  
      * @param radix the radix to be used while parsing text.
 226  
      * @return the long represented by the string argument in the specified radix.
 227  
      */
 228  
     private static long parseLong(String text, int radix) {
 229  99
         long result = 0;
 230  99
         final int max = text.length();
 231  929
         for (int i = 0; i < max; i++) {
 232  830
             final int digit = Character.digit(text.charAt(i), radix);
 233  830
             result *= radix;
 234  830
             result += digit;
 235  
         }
 236  99
         return result;
 237  
     }
 238  
 
 239  
     /**
 240  
      * Finds sub-node for given node minimal (line, column) pair.
 241  
      * @param node the root of tree for search.
 242  
      * @return sub-node with minimal (line, column) pair.
 243  
      */
 244  
     public static DetailAST getFirstNode(final DetailAST node) {
 245  6615
         DetailAST currentNode = node;
 246  6615
         DetailAST child = node.getFirstChild();
 247  12899
         while (child != null) {
 248  6284
             final DetailAST newNode = getFirstNode(child);
 249  6284
             if (newNode.getLineNo() < currentNode.getLineNo()
 250  6255
                 || newNode.getLineNo() == currentNode.getLineNo()
 251  5382
                     && newNode.getColumnNo() < currentNode.getColumnNo()) {
 252  746
                 currentNode = newNode;
 253  
             }
 254  6284
             child = child.getNextSibling();
 255  6284
         }
 256  
 
 257  6615
         return currentNode;
 258  
     }
 259  
 
 260  
     /**
 261  
      * Retrieves the names of the type parameters to the node.
 262  
      * @param node the parameterized AST node
 263  
      * @return a list of type parameter names
 264  
      */
 265  
     public static List<String> getTypeParameterNames(final DetailAST node) {
 266  280
         final DetailAST typeParameters =
 267  280
             node.findFirstToken(TokenTypes.TYPE_PARAMETERS);
 268  
 
 269  280
         final List<String> typeParameterNames = new ArrayList<>();
 270  280
         if (typeParameters != null) {
 271  31
             final DetailAST typeParam =
 272  31
                 typeParameters.findFirstToken(TokenTypes.TYPE_PARAMETER);
 273  62
             typeParameterNames.add(
 274  31
                     typeParam.findFirstToken(TokenTypes.IDENT).getText());
 275  
 
 276  31
             DetailAST sibling = typeParam.getNextSibling();
 277  86
             while (sibling != null) {
 278  55
                 if (sibling.getType() == TokenTypes.TYPE_PARAMETER) {
 279  24
                     typeParameterNames.add(
 280  12
                             sibling.findFirstToken(TokenTypes.IDENT).getText());
 281  
                 }
 282  55
                 sibling = sibling.getNextSibling();
 283  
             }
 284  
         }
 285  
 
 286  280
         return typeParameterNames;
 287  
     }
 288  
 
 289  
     /**
 290  
      * Retrieves the type parameters to the node.
 291  
      * @param node the parameterized AST node
 292  
      * @return a list of type parameter names
 293  
      */
 294  
     public static List<DetailAST> getTypeParameters(final DetailAST node) {
 295  170
         final DetailAST typeParameters =
 296  170
             node.findFirstToken(TokenTypes.TYPE_PARAMETERS);
 297  
 
 298  170
         final List<DetailAST> typeParams = new ArrayList<>();
 299  170
         if (typeParameters != null) {
 300  8
             final DetailAST typeParam =
 301  8
                 typeParameters.findFirstToken(TokenTypes.TYPE_PARAMETER);
 302  8
             typeParams.add(typeParam);
 303  
 
 304  8
             DetailAST sibling = typeParam.getNextSibling();
 305  20
             while (sibling != null) {
 306  12
                 if (sibling.getType() == TokenTypes.TYPE_PARAMETER) {
 307  2
                     typeParams.add(sibling);
 308  
                 }
 309  12
                 sibling = sibling.getNextSibling();
 310  
             }
 311  
         }
 312  
 
 313  170
         return typeParams;
 314  
     }
 315  
 
 316  
     /**
 317  
      * Returns whether an AST represents a setter method.
 318  
      * @param ast the AST to check with
 319  
      * @return whether the AST represents a setter method
 320  
      */
 321  
     public static boolean isSetterMethod(final DetailAST ast) {
 322  19
         boolean setterMethod = false;
 323  
 
 324  
         // Check have a method with exactly 7 children which are all that
 325  
         // is allowed in a proper setter method which does not throw any
 326  
         // exceptions.
 327  19
         if (ast.getType() == TokenTypes.METHOD_DEF
 328  17
                 && ast.getChildCount() == SETTER_GETTER_MAX_CHILDREN) {
 329  
 
 330  16
             final DetailAST type = ast.findFirstToken(TokenTypes.TYPE);
 331  16
             final String name = type.getNextSibling().getText();
 332  16
             final boolean matchesSetterFormat = SETTER_PATTERN.matcher(name).matches();
 333  16
             final boolean voidReturnType = type.findFirstToken(TokenTypes.LITERAL_VOID) != null;
 334  
 
 335  16
             final DetailAST params = ast.findFirstToken(TokenTypes.PARAMETERS);
 336  16
             final boolean singleParam = params.getChildCount(TokenTypes.PARAMETER_DEF) == 1;
 337  
 
 338  16
             if (matchesSetterFormat && voidReturnType && singleParam) {
 339  
                 // Now verify that the body consists of:
 340  
                 // SLIST -> EXPR -> ASSIGN
 341  
                 // SEMI
 342  
                 // RCURLY
 343  5
                 final DetailAST slist = ast.findFirstToken(TokenTypes.SLIST);
 344  
 
 345  5
                 if (slist != null && slist.getChildCount() == SETTER_BODY_SIZE) {
 346  3
                     final DetailAST expr = slist.getFirstChild();
 347  3
                     setterMethod = expr.getFirstChild().getType() == TokenTypes.ASSIGN;
 348  
                 }
 349  
             }
 350  
         }
 351  19
         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  18
         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  18
         if (ast.getType() == TokenTypes.METHOD_DEF
 366  16
                 && ast.getChildCount() == SETTER_GETTER_MAX_CHILDREN) {
 367  
 
 368  15
             final DetailAST type = ast.findFirstToken(TokenTypes.TYPE);
 369  15
             final String name = type.getNextSibling().getText();
 370  15
             final boolean matchesGetterFormat = GETTER_PATTERN.matcher(name).matches();
 371  15
             final boolean noVoidReturnType = type.findFirstToken(TokenTypes.LITERAL_VOID) == null;
 372  
 
 373  15
             final DetailAST params = ast.findFirstToken(TokenTypes.PARAMETERS);
 374  15
             final boolean noParams = params.getChildCount(TokenTypes.PARAMETER_DEF) == 0;
 375  
 
 376  15
             if (matchesGetterFormat && noVoidReturnType && noParams) {
 377  
                 // Now verify that the body consists of:
 378  
                 // SLIST -> RETURN
 379  
                 // RCURLY
 380  6
                 final DetailAST slist = ast.findFirstToken(TokenTypes.SLIST);
 381  
 
 382  6
                 if (slist != null && slist.getChildCount() == GETTER_BODY_SIZE) {
 383  4
                     final DetailAST expr = slist.getFirstChild();
 384  4
                     getterMethod = expr.getType() == TokenTypes.LITERAL_RETURN;
 385  
                 }
 386  
             }
 387  
         }
 388  18
         return getterMethod;
 389  
     }
 390  
 
 391  
     /**
 392  
      * Checks whether a method is a not void one.
 393  
      *
 394  
      * @param methodDefAst the method node.
 395  
      * @return true if method is a not void one.
 396  
      */
 397  
     public static boolean isNonVoidMethod(DetailAST methodDefAst) {
 398  170
         boolean returnValue = false;
 399  170
         if (methodDefAst.getType() == TokenTypes.METHOD_DEF) {
 400  157
             final DetailAST typeAST = methodDefAst.findFirstToken(TokenTypes.TYPE);
 401  157
             if (typeAST.findFirstToken(TokenTypes.LITERAL_VOID) == null) {
 402  47
                 returnValue = true;
 403  
             }
 404  
         }
 405  170
         return returnValue;
 406  
     }
 407  
 
 408  
     /**
 409  
      * Checks whether a parameter is a receiver.
 410  
      *
 411  
      * @param parameterDefAst the parameter node.
 412  
      * @return true if the parameter is a receiver.
 413  
      */
 414  
     public static boolean isReceiverParameter(DetailAST parameterDefAst) {
 415  1970
         return parameterDefAst.getType() == TokenTypes.PARAMETER_DEF
 416  433
                 && parameterDefAst.findFirstToken(TokenTypes.IDENT) == null;
 417  
     }
 418  
 
 419  
     /**
 420  
      * Returns {@link AccessModifier} based on the information about access modifier
 421  
      * taken from the given token of type {@link TokenTypes#MODIFIERS}.
 422  
      * @param modifiersToken token of type {@link TokenTypes#MODIFIERS}.
 423  
      * @return {@link AccessModifier}.
 424  
      */
 425  
     public static AccessModifier getAccessModifierFromModifiersToken(DetailAST modifiersToken) {
 426  69
         if (modifiersToken == null || modifiersToken.getType() != TokenTypes.MODIFIERS) {
 427  2
             throw new IllegalArgumentException("expected non-null AST-token with type 'MODIFIERS'");
 428  
         }
 429  
 
 430  
         // default access modifier
 431  67
         AccessModifier accessModifier = AccessModifier.PACKAGE;
 432  67
         for (AST token = modifiersToken.getFirstChild(); token != null;
 433  31
              token = token.getNextSibling()) {
 434  
 
 435  31
             final int tokenType = token.getType();
 436  31
             if (tokenType == TokenTypes.LITERAL_PUBLIC) {
 437  12
                 accessModifier = AccessModifier.PUBLIC;
 438  
             }
 439  19
             else if (tokenType == TokenTypes.LITERAL_PROTECTED) {
 440  5
                 accessModifier = AccessModifier.PROTECTED;
 441  
             }
 442  14
             else if (tokenType == TokenTypes.LITERAL_PRIVATE) {
 443  5
                 accessModifier = AccessModifier.PRIVATE;
 444  
             }
 445  
         }
 446  67
         return accessModifier;
 447  
     }
 448  
 
 449  
     /**
 450  
      * Create set of class names and short class names.
 451  
      *
 452  
      * @param classNames array of class names.
 453  
      * @return set of class names and short class names.
 454  
      */
 455  
     public static Set<String> parseClassNames(String... classNames) {
 456  6
         final Set<String> illegalClassNames = new HashSet<>();
 457  23
         for (final String name : classNames) {
 458  17
             illegalClassNames.add(name);
 459  17
             final int lastDot = name.lastIndexOf('.');
 460  17
             if (lastDot != -1 && lastDot < name.length() - 1) {
 461  9
                 final String shortName = name
 462  9
                         .substring(name.lastIndexOf('.') + 1);
 463  9
                 illegalClassNames.add(shortName);
 464  
             }
 465  
         }
 466  6
         return illegalClassNames;
 467  
     }
 468  
 }