001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2018 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 043 // constants for parseDouble() 044 /** Octal radix. */ 045 private static final int BASE_8 = 8; 046 047 /** Decimal radix. */ 048 private static final int BASE_10 = 10; 049 050 /** Hex radix. */ 051 private static final int BASE_16 = 16; 052 053 /** Maximum children allowed in setter/getter. */ 054 private static final int SETTER_GETTER_MAX_CHILDREN = 7; 055 056 /** Maximum nodes allowed in a body of setter. */ 057 private static final int SETTER_BODY_SIZE = 3; 058 059 /** Maximum nodes allowed in a body of getter. */ 060 private static final int GETTER_BODY_SIZE = 2; 061 062 /** Pattern matching underscore characters ('_'). */ 063 private static final Pattern UNDERSCORE_PATTERN = Pattern.compile("_"); 064 065 /** Pattern matching names of setter methods. */ 066 private static final Pattern SETTER_PATTERN = Pattern.compile("^set[A-Z].*"); 067 068 /** Pattern matching names of getter methods. */ 069 private static final Pattern GETTER_PATTERN = Pattern.compile("^(is|get)[A-Z].*"); 070 071 /** Prevent instances. */ 072 private CheckUtils() { 073 } 074 075 /** 076 * Creates {@code FullIdent} for given type node. 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 * @param ast the method definition AST to test. 094 * Precondition: ast is a TokenTypes.METHOD_DEF node. 095 * @return true if ast defines an equals covariant. 096 */ 097 public static boolean isEqualsMethod(DetailAST ast) { 098 boolean equalsMethod = false; 099 100 if (ast.getType() == TokenTypes.METHOD_DEF) { 101 final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS); 102 final boolean staticOrAbstract = 103 modifiers.findFirstToken(TokenTypes.LITERAL_STATIC) != null 104 || modifiers.findFirstToken(TokenTypes.ABSTRACT) != null; 105 106 if (!staticOrAbstract) { 107 final DetailAST nameNode = ast.findFirstToken(TokenTypes.IDENT); 108 final String name = nameNode.getText(); 109 110 if ("equals".equals(name)) { 111 // one parameter? 112 final DetailAST paramsNode = ast.findFirstToken(TokenTypes.PARAMETERS); 113 equalsMethod = paramsNode.getChildCount() == 1; 114 } 115 } 116 } 117 return equalsMethod; 118 } 119 120 /** 121 * Returns whether a token represents an ELSE as part of an ELSE / IF set. 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 * @param ast the token to check 135 * @return whether the token represents an ELSE 136 */ 137 private static boolean isElse(DetailAST ast) { 138 return ast.getType() == TokenTypes.LITERAL_ELSE; 139 } 140 141 /** 142 * Returns whether a token represents an SLIST as part of an ELSE 143 * statement. 144 * @param ast the token to check 145 * @return whether the toke does represent an SLIST as part of an ELSE 146 */ 147 private static boolean isElseWithCurlyBraces(DetailAST ast) { 148 return ast.getType() == TokenTypes.SLIST 149 && ast.getChildCount() == 2 150 && isElse(ast.getParent()); 151 } 152 153 /** 154 * Returns the value represented by the specified string of the specified 155 * type. Returns 0 for types other than float, double, int, and long. 156 * @param text the string to be parsed. 157 * @param type the token type of the text. Should be a constant of 158 * {@link TokenTypes}. 159 * @return the double value represented by the string argument. 160 */ 161 public static double parseDouble(String text, int type) { 162 String txt = UNDERSCORE_PATTERN.matcher(text).replaceAll(""); 163 double result = 0; 164 switch (type) { 165 case TokenTypes.NUM_FLOAT: 166 case TokenTypes.NUM_DOUBLE: 167 result = Double.parseDouble(txt); 168 break; 169 case TokenTypes.NUM_INT: 170 case TokenTypes.NUM_LONG: 171 int radix = BASE_10; 172 if (txt.startsWith("0x") || txt.startsWith("0X")) { 173 radix = BASE_16; 174 txt = txt.substring(2); 175 } 176 else if (txt.charAt(0) == '0') { 177 radix = BASE_8; 178 txt = txt.substring(1); 179 } 180 if (CommonUtils.endsWithChar(txt, 'L') || CommonUtils.endsWithChar(txt, 'l')) { 181 txt = txt.substring(0, txt.length() - 1); 182 } 183 if (!txt.isEmpty()) { 184 if (type == TokenTypes.NUM_INT) { 185 result = parseInt(txt, radix); 186 } 187 else { 188 result = parseLong(txt, radix); 189 } 190 } 191 break; 192 default: 193 break; 194 } 195 return result; 196 } 197 198 /** 199 * Parses the string argument as a signed integer in the radix specified by 200 * the second argument. The characters in the string must all be digits of 201 * the specified radix. Handles negative values, which method 202 * java.lang.Integer.parseInt(String, int) does not. 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 * @return the integer represented by the string argument in the specified radix. 207 */ 208 private static int parseInt(String text, int radix) { 209 int result = 0; 210 final int max = text.length(); 211 for (int i = 0; i < max; i++) { 212 final int digit = Character.digit(text.charAt(i), radix); 213 result *= radix; 214 result += digit; 215 } 216 return result; 217 } 218 219 /** 220 * Parses the string argument as a signed long in the radix specified by 221 * the second argument. The characters in the string must all be digits of 222 * the specified radix. Handles negative values, which method 223 * java.lang.Integer.parseInt(String, int) does not. 224 * @param text the String containing the integer representation to be 225 * parsed. Precondition: text contains a parsable int. 226 * @param radix the radix to be used while parsing text. 227 * @return the long represented by the string argument in the specified radix. 228 */ 229 private static long parseLong(String text, int radix) { 230 long result = 0; 231 final int max = text.length(); 232 for (int i = 0; i < max; i++) { 233 final int digit = Character.digit(text.charAt(i), radix); 234 result *= radix; 235 result += digit; 236 } 237 return result; 238 } 239 240 /** 241 * Finds sub-node for given node minimal (line, column) pair. 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 (newNode.getLineNo() < currentNode.getLineNo() 251 || newNode.getLineNo() == currentNode.getLineNo() 252 && newNode.getColumnNo() < currentNode.getColumnNo()) { 253 currentNode = newNode; 254 } 255 child = child.getNextSibling(); 256 } 257 258 return currentNode; 259 } 260 261 /** 262 * Retrieves the names of the type parameters to the node. 263 * @param node the parameterized AST node 264 * @return a list of type parameter names 265 */ 266 public static List<String> getTypeParameterNames(final DetailAST node) { 267 final DetailAST typeParameters = 268 node.findFirstToken(TokenTypes.TYPE_PARAMETERS); 269 270 final List<String> typeParameterNames = new ArrayList<>(); 271 if (typeParameters != null) { 272 final DetailAST typeParam = 273 typeParameters.findFirstToken(TokenTypes.TYPE_PARAMETER); 274 typeParameterNames.add( 275 typeParam.findFirstToken(TokenTypes.IDENT).getText()); 276 277 DetailAST sibling = typeParam.getNextSibling(); 278 while (sibling != null) { 279 if (sibling.getType() == TokenTypes.TYPE_PARAMETER) { 280 typeParameterNames.add( 281 sibling.findFirstToken(TokenTypes.IDENT).getText()); 282 } 283 sibling = sibling.getNextSibling(); 284 } 285 } 286 287 return typeParameterNames; 288 } 289 290 /** 291 * Retrieves the type parameters to the node. 292 * @param node the parameterized AST node 293 * @return a list of type parameter names 294 */ 295 public static List<DetailAST> getTypeParameters(final DetailAST node) { 296 final DetailAST typeParameters = 297 node.findFirstToken(TokenTypes.TYPE_PARAMETERS); 298 299 final List<DetailAST> typeParams = new ArrayList<>(); 300 if (typeParameters != null) { 301 final DetailAST typeParam = 302 typeParameters.findFirstToken(TokenTypes.TYPE_PARAMETER); 303 typeParams.add(typeParam); 304 305 DetailAST sibling = typeParam.getNextSibling(); 306 while (sibling != null) { 307 if (sibling.getType() == TokenTypes.TYPE_PARAMETER) { 308 typeParams.add(sibling); 309 } 310 sibling = sibling.getNextSibling(); 311 } 312 } 313 314 return typeParams; 315 } 316 317 /** 318 * Returns whether an AST represents a setter method. 319 * @param ast the AST to check with 320 * @return whether the AST represents a setter method 321 */ 322 public static boolean isSetterMethod(final DetailAST ast) { 323 boolean setterMethod = false; 324 325 // Check have a method with exactly 7 children which are all that 326 // is allowed in a proper setter method which does not throw any 327 // exceptions. 328 if (ast.getType() == TokenTypes.METHOD_DEF 329 && ast.getChildCount() == SETTER_GETTER_MAX_CHILDREN) { 330 final DetailAST type = ast.findFirstToken(TokenTypes.TYPE); 331 final String name = type.getNextSibling().getText(); 332 final boolean matchesSetterFormat = SETTER_PATTERN.matcher(name).matches(); 333 final boolean voidReturnType = type.findFirstToken(TokenTypes.LITERAL_VOID) != null; 334 335 final DetailAST params = ast.findFirstToken(TokenTypes.PARAMETERS); 336 final boolean singleParam = params.getChildCount(TokenTypes.PARAMETER_DEF) == 1; 337 338 if (matchesSetterFormat && voidReturnType && singleParam) { 339 // Now verify that the body consists of: 340 // SLIST -> EXPR -> ASSIGN 341 // SEMI 342 // RCURLY 343 final DetailAST slist = ast.findFirstToken(TokenTypes.SLIST); 344 345 if (slist != null && slist.getChildCount() == SETTER_BODY_SIZE) { 346 final DetailAST expr = slist.getFirstChild(); 347 setterMethod = expr.getFirstChild().getType() == TokenTypes.ASSIGN; 348 } 349 } 350 } 351 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 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 if (ast.getType() == TokenTypes.METHOD_DEF 366 && ast.getChildCount() == SETTER_GETTER_MAX_CHILDREN) { 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 return parameterDefAst.getType() == TokenTypes.PARAMETER_DEF 415 && parameterDefAst.findFirstToken(TokenTypes.IDENT) == null; 416 } 417 418 /** 419 * Returns {@link AccessModifier} based on the information about access modifier 420 * taken from the given token of type {@link TokenTypes#MODIFIERS}. 421 * @param modifiersToken token of type {@link TokenTypes#MODIFIERS}. 422 * @return {@link AccessModifier}. 423 */ 424 public static AccessModifier getAccessModifierFromModifiersToken(DetailAST modifiersToken) { 425 if (modifiersToken == null || modifiersToken.getType() != TokenTypes.MODIFIERS) { 426 throw new IllegalArgumentException("expected non-null AST-token with type 'MODIFIERS'"); 427 } 428 429 // default access modifier 430 AccessModifier accessModifier = AccessModifier.PACKAGE; 431 for (AST token = modifiersToken.getFirstChild(); token != null; 432 token = token.getNextSibling()) { 433 final int tokenType = token.getType(); 434 if (tokenType == TokenTypes.LITERAL_PUBLIC) { 435 accessModifier = AccessModifier.PUBLIC; 436 } 437 else if (tokenType == TokenTypes.LITERAL_PROTECTED) { 438 accessModifier = AccessModifier.PROTECTED; 439 } 440 else if (tokenType == TokenTypes.LITERAL_PRIVATE) { 441 accessModifier = AccessModifier.PRIVATE; 442 } 443 } 444 return accessModifier; 445 } 446 447 /** 448 * Create set of class names and short class names. 449 * 450 * @param classNames array of class names. 451 * @return set of class names and short class names. 452 */ 453 public static Set<String> parseClassNames(String... classNames) { 454 final Set<String> illegalClassNames = new HashSet<>(); 455 for (final String name : classNames) { 456 illegalClassNames.add(name); 457 final int lastDot = name.lastIndexOf('.'); 458 if (lastDot != -1 && lastDot < name.length() - 1) { 459 final String shortName = name 460 .substring(name.lastIndexOf('.') + 1); 461 illegalClassNames.add(shortName); 462 } 463 } 464 return illegalClassNames; 465 } 466 467}