001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2020 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 com.puppycrawl.tools.checkstyle.api.DetailAST; 029import com.puppycrawl.tools.checkstyle.api.FullIdent; 030import com.puppycrawl.tools.checkstyle.api.TokenTypes; 031import com.puppycrawl.tools.checkstyle.checks.naming.AccessModifierOption; 032 033/** 034 * Contains utility methods for the checks. 035 * 036 */ 037public final class CheckUtil { 038 039 // constants for parseDouble() 040 /** Binary radix. */ 041 private static final int BASE_2 = 2; 042 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 CheckUtil() { 072 } 073 074 /** 075 * Creates {@code FullIdent} for given type node. 076 * 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 * 094 * @param ast the method definition AST to test. 095 * Precondition: ast is a TokenTypes.METHOD_DEF node. 096 * @return true if ast defines an equals covariant. 097 */ 098 public static boolean isEqualsMethod(DetailAST ast) { 099 boolean equalsMethod = false; 100 101 if (ast.getType() == TokenTypes.METHOD_DEF) { 102 final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS); 103 final boolean staticOrAbstract = 104 modifiers.findFirstToken(TokenTypes.LITERAL_STATIC) != null 105 || modifiers.findFirstToken(TokenTypes.ABSTRACT) != null; 106 107 if (!staticOrAbstract) { 108 final DetailAST nameNode = ast.findFirstToken(TokenTypes.IDENT); 109 final String name = nameNode.getText(); 110 111 if ("equals".equals(name)) { 112 // one parameter? 113 final DetailAST paramsNode = ast.findFirstToken(TokenTypes.PARAMETERS); 114 equalsMethod = paramsNode.getChildCount() == 1; 115 } 116 } 117 } 118 return equalsMethod; 119 } 120 121 /** 122 * Returns whether a token represents an ELSE as part of an ELSE / IF set. 123 * 124 * @param ast the token to check 125 * @return whether it is 126 */ 127 public static boolean isElseIf(DetailAST ast) { 128 final DetailAST parentAST = ast.getParent(); 129 130 return ast.getType() == TokenTypes.LITERAL_IF 131 && (isElse(parentAST) || isElseWithCurlyBraces(parentAST)); 132 } 133 134 /** 135 * Returns whether a token represents an ELSE. 136 * 137 * @param ast the token to check 138 * @return whether the token represents an ELSE 139 */ 140 private static boolean isElse(DetailAST ast) { 141 return ast.getType() == TokenTypes.LITERAL_ELSE; 142 } 143 144 /** 145 * Returns whether a token represents an SLIST as part of an ELSE 146 * statement. 147 * 148 * @param ast the token to check 149 * @return whether the toke does represent an SLIST as part of an ELSE 150 */ 151 private static boolean isElseWithCurlyBraces(DetailAST ast) { 152 return ast.getType() == TokenTypes.SLIST 153 && ast.getChildCount() == 2 154 && isElse(ast.getParent()); 155 } 156 157 /** 158 * Returns the value represented by the specified string of the specified 159 * type. Returns 0 for types other than float, double, int, and long. 160 * 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 final double result; 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.startsWith("0b") || txt.startsWith("0B")) { 182 radix = BASE_2; 183 txt = txt.substring(2); 184 } 185 else if (CommonUtil.startsWithChar(txt, '0')) { 186 radix = BASE_8; 187 txt = txt.substring(1); 188 } 189 result = parseNumber(txt, radix, type); 190 break; 191 default: 192 result = Double.NaN; 193 break; 194 } 195 return result; 196 } 197 198 /** 199 * Parses the string argument as an integer or a long in the radix specified by 200 * the second argument. The characters in the string must all be digits of 201 * the specified radix. 202 * 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 * @param type the token type of the text. Should be a constant of 207 * {@link TokenTypes}. 208 * @return the number represented by the string argument in the specified radix. 209 */ 210 private static double parseNumber(final String text, final int radix, final int type) { 211 String txt = text; 212 if (CommonUtil.endsWithChar(txt, 'L') || CommonUtil.endsWithChar(txt, 'l')) { 213 txt = txt.substring(0, txt.length() - 1); 214 } 215 final double result; 216 if (txt.isEmpty()) { 217 result = 0.0; 218 } 219 else { 220 final boolean negative = txt.charAt(0) == '-'; 221 if (type == TokenTypes.NUM_INT) { 222 if (negative) { 223 result = Integer.parseInt(txt, radix); 224 } 225 else { 226 result = Integer.parseUnsignedInt(txt, radix); 227 } 228 } 229 else { 230 if (negative) { 231 result = Long.parseLong(txt, radix); 232 } 233 else { 234 result = Long.parseUnsignedLong(txt, radix); 235 } 236 } 237 } 238 return result; 239 } 240 241 /** 242 * Finds sub-node for given node minimal (line, column) pair. 243 * 244 * @param node the root of tree for search. 245 * @return sub-node with minimal (line, column) pair. 246 */ 247 public static DetailAST getFirstNode(final DetailAST node) { 248 DetailAST currentNode = node; 249 DetailAST child = node.getFirstChild(); 250 while (child != null) { 251 final DetailAST newNode = getFirstNode(child); 252 if (isBeforeInSource(newNode, currentNode)) { 253 currentNode = newNode; 254 } 255 child = child.getNextSibling(); 256 } 257 258 return currentNode; 259 } 260 261 /** 262 * Retrieves whether ast1 is located before ast2. 263 * 264 * @param ast1 the first node. 265 * @param ast2 the second node. 266 * @return true, if ast1 is located before ast2. 267 */ 268 public static boolean isBeforeInSource(DetailAST ast1, DetailAST ast2) { 269 return ast1.getLineNo() < ast2.getLineNo() 270 || TokenUtil.areOnSameLine(ast1, ast2) 271 && ast1.getColumnNo() < ast2.getColumnNo(); 272 } 273 274 /** 275 * Retrieves the names of the type parameters to the node. 276 * 277 * @param node the parameterized AST node 278 * @return a list of type parameter names 279 */ 280 public static List<String> getTypeParameterNames(final DetailAST node) { 281 final DetailAST typeParameters = 282 node.findFirstToken(TokenTypes.TYPE_PARAMETERS); 283 284 final List<String> typeParameterNames = new ArrayList<>(); 285 if (typeParameters != null) { 286 final DetailAST typeParam = 287 typeParameters.findFirstToken(TokenTypes.TYPE_PARAMETER); 288 typeParameterNames.add( 289 typeParam.findFirstToken(TokenTypes.IDENT).getText()); 290 291 DetailAST sibling = typeParam.getNextSibling(); 292 while (sibling != null) { 293 if (sibling.getType() == TokenTypes.TYPE_PARAMETER) { 294 typeParameterNames.add( 295 sibling.findFirstToken(TokenTypes.IDENT).getText()); 296 } 297 sibling = sibling.getNextSibling(); 298 } 299 } 300 301 return typeParameterNames; 302 } 303 304 /** 305 * Retrieves the type parameters to the node. 306 * 307 * @param node the parameterized AST node 308 * @return a list of type parameter names 309 */ 310 public static List<DetailAST> getTypeParameters(final DetailAST node) { 311 final DetailAST typeParameters = 312 node.findFirstToken(TokenTypes.TYPE_PARAMETERS); 313 314 final List<DetailAST> typeParams = new ArrayList<>(); 315 if (typeParameters != null) { 316 final DetailAST typeParam = 317 typeParameters.findFirstToken(TokenTypes.TYPE_PARAMETER); 318 typeParams.add(typeParam); 319 320 DetailAST sibling = typeParam.getNextSibling(); 321 while (sibling != null) { 322 if (sibling.getType() == TokenTypes.TYPE_PARAMETER) { 323 typeParams.add(sibling); 324 } 325 sibling = sibling.getNextSibling(); 326 } 327 } 328 329 return typeParams; 330 } 331 332 /** 333 * Returns whether an AST represents a setter method. 334 * 335 * @param ast the AST to check with 336 * @return whether the AST represents a setter method 337 */ 338 public static boolean isSetterMethod(final DetailAST ast) { 339 boolean setterMethod = false; 340 341 // Check have a method with exactly 7 children which are all that 342 // is allowed in a proper setter method which does not throw any 343 // exceptions. 344 if (ast.getType() == TokenTypes.METHOD_DEF 345 && ast.getChildCount() == SETTER_GETTER_MAX_CHILDREN) { 346 final DetailAST type = ast.findFirstToken(TokenTypes.TYPE); 347 final String name = type.getNextSibling().getText(); 348 final boolean matchesSetterFormat = SETTER_PATTERN.matcher(name).matches(); 349 final boolean voidReturnType = type.findFirstToken(TokenTypes.LITERAL_VOID) != null; 350 351 final DetailAST params = ast.findFirstToken(TokenTypes.PARAMETERS); 352 final boolean singleParam = params.getChildCount(TokenTypes.PARAMETER_DEF) == 1; 353 354 if (matchesSetterFormat && voidReturnType && singleParam) { 355 // Now verify that the body consists of: 356 // SLIST -> EXPR -> ASSIGN 357 // SEMI 358 // RCURLY 359 final DetailAST slist = ast.findFirstToken(TokenTypes.SLIST); 360 361 if (slist != null && slist.getChildCount() == SETTER_BODY_SIZE) { 362 final DetailAST expr = slist.getFirstChild(); 363 setterMethod = expr.getFirstChild().getType() == TokenTypes.ASSIGN; 364 } 365 } 366 } 367 return setterMethod; 368 } 369 370 /** 371 * Returns whether an AST represents a getter method. 372 * 373 * @param ast the AST to check with 374 * @return whether the AST represents a getter method 375 */ 376 public static boolean isGetterMethod(final DetailAST ast) { 377 boolean getterMethod = false; 378 379 // Check have a method with exactly 7 children which are all that 380 // is allowed in a proper getter method which does not throw any 381 // exceptions. 382 if (ast.getType() == TokenTypes.METHOD_DEF 383 && ast.getChildCount() == SETTER_GETTER_MAX_CHILDREN) { 384 final DetailAST type = ast.findFirstToken(TokenTypes.TYPE); 385 final String name = type.getNextSibling().getText(); 386 final boolean matchesGetterFormat = GETTER_PATTERN.matcher(name).matches(); 387 final boolean noVoidReturnType = type.findFirstToken(TokenTypes.LITERAL_VOID) == null; 388 389 final DetailAST params = ast.findFirstToken(TokenTypes.PARAMETERS); 390 final boolean noParams = params.getChildCount(TokenTypes.PARAMETER_DEF) == 0; 391 392 if (matchesGetterFormat && noVoidReturnType && noParams) { 393 // Now verify that the body consists of: 394 // SLIST -> RETURN 395 // RCURLY 396 final DetailAST slist = ast.findFirstToken(TokenTypes.SLIST); 397 398 if (slist != null && slist.getChildCount() == GETTER_BODY_SIZE) { 399 final DetailAST expr = slist.getFirstChild(); 400 getterMethod = expr.getType() == TokenTypes.LITERAL_RETURN; 401 } 402 } 403 } 404 return getterMethod; 405 } 406 407 /** 408 * Checks whether a method is a not void one. 409 * 410 * @param methodDefAst the method node. 411 * @return true if method is a not void one. 412 */ 413 public static boolean isNonVoidMethod(DetailAST methodDefAst) { 414 boolean returnValue = false; 415 if (methodDefAst.getType() == TokenTypes.METHOD_DEF) { 416 final DetailAST typeAST = methodDefAst.findFirstToken(TokenTypes.TYPE); 417 if (typeAST.findFirstToken(TokenTypes.LITERAL_VOID) == null) { 418 returnValue = true; 419 } 420 } 421 return returnValue; 422 } 423 424 /** 425 * Checks whether a parameter is a receiver. 426 * 427 * @param parameterDefAst the parameter node. 428 * @return true if the parameter is a receiver. 429 */ 430 public static boolean isReceiverParameter(DetailAST parameterDefAst) { 431 return parameterDefAst.getType() == TokenTypes.PARAMETER_DEF 432 && parameterDefAst.findFirstToken(TokenTypes.IDENT) == null; 433 } 434 435 /** 436 * Returns {@link AccessModifierOption} based on the information about access modifier 437 * taken from the given token of type {@link TokenTypes#MODIFIERS}. 438 * 439 * @param modifiersToken token of type {@link TokenTypes#MODIFIERS}. 440 * @return {@link AccessModifierOption}. 441 * @throws IllegalArgumentException when expected non-null modifiersToken with type 'MODIFIERS' 442 */ 443 public static AccessModifierOption 444 getAccessModifierFromModifiersToken(DetailAST modifiersToken) { 445 if (modifiersToken == null || modifiersToken.getType() != TokenTypes.MODIFIERS) { 446 throw new IllegalArgumentException("expected non-null AST-token with type 'MODIFIERS'"); 447 } 448 449 // default access modifier 450 AccessModifierOption accessModifier = AccessModifierOption.PACKAGE; 451 for (DetailAST token = modifiersToken.getFirstChild(); token != null; 452 token = token.getNextSibling()) { 453 final int tokenType = token.getType(); 454 if (tokenType == TokenTypes.LITERAL_PUBLIC) { 455 accessModifier = AccessModifierOption.PUBLIC; 456 } 457 else if (tokenType == TokenTypes.LITERAL_PROTECTED) { 458 accessModifier = AccessModifierOption.PROTECTED; 459 } 460 else if (tokenType == TokenTypes.LITERAL_PRIVATE) { 461 accessModifier = AccessModifierOption.PRIVATE; 462 } 463 } 464 return accessModifier; 465 } 466 467 /** 468 * Create set of class names and short class names. 469 * 470 * @param classNames array of class names. 471 * @return set of class names and short class names. 472 */ 473 public static Set<String> parseClassNames(String... classNames) { 474 final Set<String> illegalClassNames = new HashSet<>(); 475 for (final String name : classNames) { 476 illegalClassNames.add(name); 477 final int lastDot = name.lastIndexOf('.'); 478 if (lastDot != -1 && lastDot < name.length() - 1) { 479 final String shortName = name 480 .substring(name.lastIndexOf('.') + 1); 481 illegalClassNames.add(shortName); 482 } 483 } 484 return illegalClassNames; 485 } 486 487}