001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2017 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.List; 024import java.util.regex.Pattern; 025 026import antlr.collections.AST; 027import com.puppycrawl.tools.checkstyle.api.DetailAST; 028import com.puppycrawl.tools.checkstyle.api.FullIdent; 029import com.puppycrawl.tools.checkstyle.api.TokenTypes; 030import com.puppycrawl.tools.checkstyle.checks.naming.AccessModifier; 031 032/** 033 * Contains utility methods for the checks. 034 * 035 * @author Oliver Burn 036 * @author <a href="mailto:simon@redhillconsulting.com.au">Simon Harris</a> 037 * @author o_sukhodolsky 038 */ 039public final class CheckUtils { 040 // constants for parseDouble() 041 /** Octal radix. */ 042 private static final int BASE_8 = 8; 043 044 /** Decimal radix. */ 045 private static final int BASE_10 = 10; 046 047 /** Hex radix. */ 048 private static final int BASE_16 = 16; 049 050 /** Maximum children allowed in setter/getter. */ 051 private static final int SETTER_GETTER_MAX_CHILDREN = 7; 052 053 /** Maximum nodes allowed in a body of setter. */ 054 private static final int SETTER_BODY_SIZE = 3; 055 056 /** Maximum nodes allowed in a body of getter. */ 057 private static final int GETTER_BODY_SIZE = 2; 058 059 /** Pattern matching underscore characters ('_'). */ 060 private static final Pattern UNDERSCORE_PATTERN = Pattern.compile("_"); 061 062 /** Pattern matching names of setter methods. */ 063 private static final Pattern SETTER_PATTERN = Pattern.compile("^set[A-Z].*"); 064 065 /** Pattern matching names of getter methods. */ 066 private static final Pattern GETTER_PATTERN = Pattern.compile("^(is|get)[A-Z].*"); 067 068 /** Prevent instances. */ 069 private CheckUtils() { 070 } 071 072 /** 073 * Creates {@code FullIdent} for given type node. 074 * @param typeAST a type node. 075 * @return {@code FullIdent} for given type. 076 */ 077 public static FullIdent createFullType(DetailAST typeAST) { 078 final DetailAST arrayDeclaratorAST = 079 typeAST.findFirstToken(TokenTypes.ARRAY_DECLARATOR); 080 final FullIdent fullType; 081 082 if (arrayDeclaratorAST == null) { 083 fullType = createFullTypeNoArrays(typeAST); 084 } 085 else { 086 fullType = createFullTypeNoArrays(arrayDeclaratorAST); 087 } 088 return fullType; 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 = modifiers.branchContains(TokenTypes.LITERAL_STATIC) 103 || modifiers.branchContains(TokenTypes.ABSTRACT); 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 FullIndent for given type. 154 * @param typeAST a type node (no array) 155 * @return {@code FullIdent} for given type. 156 */ 157 private static FullIdent createFullTypeNoArrays(DetailAST typeAST) { 158 return FullIdent.createFullIdent(typeAST.getFirstChild()); 159 } 160 161 /** 162 * Returns the value represented by the specified string of the specified 163 * type. Returns 0 for types other than float, double, int, and long. 164 * @param text the string to be parsed. 165 * @param type the token type of the text. Should be a constant of 166 * {@link TokenTypes}. 167 * @return the double value represented by the string argument. 168 */ 169 public static double parseDouble(String text, int type) { 170 String txt = UNDERSCORE_PATTERN.matcher(text).replaceAll(""); 171 double result = 0; 172 switch (type) { 173 case TokenTypes.NUM_FLOAT: 174 case TokenTypes.NUM_DOUBLE: 175 result = Double.parseDouble(txt); 176 break; 177 case TokenTypes.NUM_INT: 178 case TokenTypes.NUM_LONG: 179 int radix = BASE_10; 180 if (txt.startsWith("0x") || txt.startsWith("0X")) { 181 radix = BASE_16; 182 txt = txt.substring(2); 183 } 184 else if (txt.charAt(0) == '0') { 185 radix = BASE_8; 186 txt = txt.substring(1); 187 } 188 if (CommonUtils.endsWithChar(txt, 'L') || CommonUtils.endsWithChar(txt, 'l')) { 189 txt = txt.substring(0, txt.length() - 1); 190 } 191 if (!txt.isEmpty()) { 192 if (type == TokenTypes.NUM_INT) { 193 result = parseInt(txt, radix); 194 } 195 else { 196 result = parseLong(txt, radix); 197 } 198 } 199 break; 200 default: 201 break; 202 } 203 return result; 204 } 205 206 /** 207 * Parses the string argument as a signed integer in the radix specified by 208 * the second argument. The characters in the string must all be digits of 209 * the specified radix. Handles negative values, which method 210 * java.lang.Integer.parseInt(String, int) does not. 211 * @param text the String containing the integer representation to be 212 * parsed. Precondition: text contains a parsable int. 213 * @param radix the radix to be used while parsing text. 214 * @return the integer represented by the string argument in the specified radix. 215 */ 216 private static int parseInt(String text, int radix) { 217 int result = 0; 218 final int max = text.length(); 219 for (int i = 0; i < max; i++) { 220 final int digit = Character.digit(text.charAt(i), radix); 221 result *= radix; 222 result += digit; 223 } 224 return result; 225 } 226 227 /** 228 * Parses the string argument as a signed long in the radix specified by 229 * the second argument. The characters in the string must all be digits of 230 * the specified radix. Handles negative values, which method 231 * java.lang.Integer.parseInt(String, int) does not. 232 * @param text the String containing the integer representation to be 233 * parsed. Precondition: text contains a parsable int. 234 * @param radix the radix to be used while parsing text. 235 * @return the long represented by the string argument in the specified radix. 236 */ 237 private static long parseLong(String text, int radix) { 238 long result = 0; 239 final int max = text.length(); 240 for (int i = 0; i < max; i++) { 241 final int digit = Character.digit(text.charAt(i), radix); 242 result *= radix; 243 result += digit; 244 } 245 return result; 246 } 247 248 /** 249 * Finds sub-node for given node minimal (line, column) pair. 250 * @param node the root of tree for search. 251 * @return sub-node with minimal (line, column) pair. 252 */ 253 public static DetailAST getFirstNode(final DetailAST node) { 254 DetailAST currentNode = node; 255 DetailAST child = node.getFirstChild(); 256 while (child != null) { 257 final DetailAST newNode = getFirstNode(child); 258 if (newNode.getLineNo() < currentNode.getLineNo() 259 || newNode.getLineNo() == currentNode.getLineNo() 260 && newNode.getColumnNo() < currentNode.getColumnNo()) { 261 currentNode = newNode; 262 } 263 child = child.getNextSibling(); 264 } 265 266 return currentNode; 267 } 268 269 /** 270 * Retrieves the names of the type parameters to the node. 271 * @param node the parameterized AST node 272 * @return a list of type parameter names 273 */ 274 public static List<String> getTypeParameterNames(final DetailAST node) { 275 final DetailAST typeParameters = 276 node.findFirstToken(TokenTypes.TYPE_PARAMETERS); 277 278 final List<String> typeParameterNames = new ArrayList<>(); 279 if (typeParameters != null) { 280 final DetailAST typeParam = 281 typeParameters.findFirstToken(TokenTypes.TYPE_PARAMETER); 282 typeParameterNames.add( 283 typeParam.findFirstToken(TokenTypes.IDENT).getText()); 284 285 DetailAST sibling = typeParam.getNextSibling(); 286 while (sibling != null) { 287 if (sibling.getType() == TokenTypes.TYPE_PARAMETER) { 288 typeParameterNames.add( 289 sibling.findFirstToken(TokenTypes.IDENT).getText()); 290 } 291 sibling = sibling.getNextSibling(); 292 } 293 } 294 295 return typeParameterNames; 296 } 297 298 /** 299 * Retrieves the type parameters to the node. 300 * @param node the parameterized AST node 301 * @return a list of type parameter names 302 */ 303 public static List<DetailAST> getTypeParameters(final DetailAST node) { 304 final DetailAST typeParameters = 305 node.findFirstToken(TokenTypes.TYPE_PARAMETERS); 306 307 final List<DetailAST> typeParams = new ArrayList<>(); 308 if (typeParameters != null) { 309 final DetailAST typeParam = 310 typeParameters.findFirstToken(TokenTypes.TYPE_PARAMETER); 311 typeParams.add(typeParam); 312 313 DetailAST sibling = typeParam.getNextSibling(); 314 while (sibling != null) { 315 if (sibling.getType() == TokenTypes.TYPE_PARAMETER) { 316 typeParams.add(sibling); 317 } 318 sibling = sibling.getNextSibling(); 319 } 320 } 321 322 return typeParams; 323 } 324 325 /** 326 * Returns whether an AST represents a setter method. 327 * @param ast the AST to check with 328 * @return whether the AST represents a setter method 329 */ 330 public static boolean isSetterMethod(final DetailAST ast) { 331 boolean setterMethod = false; 332 333 // Check have a method with exactly 7 children which are all that 334 // is allowed in a proper setter method which does not throw any 335 // exceptions. 336 if (ast.getType() == TokenTypes.METHOD_DEF 337 && ast.getChildCount() == SETTER_GETTER_MAX_CHILDREN) { 338 339 final DetailAST type = ast.findFirstToken(TokenTypes.TYPE); 340 final String name = type.getNextSibling().getText(); 341 final boolean matchesSetterFormat = SETTER_PATTERN.matcher(name).matches(); 342 final boolean voidReturnType = type.getChildCount(TokenTypes.LITERAL_VOID) > 0; 343 344 final DetailAST params = ast.findFirstToken(TokenTypes.PARAMETERS); 345 final boolean singleParam = params.getChildCount(TokenTypes.PARAMETER_DEF) == 1; 346 347 if (matchesSetterFormat && voidReturnType && singleParam) { 348 // Now verify that the body consists of: 349 // SLIST -> EXPR -> ASSIGN 350 // SEMI 351 // RCURLY 352 final DetailAST slist = ast.findFirstToken(TokenTypes.SLIST); 353 354 if (slist != null && slist.getChildCount() == SETTER_BODY_SIZE) { 355 final DetailAST expr = slist.getFirstChild(); 356 setterMethod = expr.getFirstChild().getType() == TokenTypes.ASSIGN; 357 } 358 } 359 } 360 return setterMethod; 361 } 362 363 /** 364 * Returns whether an AST represents a getter method. 365 * @param ast the AST to check with 366 * @return whether the AST represents a getter method 367 */ 368 public static boolean isGetterMethod(final DetailAST ast) { 369 boolean getterMethod = false; 370 371 // Check have a method with exactly 7 children which are all that 372 // is allowed in a proper getter method which does not throw any 373 // exceptions. 374 if (ast.getType() == TokenTypes.METHOD_DEF 375 && ast.getChildCount() == SETTER_GETTER_MAX_CHILDREN) { 376 377 final DetailAST type = ast.findFirstToken(TokenTypes.TYPE); 378 final String name = type.getNextSibling().getText(); 379 final boolean matchesGetterFormat = GETTER_PATTERN.matcher(name).matches(); 380 final boolean noVoidReturnType = type.getChildCount(TokenTypes.LITERAL_VOID) == 0; 381 382 final DetailAST params = ast.findFirstToken(TokenTypes.PARAMETERS); 383 final boolean noParams = params.getChildCount(TokenTypes.PARAMETER_DEF) == 0; 384 385 if (matchesGetterFormat && noVoidReturnType && noParams) { 386 // Now verify that the body consists of: 387 // SLIST -> RETURN 388 // RCURLY 389 final DetailAST slist = ast.findFirstToken(TokenTypes.SLIST); 390 391 if (slist != null && slist.getChildCount() == GETTER_BODY_SIZE) { 392 final DetailAST expr = slist.getFirstChild(); 393 getterMethod = expr.getType() == TokenTypes.LITERAL_RETURN; 394 } 395 } 396 } 397 return getterMethod; 398 } 399 400 /** 401 * Checks whether a method is a not void one. 402 * 403 * @param methodDefAst the method node. 404 * @return true if method is a not void one. 405 */ 406 public static boolean isNonVoidMethod(DetailAST methodDefAst) { 407 boolean returnValue = false; 408 if (methodDefAst.getType() == TokenTypes.METHOD_DEF) { 409 final DetailAST typeAST = methodDefAst.findFirstToken(TokenTypes.TYPE); 410 if (typeAST.findFirstToken(TokenTypes.LITERAL_VOID) == null) { 411 returnValue = true; 412 } 413 } 414 return returnValue; 415 } 416 417 /** 418 * Checks whether a parameter is a receiver. 419 * 420 * @param parameterDefAst the parameter node. 421 * @return true if the parameter is a receiver. 422 */ 423 public static boolean isReceiverParameter(DetailAST parameterDefAst) { 424 boolean returnValue = false; 425 if (parameterDefAst.getType() == TokenTypes.PARAMETER_DEF 426 && parameterDefAst.findFirstToken(TokenTypes.IDENT) == null) { 427 returnValue = parameterDefAst.branchContains(TokenTypes.LITERAL_THIS); 428 } 429 return returnValue; 430 } 431 432 /** 433 * Returns {@link AccessModifier} based on the information about access modifier 434 * taken from the given token of type {@link TokenTypes#MODIFIERS}. 435 * @param modifiersToken token of type {@link TokenTypes#MODIFIERS}. 436 * @return {@link AccessModifier}. 437 */ 438 public static AccessModifier getAccessModifierFromModifiersToken(DetailAST modifiersToken) { 439 if (modifiersToken == null || modifiersToken.getType() != TokenTypes.MODIFIERS) { 440 throw new IllegalArgumentException("expected non-null AST-token with type 'MODIFIERS'"); 441 } 442 443 // default access modifier 444 AccessModifier accessModifier = AccessModifier.PACKAGE; 445 for (AST token = modifiersToken.getFirstChild(); token != null; 446 token = token.getNextSibling()) { 447 448 final int tokenType = token.getType(); 449 if (tokenType == TokenTypes.LITERAL_PUBLIC) { 450 accessModifier = AccessModifier.PUBLIC; 451 } 452 else if (tokenType == TokenTypes.LITERAL_PROTECTED) { 453 accessModifier = AccessModifier.PROTECTED; 454 } 455 else if (tokenType == TokenTypes.LITERAL_PRIVATE) { 456 accessModifier = AccessModifier.PRIVATE; 457 } 458 } 459 return accessModifier; 460 } 461}