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}