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.AccessModifier; 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 * @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 = 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 * @param ast the token to check 122 * @return whether it is 123 */ 124 public static boolean isElseIf(DetailAST ast) { 125 final DetailAST parentAST = ast.getParent(); 126 127 return ast.getType() == TokenTypes.LITERAL_IF 128 && (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 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 return ast.getType() == TokenTypes.SLIST 148 && ast.getChildCount() == 2 149 && 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 String txt = UNDERSCORE_PATTERN.matcher(text).replaceAll(""); 162 final double result; 163 switch (type) { 164 case TokenTypes.NUM_FLOAT: 165 case TokenTypes.NUM_DOUBLE: 166 result = Double.parseDouble(txt); 167 break; 168 case TokenTypes.NUM_INT: 169 case TokenTypes.NUM_LONG: 170 int radix = BASE_10; 171 if (txt.startsWith("0x") || txt.startsWith("0X")) { 172 radix = BASE_16; 173 txt = txt.substring(2); 174 } 175 else if (txt.startsWith("0b") || txt.startsWith("0B")) { 176 radix = BASE_2; 177 txt = txt.substring(2); 178 } 179 else if (CommonUtil.startsWithChar(txt, '0')) { 180 radix = BASE_8; 181 txt = txt.substring(1); 182 } 183 result = parseNumber(txt, radix, type); 184 break; 185 default: 186 result = Double.NaN; 187 break; 188 } 189 return result; 190 } 191 192 /** 193 * Parses the string argument as an integer or a long in the radix specified by 194 * the second argument. The characters in the string must all be digits of 195 * the specified radix. 196 * @param text the String containing the integer representation to be 197 * parsed. Precondition: text contains a parsable int. 198 * @param radix the radix to be used while parsing text. 199 * @param type the token type of the text. Should be a constant of 200 * {@link TokenTypes}. 201 * @return the number represented by the string argument in the specified radix. 202 */ 203 private static double parseNumber(final String text, final int radix, final int type) { 204 String txt = text; 205 if (CommonUtil.endsWithChar(txt, 'L') || CommonUtil.endsWithChar(txt, 'l')) { 206 txt = txt.substring(0, txt.length() - 1); 207 } 208 final double result; 209 if (txt.isEmpty()) { 210 result = 0.0; 211 } 212 else { 213 final boolean negative = txt.charAt(0) == '-'; 214 if (type == TokenTypes.NUM_INT) { 215 if (negative) { 216 result = Integer.parseInt(txt, radix); 217 } 218 else { 219 result = Integer.parseUnsignedInt(txt, radix); 220 } 221 } 222 else { 223 if (negative) { 224 result = Long.parseLong(txt, radix); 225 } 226 else { 227 result = Long.parseUnsignedLong(txt, radix); 228 } 229 } 230 } 231 return result; 232 } 233 234 /** 235 * Finds sub-node for given node minimal (line, column) pair. 236 * @param node the root of tree for search. 237 * @return sub-node with minimal (line, column) pair. 238 */ 239 public static DetailAST getFirstNode(final DetailAST node) { 240 DetailAST currentNode = node; 241 DetailAST child = node.getFirstChild(); 242 while (child != null) { 243 final DetailAST newNode = getFirstNode(child); 244 if (isBeforeInSource(newNode, currentNode)) { 245 currentNode = newNode; 246 } 247 child = child.getNextSibling(); 248 } 249 250 return currentNode; 251 } 252 253 /** 254 * Retrieves whether ast1 is located before ast2. 255 * @param ast1 the first node. 256 * @param ast2 the second node. 257 * @return true, if ast1 is located before ast2. 258 */ 259 public static boolean isBeforeInSource(DetailAST ast1, DetailAST ast2) { 260 return ast1.getLineNo() < ast2.getLineNo() 261 || ast1.getLineNo() == ast2.getLineNo() 262 && ast1.getColumnNo() < ast2.getColumnNo(); 263 } 264 265 /** 266 * Retrieves the names of the type parameters to the node. 267 * @param node the parameterized AST node 268 * @return a list of type parameter names 269 */ 270 public static List<String> getTypeParameterNames(final DetailAST node) { 271 final DetailAST typeParameters = 272 node.findFirstToken(TokenTypes.TYPE_PARAMETERS); 273 274 final List<String> typeParameterNames = new ArrayList<>(); 275 if (typeParameters != null) { 276 final DetailAST typeParam = 277 typeParameters.findFirstToken(TokenTypes.TYPE_PARAMETER); 278 typeParameterNames.add( 279 typeParam.findFirstToken(TokenTypes.IDENT).getText()); 280 281 DetailAST sibling = typeParam.getNextSibling(); 282 while (sibling != null) { 283 if (sibling.getType() == TokenTypes.TYPE_PARAMETER) { 284 typeParameterNames.add( 285 sibling.findFirstToken(TokenTypes.IDENT).getText()); 286 } 287 sibling = sibling.getNextSibling(); 288 } 289 } 290 291 return typeParameterNames; 292 } 293 294 /** 295 * Retrieves the type parameters to the node. 296 * @param node the parameterized AST node 297 * @return a list of type parameter names 298 */ 299 public static List<DetailAST> getTypeParameters(final DetailAST node) { 300 final DetailAST typeParameters = 301 node.findFirstToken(TokenTypes.TYPE_PARAMETERS); 302 303 final List<DetailAST> typeParams = new ArrayList<>(); 304 if (typeParameters != null) { 305 final DetailAST typeParam = 306 typeParameters.findFirstToken(TokenTypes.TYPE_PARAMETER); 307 typeParams.add(typeParam); 308 309 DetailAST sibling = typeParam.getNextSibling(); 310 while (sibling != null) { 311 if (sibling.getType() == TokenTypes.TYPE_PARAMETER) { 312 typeParams.add(sibling); 313 } 314 sibling = sibling.getNextSibling(); 315 } 316 } 317 318 return typeParams; 319 } 320 321 /** 322 * Returns whether an AST represents a setter method. 323 * @param ast the AST to check with 324 * @return whether the AST represents a setter method 325 */ 326 public static boolean isSetterMethod(final DetailAST ast) { 327 boolean setterMethod = false; 328 329 // Check have a method with exactly 7 children which are all that 330 // is allowed in a proper setter method which does not throw any 331 // exceptions. 332 if (ast.getType() == TokenTypes.METHOD_DEF 333 && ast.getChildCount() == SETTER_GETTER_MAX_CHILDREN) { 334 final DetailAST type = ast.findFirstToken(TokenTypes.TYPE); 335 final String name = type.getNextSibling().getText(); 336 final boolean matchesSetterFormat = SETTER_PATTERN.matcher(name).matches(); 337 final boolean voidReturnType = type.findFirstToken(TokenTypes.LITERAL_VOID) != null; 338 339 final DetailAST params = ast.findFirstToken(TokenTypes.PARAMETERS); 340 final boolean singleParam = params.getChildCount(TokenTypes.PARAMETER_DEF) == 1; 341 342 if (matchesSetterFormat && voidReturnType && singleParam) { 343 // Now verify that the body consists of: 344 // SLIST -> EXPR -> ASSIGN 345 // SEMI 346 // RCURLY 347 final DetailAST slist = ast.findFirstToken(TokenTypes.SLIST); 348 349 if (slist != null && slist.getChildCount() == SETTER_BODY_SIZE) { 350 final DetailAST expr = slist.getFirstChild(); 351 setterMethod = expr.getFirstChild().getType() == TokenTypes.ASSIGN; 352 } 353 } 354 } 355 return setterMethod; 356 } 357 358 /** 359 * Returns whether an AST represents a getter method. 360 * @param ast the AST to check with 361 * @return whether the AST represents a getter method 362 */ 363 public static boolean isGetterMethod(final DetailAST ast) { 364 boolean getterMethod = false; 365 366 // Check have a method with exactly 7 children which are all that 367 // is allowed in a proper getter method which does not throw any 368 // exceptions. 369 if (ast.getType() == TokenTypes.METHOD_DEF 370 && ast.getChildCount() == SETTER_GETTER_MAX_CHILDREN) { 371 final DetailAST type = ast.findFirstToken(TokenTypes.TYPE); 372 final String name = type.getNextSibling().getText(); 373 final boolean matchesGetterFormat = GETTER_PATTERN.matcher(name).matches(); 374 final boolean noVoidReturnType = type.findFirstToken(TokenTypes.LITERAL_VOID) == null; 375 376 final DetailAST params = ast.findFirstToken(TokenTypes.PARAMETERS); 377 final boolean noParams = params.getChildCount(TokenTypes.PARAMETER_DEF) == 0; 378 379 if (matchesGetterFormat && noVoidReturnType && noParams) { 380 // Now verify that the body consists of: 381 // SLIST -> RETURN 382 // RCURLY 383 final DetailAST slist = ast.findFirstToken(TokenTypes.SLIST); 384 385 if (slist != null && slist.getChildCount() == GETTER_BODY_SIZE) { 386 final DetailAST expr = slist.getFirstChild(); 387 getterMethod = expr.getType() == TokenTypes.LITERAL_RETURN; 388 } 389 } 390 } 391 return getterMethod; 392 } 393 394 /** 395 * Checks whether a method is a not void one. 396 * 397 * @param methodDefAst the method node. 398 * @return true if method is a not void one. 399 */ 400 public static boolean isNonVoidMethod(DetailAST methodDefAst) { 401 boolean returnValue = false; 402 if (methodDefAst.getType() == TokenTypes.METHOD_DEF) { 403 final DetailAST typeAST = methodDefAst.findFirstToken(TokenTypes.TYPE); 404 if (typeAST.findFirstToken(TokenTypes.LITERAL_VOID) == null) { 405 returnValue = true; 406 } 407 } 408 return returnValue; 409 } 410 411 /** 412 * Checks whether a parameter is a receiver. 413 * 414 * @param parameterDefAst the parameter node. 415 * @return true if the parameter is a receiver. 416 */ 417 public static boolean isReceiverParameter(DetailAST parameterDefAst) { 418 return parameterDefAst.getType() == TokenTypes.PARAMETER_DEF 419 && parameterDefAst.findFirstToken(TokenTypes.IDENT) == null; 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 * @throws IllegalArgumentException when expected non-null modifiersToken with type 'MODIFIERS' 428 */ 429 public static AccessModifier getAccessModifierFromModifiersToken(DetailAST modifiersToken) { 430 if (modifiersToken == null || modifiersToken.getType() != TokenTypes.MODIFIERS) { 431 throw new IllegalArgumentException("expected non-null AST-token with type 'MODIFIERS'"); 432 } 433 434 // default access modifier 435 AccessModifier accessModifier = AccessModifier.PACKAGE; 436 for (DetailAST token = modifiersToken.getFirstChild(); token != null; 437 token = token.getNextSibling()) { 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 472}