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