001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2015 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.List; 023import java.util.regex.Pattern; 024 025import com.google.common.collect.Lists; 026import com.puppycrawl.tools.checkstyle.api.DetailAST; 027import com.puppycrawl.tools.checkstyle.api.FullIdent; 028import com.puppycrawl.tools.checkstyle.api.TokenTypes; 029 030/** 031 * Contains utility methods for the checks. 032 * 033 * @author Oliver Burn 034 * @author <a href="mailto:simon@redhillconsulting.com.au">Simon Harris</a> 035 * @author o_sukhodolsky 036 */ 037public final class CheckUtils { 038 // constants for parseDouble() 039 /** Octal radix. */ 040 private static final int BASE_8 = 8; 041 042 /** Decimal radix. */ 043 private static final int BASE_10 = 10; 044 045 /** Hex radix. */ 046 private static final int BASE_16 = 16; 047 048 /** Maximum children allowed in setter/getter. */ 049 private static final int SETTER_GETTER_MAX_CHILDREN = 7; 050 051 /** Maximum nodes allowed in a body of setter. */ 052 private static final int SETTER_BODY_SIZE = 3; 053 054 /** Maximum nodes allowed in a body of getter. */ 055 private static final int GETTER_BODY_SIZE = 2; 056 057 /** Pattern matching underscore characters ('_'). */ 058 private static final Pattern UNDERSCORE_PATTERN = Pattern.compile("_"); 059 060 /** Pattern matching names of setter methods. */ 061 private static final Pattern SETTER_PATTERN = Pattern.compile("^set[A-Z].*"); 062 063 /** Pattern matching names of getter methods. */ 064 private static final Pattern GETTER_PATTERN = Pattern.compile("^(is|get)[A-Z].*"); 065 066 /** Prevent instances. */ 067 private CheckUtils() { 068 } 069 070 /** 071 * Tests whether a method definition AST defines an equals covariant. 072 * @param ast the method definition AST to test. 073 * Precondition: ast is a TokenTypes.METHOD_DEF node. 074 * @return true if ast defines an equals covariant. 075 */ 076 public static boolean isEqualsMethod(DetailAST ast) { 077 boolean equalsMethod = false; 078 079 if (ast.getType() == TokenTypes.METHOD_DEF) { 080 final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS); 081 final boolean staticOrAbstract = modifiers.branchContains(TokenTypes.LITERAL_STATIC) 082 || modifiers.branchContains(TokenTypes.ABSTRACT); 083 084 if (!staticOrAbstract) { 085 final DetailAST nameNode = ast.findFirstToken(TokenTypes.IDENT); 086 final String name = nameNode.getText(); 087 088 if ("equals".equals(name)) { 089 // one parameter? 090 final DetailAST paramsNode = ast.findFirstToken(TokenTypes.PARAMETERS); 091 equalsMethod = paramsNode.getChildCount() == 1; 092 } 093 } 094 } 095 return equalsMethod; 096 } 097 098 /** 099 * Returns whether a token represents an ELSE as part of an ELSE / IF set. 100 * @param ast the token to check 101 * @return whether it is 102 */ 103 public static boolean isElseIf(DetailAST ast) { 104 final DetailAST parentAST = ast.getParent(); 105 106 return ast.getType() == TokenTypes.LITERAL_IF 107 && (isElse(parentAST) || isElseWithCurlyBraces(parentAST)); 108 } 109 110 /** 111 * Returns whether a token represents an ELSE. 112 * @param ast the token to check 113 * @return whether the token represents an ELSE 114 */ 115 private static boolean isElse(DetailAST ast) { 116 return ast.getType() == TokenTypes.LITERAL_ELSE; 117 } 118 119 /** 120 * Returns whether a token represents an SLIST as part of an ELSE 121 * statement. 122 * @param ast the token to check 123 * @return whether the toke does represent an SLIST as part of an ELSE 124 */ 125 private static boolean isElseWithCurlyBraces(DetailAST ast) { 126 return ast.getType() == TokenTypes.SLIST 127 && ast.getChildCount() == 2 128 && isElse(ast.getParent()); 129 } 130 131 /** 132 * Creates {@code FullIdent} for given type node. 133 * @param typeAST a type node. 134 * @return {@code FullIdent} for given type. 135 */ 136 public static FullIdent createFullType(DetailAST typeAST) { 137 final DetailAST arrayDeclaratorAST = 138 typeAST.findFirstToken(TokenTypes.ARRAY_DECLARATOR); 139 final FullIdent fullType; 140 141 if (arrayDeclaratorAST == null) { 142 fullType = createFullTypeNoArrays(typeAST); 143 } 144 else { 145 fullType = createFullTypeNoArrays(arrayDeclaratorAST); 146 } 147 return fullType; 148 } 149 150 /** 151 * @param typeAST a type node (no array) 152 * @return {@code FullIdent} for given type. 153 */ 154 private static FullIdent createFullTypeNoArrays(DetailAST typeAST) { 155 return FullIdent.createFullIdent(typeAST.getFirstChild()); 156 } 157 158 /** 159 * Returns the value represented by the specified string of the specified 160 * type. Returns 0 for types other than float, double, int, and long. 161 * @param text the string to be parsed. 162 * @param type the token type of the text. Should be a constant of 163 * {@link TokenTypes}. 164 * @return the double value represented by the string argument. 165 */ 166 public static double parseDouble(String text, int type) { 167 String txt = UNDERSCORE_PATTERN.matcher(text).replaceAll(""); 168 double result = 0; 169 switch (type) { 170 case TokenTypes.NUM_FLOAT: 171 case TokenTypes.NUM_DOUBLE: 172 result = Double.parseDouble(txt); 173 break; 174 case TokenTypes.NUM_INT: 175 case TokenTypes.NUM_LONG: 176 int radix = BASE_10; 177 if (txt.startsWith("0x") || txt.startsWith("0X")) { 178 radix = BASE_16; 179 txt = txt.substring(2); 180 } 181 else if (txt.charAt(0) == '0') { 182 radix = BASE_8; 183 txt = txt.substring(1); 184 } 185 if (CommonUtils.endsWithChar(txt, 'L') || CommonUtils.endsWithChar(txt, 'l')) { 186 txt = txt.substring(0, txt.length() - 1); 187 } 188 if (!txt.isEmpty()) { 189 if (type == TokenTypes.NUM_INT) { 190 result = parseInt(txt, radix); 191 } 192 else { 193 result = parseLong(txt, radix); 194 } 195 } 196 break; 197 default: 198 break; 199 } 200 return result; 201 } 202 203 /** 204 * Parses the string argument as a signed integer in the radix specified by 205 * the second argument. The characters in the string must all be digits of 206 * the specified radix. Handles negative values, which method 207 * java.lang.Integer.parseInt(String, int) does not. 208 * @param text the String containing the integer representation to be 209 * parsed. Precondition: text contains a parsable int. 210 * @param radix the radix to be used while parsing text. 211 * @return the integer represented by the string argument in the specified radix. 212 */ 213 private static int parseInt(String text, int radix) { 214 int result = 0; 215 final int max = text.length(); 216 for (int i = 0; i < max; i++) { 217 final int digit = Character.digit(text.charAt(i), radix); 218 result *= radix; 219 result += digit; 220 } 221 return result; 222 } 223 224 /** 225 * Parses the string argument as a signed long in the radix specified by 226 * the second argument. The characters in the string must all be digits of 227 * the specified radix. Handles negative values, which method 228 * java.lang.Integer.parseInt(String, int) does not. 229 * @param text the String containing the integer representation to be 230 * parsed. Precondition: text contains a parsable int. 231 * @param radix the radix to be used while parsing text. 232 * @return the long represented by the string argument in the specified radix. 233 */ 234 private static long parseLong(String text, int radix) { 235 long result = 0; 236 final int max = text.length(); 237 for (int i = 0; i < max; i++) { 238 final int digit = Character.digit(text.charAt(i), radix); 239 result *= radix; 240 result += digit; 241 } 242 return result; 243 } 244 245 /** 246 * Finds sub-node for given node minimal (line, column) pair. 247 * @param node the root of tree for search. 248 * @return sub-node with minimal (line, column) pair. 249 */ 250 public static DetailAST getFirstNode(final DetailAST node) { 251 DetailAST currentNode = node; 252 DetailAST child = node.getFirstChild(); 253 while (child != null) { 254 final DetailAST newNode = getFirstNode(child); 255 if (newNode.getLineNo() < currentNode.getLineNo() 256 || newNode.getLineNo() == currentNode.getLineNo() 257 && newNode.getColumnNo() < currentNode.getColumnNo()) { 258 currentNode = newNode; 259 } 260 child = child.getNextSibling(); 261 } 262 263 return currentNode; 264 } 265 266 /** 267 * Retrieves the names of the type parameters to the node. 268 * @param node the parameterized AST node 269 * @return a list of type parameter names 270 */ 271 public static List<String> getTypeParameterNames(final DetailAST node) { 272 final DetailAST typeParameters = 273 node.findFirstToken(TokenTypes.TYPE_PARAMETERS); 274 275 final List<String> typeParameterNames = Lists.newArrayList(); 276 if (typeParameters != null) { 277 final DetailAST typeParam = 278 typeParameters.findFirstToken(TokenTypes.TYPE_PARAMETER); 279 typeParameterNames.add( 280 typeParam.findFirstToken(TokenTypes.IDENT).getText()); 281 282 DetailAST sibling = typeParam.getNextSibling(); 283 while (sibling != null) { 284 if (sibling.getType() == TokenTypes.TYPE_PARAMETER) { 285 typeParameterNames.add( 286 sibling.findFirstToken(TokenTypes.IDENT).getText()); 287 } 288 sibling = sibling.getNextSibling(); 289 } 290 } 291 292 return typeParameterNames; 293 } 294 295 /** 296 * Retrieves the type parameters to the node. 297 * @param node the parameterized AST node 298 * @return a list of type parameter names 299 */ 300 public static List<DetailAST> getTypeParameters(final DetailAST node) { 301 final DetailAST typeParameters = 302 node.findFirstToken(TokenTypes.TYPE_PARAMETERS); 303 304 final List<DetailAST> typeParams = Lists.newArrayList(); 305 if (typeParameters != null) { 306 final DetailAST typeParam = 307 typeParameters.findFirstToken(TokenTypes.TYPE_PARAMETER); 308 typeParams.add(typeParam); 309 310 DetailAST sibling = typeParam.getNextSibling(); 311 while (sibling != null) { 312 if (sibling.getType() == TokenTypes.TYPE_PARAMETER) { 313 typeParams.add(sibling); 314 } 315 sibling = sibling.getNextSibling(); 316 } 317 } 318 319 return typeParams; 320 } 321 322 /** 323 * Returns whether an AST represents a setter method. 324 * @param ast the AST to check with 325 * @return whether the AST represents a setter method 326 */ 327 public static boolean isSetterMethod(final DetailAST ast) { 328 boolean setterMethod = false; 329 330 // Check have a method with exactly 7 children which are all that 331 // is allowed in a proper setter method which does not throw any 332 // exceptions. 333 if (ast.getType() == TokenTypes.METHOD_DEF 334 && ast.getChildCount() == SETTER_GETTER_MAX_CHILDREN) { 335 336 final DetailAST type = ast.findFirstToken(TokenTypes.TYPE); 337 final String name = type.getNextSibling().getText(); 338 final boolean matchesSetterFormat = SETTER_PATTERN.matcher(name).matches(); 339 final boolean voidReturnType = type.getChildCount(TokenTypes.LITERAL_VOID) > 0; 340 341 final DetailAST params = ast.findFirstToken(TokenTypes.PARAMETERS); 342 final boolean singleParam = params.getChildCount(TokenTypes.PARAMETER_DEF) == 1; 343 344 if (matchesSetterFormat && voidReturnType && singleParam) { 345 // Now verify that the body consists of: 346 // SLIST -> EXPR -> ASSIGN 347 // SEMI 348 // RCURLY 349 final DetailAST slist = ast.findFirstToken(TokenTypes.SLIST); 350 351 if (slist != null && slist.getChildCount() == SETTER_BODY_SIZE) { 352 final DetailAST expr = slist.getFirstChild(); 353 setterMethod = expr.getFirstChild().getType() == TokenTypes.ASSIGN; 354 } 355 } 356 } 357 return setterMethod; 358 } 359 360 /** 361 * Returns whether an AST represents a getter method. 362 * @param ast the AST to check with 363 * @return whether the AST represents a getter method 364 */ 365 public static boolean isGetterMethod(final DetailAST ast) { 366 boolean getterMethod = false; 367 368 // Check have a method with exactly 7 children which are all that 369 // is allowed in a proper getter method which does not throw any 370 // exceptions. 371 if (ast.getType() == TokenTypes.METHOD_DEF 372 && ast.getChildCount() == SETTER_GETTER_MAX_CHILDREN) { 373 374 final DetailAST type = ast.findFirstToken(TokenTypes.TYPE); 375 final String name = type.getNextSibling().getText(); 376 final boolean matchesGetterFormat = GETTER_PATTERN.matcher(name).matches(); 377 final boolean noVoidReturnType = type.getChildCount(TokenTypes.LITERAL_VOID) == 0; 378 379 final DetailAST params = ast.findFirstToken(TokenTypes.PARAMETERS); 380 final boolean noParams = params.getChildCount(TokenTypes.PARAMETER_DEF) == 0; 381 382 if (matchesGetterFormat && noVoidReturnType && noParams) { 383 // Now verify that the body consists of: 384 // SLIST -> RETURN 385 // RCURLY 386 final DetailAST slist = ast.findFirstToken(TokenTypes.SLIST); 387 388 if (slist != null && slist.getChildCount() == GETTER_BODY_SIZE) { 389 final DetailAST expr = slist.getFirstChild(); 390 getterMethod = expr.getType() == TokenTypes.LITERAL_RETURN; 391 } 392 } 393 } 394 return getterMethod; 395 } 396 397 /** 398 * Checks whether a method is a not void one. 399 * 400 * @param methodDefAst the method node. 401 * @return true if method is a not void one. 402 */ 403 public static boolean isNonVoidMethod(DetailAST methodDefAst) { 404 boolean returnValue = false; 405 if (methodDefAst.getType() == TokenTypes.METHOD_DEF) { 406 final DetailAST typeAST = methodDefAst.findFirstToken(TokenTypes.TYPE); 407 if (typeAST.findFirstToken(TokenTypes.LITERAL_VOID) == null) { 408 returnValue = true; 409 } 410 } 411 return returnValue; 412 } 413}