001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2021 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.Arrays; 024import java.util.HashSet; 025import java.util.List; 026import java.util.Set; 027import java.util.regex.Pattern; 028import java.util.stream.Collectors; 029 030import com.puppycrawl.tools.checkstyle.api.DetailAST; 031import com.puppycrawl.tools.checkstyle.api.FullIdent; 032import com.puppycrawl.tools.checkstyle.api.TokenTypes; 033import com.puppycrawl.tools.checkstyle.checks.naming.AccessModifierOption; 034 035/** 036 * Contains utility methods for the checks. 037 * 038 */ 039public final class CheckUtil { 040 041 // constants for parseDouble() 042 /** Binary radix. */ 043 private static final int BASE_2 = 2; 044 045 /** Octal radix. */ 046 private static final int BASE_8 = 8; 047 048 /** Decimal radix. */ 049 private static final int BASE_10 = 10; 050 051 /** Hex radix. */ 052 private static final int BASE_16 = 16; 053 054 /** Maximum children allowed in setter/getter. */ 055 private static final int SETTER_GETTER_MAX_CHILDREN = 7; 056 057 /** Maximum nodes allowed in a body of setter. */ 058 private static final int SETTER_BODY_SIZE = 3; 059 060 /** Maximum nodes allowed in a body of getter. */ 061 private static final int GETTER_BODY_SIZE = 2; 062 063 /** Pattern matching underscore characters ('_'). */ 064 private static final Pattern UNDERSCORE_PATTERN = Pattern.compile("_"); 065 066 /** Pattern matching names of setter methods. */ 067 private static final Pattern SETTER_PATTERN = Pattern.compile("^set[A-Z].*"); 068 069 /** Pattern matching names of getter methods. */ 070 private static final Pattern GETTER_PATTERN = Pattern.compile("^(is|get)[A-Z].*"); 071 072 /** Compiled pattern for all system newlines. */ 073 private static final Pattern ALL_NEW_LINES = Pattern.compile("\\R"); 074 075 /** Prevent instances. */ 076 private CheckUtil() { 077 } 078 079 /** 080 * Creates {@code FullIdent} for given type node. 081 * 082 * @param typeAST a type node. 083 * @return {@code FullIdent} for given type. 084 */ 085 public static FullIdent createFullType(final DetailAST typeAST) { 086 return FullIdent.createFullIdent(typeAST.getFirstChild()); 087 } 088 089 /** 090 * Tests whether a method definition AST defines an equals covariant. 091 * 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 = 102 modifiers.findFirstToken(TokenTypes.LITERAL_STATIC) != null 103 || modifiers.findFirstToken(TokenTypes.ABSTRACT) != null; 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 * 122 * @param ast the token to check 123 * @return whether it is 124 */ 125 public static boolean isElseIf(DetailAST ast) { 126 final DetailAST parentAST = ast.getParent(); 127 128 return ast.getType() == TokenTypes.LITERAL_IF 129 && (isElse(parentAST) || isElseWithCurlyBraces(parentAST)); 130 } 131 132 /** 133 * Returns whether a token represents an ELSE. 134 * 135 * @param ast the token to check 136 * @return whether the token represents an ELSE 137 */ 138 private static boolean isElse(DetailAST ast) { 139 return ast.getType() == TokenTypes.LITERAL_ELSE; 140 } 141 142 /** 143 * Returns whether a token represents an SLIST as part of an ELSE 144 * statement. 145 * 146 * @param ast the token to check 147 * @return whether the toke does represent an SLIST as part of an ELSE 148 */ 149 private static boolean isElseWithCurlyBraces(DetailAST ast) { 150 return ast.getType() == TokenTypes.SLIST 151 && ast.getChildCount() == 2 152 && isElse(ast.getParent()); 153 } 154 155 /** 156 * Returns the value represented by the specified string of the specified 157 * type. Returns 0 for types other than float, double, int, and long. 158 * 159 * @param text the string to be parsed. 160 * @param type the token type of the text. Should be a constant of 161 * {@link TokenTypes}. 162 * @return the double value represented by the string argument. 163 */ 164 public static double parseDouble(String text, int type) { 165 String txt = UNDERSCORE_PATTERN.matcher(text).replaceAll(""); 166 final double result; 167 switch (type) { 168 case TokenTypes.NUM_FLOAT: 169 case TokenTypes.NUM_DOUBLE: 170 result = Double.parseDouble(txt); 171 break; 172 case TokenTypes.NUM_INT: 173 case TokenTypes.NUM_LONG: 174 int radix = BASE_10; 175 if (txt.startsWith("0x") || txt.startsWith("0X")) { 176 radix = BASE_16; 177 txt = txt.substring(2); 178 } 179 else if (txt.startsWith("0b") || txt.startsWith("0B")) { 180 radix = BASE_2; 181 txt = txt.substring(2); 182 } 183 else if (CommonUtil.startsWithChar(txt, '0')) { 184 radix = BASE_8; 185 txt = txt.substring(1); 186 } 187 result = parseNumber(txt, radix, type); 188 break; 189 default: 190 result = Double.NaN; 191 break; 192 } 193 return result; 194 } 195 196 /** 197 * Parses the string argument as an integer or a long in the radix specified by 198 * the second argument. The characters in the string must all be digits of 199 * the specified radix. 200 * 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 * @param type the token type of the text. Should be a constant of 205 * {@link TokenTypes}. 206 * @return the number represented by the string argument in the specified radix. 207 */ 208 private static double parseNumber(final String text, final int radix, final int type) { 209 String txt = text; 210 if (CommonUtil.endsWithChar(txt, 'L') || CommonUtil.endsWithChar(txt, 'l')) { 211 txt = txt.substring(0, txt.length() - 1); 212 } 213 final double result; 214 if (txt.isEmpty()) { 215 result = 0.0; 216 } 217 else { 218 final boolean negative = txt.charAt(0) == '-'; 219 if (type == TokenTypes.NUM_INT) { 220 if (negative) { 221 result = Integer.parseInt(txt, radix); 222 } 223 else { 224 result = Integer.parseUnsignedInt(txt, radix); 225 } 226 } 227 else { 228 if (negative) { 229 result = Long.parseLong(txt, radix); 230 } 231 else { 232 result = Long.parseUnsignedLong(txt, radix); 233 } 234 } 235 } 236 return result; 237 } 238 239 /** 240 * Finds sub-node for given node minimal (line, column) pair. 241 * 242 * @param node the root of tree for search. 243 * @return sub-node with minimal (line, column) pair. 244 */ 245 public static DetailAST getFirstNode(final DetailAST node) { 246 DetailAST currentNode = node; 247 DetailAST child = node.getFirstChild(); 248 while (child != null) { 249 final DetailAST newNode = getFirstNode(child); 250 if (isBeforeInSource(newNode, currentNode)) { 251 currentNode = newNode; 252 } 253 child = child.getNextSibling(); 254 } 255 256 return currentNode; 257 } 258 259 /** 260 * Retrieves whether ast1 is located before ast2. 261 * 262 * @param ast1 the first node. 263 * @param ast2 the second node. 264 * @return true, if ast1 is located before ast2. 265 */ 266 public static boolean isBeforeInSource(DetailAST ast1, DetailAST ast2) { 267 return ast1.getLineNo() < ast2.getLineNo() 268 || TokenUtil.areOnSameLine(ast1, ast2) 269 && ast1.getColumnNo() < ast2.getColumnNo(); 270 } 271 272 /** 273 * Retrieves the names of the type parameters to the node. 274 * 275 * @param node the parameterized AST node 276 * @return a list of type parameter names 277 */ 278 public static List<String> getTypeParameterNames(final DetailAST node) { 279 final DetailAST typeParameters = 280 node.findFirstToken(TokenTypes.TYPE_PARAMETERS); 281 282 final List<String> typeParameterNames = new ArrayList<>(); 283 if (typeParameters != null) { 284 final DetailAST typeParam = 285 typeParameters.findFirstToken(TokenTypes.TYPE_PARAMETER); 286 typeParameterNames.add( 287 typeParam.findFirstToken(TokenTypes.IDENT).getText()); 288 289 DetailAST sibling = typeParam.getNextSibling(); 290 while (sibling != null) { 291 if (sibling.getType() == TokenTypes.TYPE_PARAMETER) { 292 typeParameterNames.add( 293 sibling.findFirstToken(TokenTypes.IDENT).getText()); 294 } 295 sibling = sibling.getNextSibling(); 296 } 297 } 298 299 return typeParameterNames; 300 } 301 302 /** 303 * Retrieves the type parameters to the node. 304 * 305 * @param node the parameterized AST node 306 * @return a list of type parameter names 307 */ 308 public static List<DetailAST> getTypeParameters(final DetailAST node) { 309 final DetailAST typeParameters = 310 node.findFirstToken(TokenTypes.TYPE_PARAMETERS); 311 312 final List<DetailAST> typeParams = new ArrayList<>(); 313 if (typeParameters != null) { 314 final DetailAST typeParam = 315 typeParameters.findFirstToken(TokenTypes.TYPE_PARAMETER); 316 typeParams.add(typeParam); 317 318 DetailAST sibling = typeParam.getNextSibling(); 319 while (sibling != null) { 320 if (sibling.getType() == TokenTypes.TYPE_PARAMETER) { 321 typeParams.add(sibling); 322 } 323 sibling = sibling.getNextSibling(); 324 } 325 } 326 327 return typeParams; 328 } 329 330 /** 331 * Returns whether an AST represents a setter method. 332 * 333 * @param ast the AST to check with 334 * @return whether the AST represents a setter method 335 */ 336 public static boolean isSetterMethod(final DetailAST ast) { 337 boolean setterMethod = false; 338 339 // Check have a method with exactly 7 children which are all that 340 // is allowed in a proper setter method which does not throw any 341 // exceptions. 342 if (ast.getType() == TokenTypes.METHOD_DEF 343 && ast.getChildCount() == SETTER_GETTER_MAX_CHILDREN) { 344 final DetailAST type = ast.findFirstToken(TokenTypes.TYPE); 345 final String name = type.getNextSibling().getText(); 346 final boolean matchesSetterFormat = SETTER_PATTERN.matcher(name).matches(); 347 final boolean voidReturnType = type.findFirstToken(TokenTypes.LITERAL_VOID) != null; 348 349 final DetailAST params = ast.findFirstToken(TokenTypes.PARAMETERS); 350 final boolean singleParam = params.getChildCount(TokenTypes.PARAMETER_DEF) == 1; 351 352 if (matchesSetterFormat && voidReturnType && singleParam) { 353 // Now verify that the body consists of: 354 // SLIST -> EXPR -> ASSIGN 355 // SEMI 356 // RCURLY 357 final DetailAST slist = ast.findFirstToken(TokenTypes.SLIST); 358 359 if (slist != null && slist.getChildCount() == SETTER_BODY_SIZE) { 360 final DetailAST expr = slist.getFirstChild(); 361 setterMethod = expr.getFirstChild().getType() == TokenTypes.ASSIGN; 362 } 363 } 364 } 365 return setterMethod; 366 } 367 368 /** 369 * Returns whether an AST represents a getter method. 370 * 371 * @param ast the AST to check with 372 * @return whether the AST represents a getter method 373 */ 374 public static boolean isGetterMethod(final DetailAST ast) { 375 boolean getterMethod = false; 376 377 // Check have a method with exactly 7 children which are all that 378 // is allowed in a proper getter method which does not throw any 379 // exceptions. 380 if (ast.getType() == TokenTypes.METHOD_DEF 381 && ast.getChildCount() == SETTER_GETTER_MAX_CHILDREN) { 382 final DetailAST type = ast.findFirstToken(TokenTypes.TYPE); 383 final String name = type.getNextSibling().getText(); 384 final boolean matchesGetterFormat = GETTER_PATTERN.matcher(name).matches(); 385 final boolean noVoidReturnType = type.findFirstToken(TokenTypes.LITERAL_VOID) == null; 386 387 final DetailAST params = ast.findFirstToken(TokenTypes.PARAMETERS); 388 final boolean noParams = params.getChildCount(TokenTypes.PARAMETER_DEF) == 0; 389 390 if (matchesGetterFormat && noVoidReturnType && noParams) { 391 // Now verify that the body consists of: 392 // SLIST -> RETURN 393 // RCURLY 394 final DetailAST slist = ast.findFirstToken(TokenTypes.SLIST); 395 396 if (slist != null && slist.getChildCount() == GETTER_BODY_SIZE) { 397 final DetailAST expr = slist.getFirstChild(); 398 getterMethod = expr.getType() == TokenTypes.LITERAL_RETURN; 399 } 400 } 401 } 402 return getterMethod; 403 } 404 405 /** 406 * Checks whether a method is a not void one. 407 * 408 * @param methodDefAst the method node. 409 * @return true if method is a not void one. 410 */ 411 public static boolean isNonVoidMethod(DetailAST methodDefAst) { 412 boolean returnValue = false; 413 if (methodDefAst.getType() == TokenTypes.METHOD_DEF) { 414 final DetailAST typeAST = methodDefAst.findFirstToken(TokenTypes.TYPE); 415 if (typeAST.findFirstToken(TokenTypes.LITERAL_VOID) == null) { 416 returnValue = true; 417 } 418 } 419 return returnValue; 420 } 421 422 /** 423 * Checks whether a parameter is a receiver. 424 * 425 * @param parameterDefAst the parameter node. 426 * @return true if the parameter is a receiver. 427 */ 428 public static boolean isReceiverParameter(DetailAST parameterDefAst) { 429 return parameterDefAst.getType() == TokenTypes.PARAMETER_DEF 430 && parameterDefAst.findFirstToken(TokenTypes.IDENT) == null; 431 } 432 433 /** 434 * Returns the access modifier of the method/constructor at the specified AST. If 435 * the method is in an interface or annotation block, the access modifier is assumed 436 * to be public. 437 * 438 * @param ast the token of the method/constructor. 439 * @return the access modifier of the method/constructor. 440 */ 441 public static AccessModifierOption getAccessModifierFromModifiersToken(DetailAST ast) { 442 final AccessModifierOption accessModifier; 443 444 if (ScopeUtil.isInInterfaceOrAnnotationBlock(ast)) { 445 accessModifier = AccessModifierOption.PUBLIC; 446 } 447 else { 448 final DetailAST modsToken = ast.findFirstToken(TokenTypes.MODIFIERS); 449 accessModifier = getAccessModifierFromModifiersTokenDirectly(modsToken); 450 } 451 452 return accessModifier; 453 } 454 455 /** 456 * Returns {@link AccessModifierOption} based on the information about access modifier 457 * taken from the given token of type {@link TokenTypes#MODIFIERS}. 458 * 459 * @param modifiersToken token of type {@link TokenTypes#MODIFIERS}. 460 * @return {@link AccessModifierOption}. 461 * @throws IllegalArgumentException when expected non-null modifiersToken with type 'MODIFIERS' 462 */ 463 private static AccessModifierOption getAccessModifierFromModifiersTokenDirectly( 464 DetailAST modifiersToken) { 465 if (modifiersToken == null) { 466 throw new IllegalArgumentException("expected non-null AST-token with type 'MODIFIERS'"); 467 } 468 469 // default access modifier 470 AccessModifierOption accessModifier = AccessModifierOption.PACKAGE; 471 for (DetailAST token = modifiersToken.getFirstChild(); token != null; 472 token = token.getNextSibling()) { 473 final int tokenType = token.getType(); 474 if (tokenType == TokenTypes.LITERAL_PUBLIC) { 475 accessModifier = AccessModifierOption.PUBLIC; 476 } 477 else if (tokenType == TokenTypes.LITERAL_PROTECTED) { 478 accessModifier = AccessModifierOption.PROTECTED; 479 } 480 else if (tokenType == TokenTypes.LITERAL_PRIVATE) { 481 accessModifier = AccessModifierOption.PRIVATE; 482 } 483 } 484 return accessModifier; 485 } 486 487 /** 488 * Returns the access modifier of the surrounding "block". 489 * 490 * @param node the node to return the access modifier for 491 * @return the access modifier of the surrounding block 492 */ 493 public static AccessModifierOption getSurroundingAccessModifier(DetailAST node) { 494 AccessModifierOption returnValue = null; 495 for (DetailAST token = node.getParent(); 496 token != null && returnValue == null; 497 token = token.getParent()) { 498 final int type = token.getType(); 499 if (type == TokenTypes.CLASS_DEF 500 || type == TokenTypes.INTERFACE_DEF 501 || type == TokenTypes.ANNOTATION_DEF 502 || type == TokenTypes.ENUM_DEF) { 503 final DetailAST mods = 504 token.findFirstToken(TokenTypes.MODIFIERS); 505 returnValue = getAccessModifierFromModifiersTokenDirectly(mods); 506 } 507 else if (type == TokenTypes.LITERAL_NEW) { 508 break; 509 } 510 } 511 512 return returnValue; 513 } 514 515 /** 516 * Create set of class names and short class names. 517 * 518 * @param classNames array of class names. 519 * @return set of class names and short class names. 520 */ 521 public static Set<String> parseClassNames(String... classNames) { 522 final Set<String> illegalClassNames = new HashSet<>(); 523 for (final String name : classNames) { 524 illegalClassNames.add(name); 525 final int lastDot = name.lastIndexOf('.'); 526 if (lastDot != -1 && lastDot < name.length() - 1) { 527 final String shortName = name 528 .substring(name.lastIndexOf('.') + 1); 529 illegalClassNames.add(shortName); 530 } 531 } 532 return illegalClassNames; 533 } 534 535 /** 536 * Strip initial newline and preceding whitespace on each line from text block content. 537 * In order to be consistent with how javac handles this task, we have modeled this 538 * implementation after the code from: 539 * github.com/openjdk/jdk14u/blob/master/src/java.base/share/classes/java/lang/String.java 540 * 541 * @param textBlockContent the actual content of the text block. 542 * @return string consistent with javac representation. 543 */ 544 public static String stripIndentAndInitialNewLineFromTextBlock(String textBlockContent) { 545 final String contentWithInitialNewLineRemoved = 546 ALL_NEW_LINES.matcher(textBlockContent).replaceFirst(""); 547 final List<String> lines = 548 Arrays.asList(ALL_NEW_LINES.split(contentWithInitialNewLineRemoved)); 549 final int indent = getSmallestIndent(lines); 550 final String suffix = ""; 551 552 return lines.stream() 553 .map(line -> stripIndentAndTrailingWhitespaceFromLine(line, indent)) 554 .collect(Collectors.joining(System.lineSeparator(), suffix, suffix)); 555 } 556 557 /** 558 * Helper method for stripIndentAndInitialNewLineFromTextBlock, strips correct indent 559 * from string, and trailing whitespace, or returns empty string if no text. 560 * 561 * @param line the string to strip indent and trailing whitespace from 562 * @param indent the amount of indent to remove 563 * @return modified string with removed indent and trailing whitespace, or empty string. 564 */ 565 private static String stripIndentAndTrailingWhitespaceFromLine(String line, int indent) { 566 final int lastNonWhitespace = lastIndexOfNonWhitespace(line); 567 String returnString = ""; 568 if (lastNonWhitespace > 0) { 569 returnString = line.substring(indent, lastNonWhitespace); 570 } 571 return returnString; 572 } 573 574 /** 575 * Helper method for stripIndentAndInitialNewLineFromTextBlock, to determine the smallest 576 * indent in a text block string literal. 577 * 578 * @param lines list of actual text block content, split by line. 579 * @return number of spaces representing the smallest indent in this text block. 580 */ 581 private static int getSmallestIndent(List<String> lines) { 582 return lines.stream() 583 .mapToInt(CommonUtil::indexOfNonWhitespace) 584 .min() 585 .orElse(0); 586 } 587 588 /** 589 * Helper method to find the index of the last non-whitespace character in a string. 590 * 591 * @param line the string to find the last index of a non-whitespace character for. 592 * @return the index of the last non-whitespace character. 593 */ 594 private static int lastIndexOfNonWhitespace(String line) { 595 int length; 596 for (length = line.length(); length > 0; length--) { 597 if (!Character.isWhitespace(line.charAt(length - 1))) { 598 break; 599 } 600 } 601 return length; 602 } 603}