001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2014 Oliver Burn 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//////////////////////////////////////////////////////////////////////////////// 019package com.puppycrawl.tools.checkstyle.checks.coding; 020 021import com.google.common.base.Objects; 022import com.google.common.collect.Sets; 023import com.puppycrawl.tools.checkstyle.api.Check; 024import com.puppycrawl.tools.checkstyle.api.DetailAST; 025import com.puppycrawl.tools.checkstyle.api.ScopeUtils; 026import com.puppycrawl.tools.checkstyle.api.TokenTypes; 027import com.puppycrawl.tools.checkstyle.api.Utils; 028 029import java.util.Set; 030import java.util.regex.Pattern; 031import java.util.regex.PatternSyntaxException; 032import org.apache.commons.beanutils.ConversionException; 033 034/** 035 * <p>Checks that a local variable or a parameter does not shadow 036 * a field that is defined in the same class. 037 * <p> 038 * An example of how to configure the check is: 039 * <pre> 040 * <module name="HiddenField"/> 041 * </pre> 042 * <p> 043 * An example of how to configure the check so that it checks variables but not 044 * parameters is: 045 * <pre> 046 * <module name="HiddenField"> 047 * <property name="tokens" value="VARIABLE_DEF"/> 048 * </module> 049 * </pre> 050 * <p> 051 * An example of how to configure the check so that it ignores the parameter of 052 * a setter method is: 053 * <pre> 054 * <module name="HiddenField"> 055 * <property name="ignoreSetter" value="true"/> 056 * </module> 057 * </pre> 058 * <p> 059 * A method is recognized as a setter if it is in the following form 060 * <pre> 061 * ${returnType} set${Name}(${anyType} ${name}) { ... } 062 * </pre> 063 * where ${anyType} is any primitive type, class or interface name; 064 * ${name} is name of the variable that is being set and ${Name} its 065 * capitalized form that appears in the method name. By default it is expected 066 * that setter returns void, i.e. ${returnType} is 'void'. For example 067 * <pre> 068 * void setTime(long time) { ... } 069 * </pre> 070 * Any other return types will not let method match a setter pattern. However, 071 * by setting <em>setterCanReturnItsClass</em> property to <em>true</em> 072 * definition of a setter is expanded, so that setter return type can also be 073 * a class in which setter is declared. For example 074 * <pre> 075 * class PageBuilder { 076 * PageBuilder setName(String name) { ... } 077 * } 078 * </pre> 079 * Such methods are known as chain-setters and a common when Builder-pattern 080 * is used. Property <em>setterCanReturnItsClass</em> has effect only if 081 * <em>ignoreSetter</em> is set to true. 082 * <p> 083 * An example of how to configure the check so that it ignores the parameter 084 * of either a setter that returns void or a chain-setter. 085 * <pre> 086 * <module name="HiddenField"> 087 * <property name="ignoreSetter" value="true"/> 088 * <property name="setterCanReturnItsClass" value="true"/> 089 * </module> 090 * </pre> 091 * <p> 092 * An example of how to configure the check so that it ignores constructor 093 * parameters is: 094 * <pre> 095 * <module name="HiddenField"> 096 * <property name="ignoreConstructorParameter" value="true"/> 097 * </module> 098 * </pre> 099 * @author Dmitri Priimak 100 */ 101public class HiddenFieldCheck 102 extends Check 103{ 104 /** stack of sets of field names, 105 * one for each class of a set of nested classes. 106 */ 107 private FieldFrame currentFrame; 108 109 /** the regexp to match against */ 110 private Pattern regexp; 111 112 /** controls whether to check the pnameter of a property setter method */ 113 private boolean ignoreSetter; 114 115 /** 116 * if ignoreSetter is set to true then this variable controls what 117 * the setter method can return By default setter must return void. 118 * However, is this variable is set to true then setter can also 119 * return class in which is declared. 120 */ 121 private boolean setterCanReturnItsClass; 122 123 /** controls whether to check the parameter of a constructor */ 124 private boolean ignoreConstructorParameter; 125 126 /** controls whether to check the parameter of abstract methods. */ 127 private boolean ignoreAbstractMethods; 128 129 @Override 130 public int[] getDefaultTokens() 131 { 132 return new int[] { 133 TokenTypes.VARIABLE_DEF, 134 TokenTypes.PARAMETER_DEF, 135 TokenTypes.CLASS_DEF, 136 TokenTypes.ENUM_DEF, 137 TokenTypes.ENUM_CONSTANT_DEF, 138 }; 139 } 140 141 @Override 142 public int[] getAcceptableTokens() 143 { 144 return new int[] { 145 TokenTypes.VARIABLE_DEF, 146 TokenTypes.PARAMETER_DEF, 147 }; 148 } 149 150 @Override 151 public int[] getRequiredTokens() 152 { 153 return new int[] { 154 TokenTypes.CLASS_DEF, 155 TokenTypes.ENUM_DEF, 156 TokenTypes.ENUM_CONSTANT_DEF, 157 }; 158 } 159 160 @Override 161 public void beginTree(DetailAST rootAST) 162 { 163 currentFrame = new FieldFrame(null, true, null, null); 164 } 165 166 @Override 167 public void visitToken(DetailAST ast) 168 { 169 final int type = ast.getType(); 170 switch (type) { 171 case TokenTypes.VARIABLE_DEF: 172 case TokenTypes.PARAMETER_DEF: 173 processVariable(ast); 174 break; 175 176 default: 177 visitOtherTokens(ast, type); 178 } 179 } 180 181 /** 182 * Called to process tokens other than {@link TokenTypes.VARIABLE_DEF} 183 * and {@link TokenTypes.PARAMETER_DEF} 184 * 185 * @param ast token to process 186 * @param type type of the token 187 */ 188 private void visitOtherTokens(DetailAST ast, int type) 189 { 190 //A more thorough check of enum constant class bodies is 191 //possible (checking for hidden fields against the enum 192 //class body in addition to enum constant class bodies) 193 //but not attempted as it seems out of the scope of this 194 //check. 195 final DetailAST typeMods = ast.findFirstToken(TokenTypes.MODIFIERS); 196 final boolean isStaticInnerType = 197 (typeMods != null) 198 && typeMods.branchContains(TokenTypes.LITERAL_STATIC); 199 200 final FieldFrame frame = 201 new FieldFrame(currentFrame, isStaticInnerType, type, 202 (type == TokenTypes.CLASS_DEF || type == TokenTypes.ENUM_DEF) 203 ? ast.findFirstToken(TokenTypes.IDENT).getText() 204 : null 205 ); 206 207 //add fields to container 208 final DetailAST objBlock = ast.findFirstToken(TokenTypes.OBJBLOCK); 209 // enum constants may not have bodies 210 if (objBlock != null) { 211 DetailAST child = objBlock.getFirstChild(); 212 while (child != null) { 213 if (child.getType() == TokenTypes.VARIABLE_DEF) { 214 final String name = 215 child.findFirstToken(TokenTypes.IDENT).getText(); 216 final DetailAST mods = 217 child.findFirstToken(TokenTypes.MODIFIERS); 218 if (mods.branchContains(TokenTypes.LITERAL_STATIC)) { 219 frame.addStaticField(name); 220 } 221 else { 222 frame.addInstanceField(name); 223 } 224 } 225 child = child.getNextSibling(); 226 } 227 } 228 // push container 229 currentFrame = frame; 230 } 231 232 @Override 233 public void leaveToken(DetailAST ast) 234 { 235 if ((ast.getType() == TokenTypes.CLASS_DEF) 236 || (ast.getType() == TokenTypes.ENUM_DEF) 237 || (ast.getType() == TokenTypes.ENUM_CONSTANT_DEF)) 238 { 239 //pop 240 currentFrame = currentFrame.getParent(); 241 } 242 } 243 244 /** 245 * Process a variable token. 246 * Check whether a local variable or parameter shadows a field. 247 * Store a field for later comparison with local variables and parameters. 248 * @param ast the variable token. 249 */ 250 private void processVariable(DetailAST ast) 251 { 252 if (!ScopeUtils.inInterfaceOrAnnotationBlock(ast) 253 && (ScopeUtils.isLocalVariableDef(ast) 254 || (ast.getType() == TokenTypes.PARAMETER_DEF))) 255 { 256 // local variable or parameter. Does it shadow a field? 257 final DetailAST nameAST = ast.findFirstToken(TokenTypes.IDENT); 258 final String name = nameAST.getText(); 259 260 if ((currentFrame.containsStaticField(name) 261 || (!inStatic(ast) && currentFrame.containsInstanceField(name))) 262 && ((regexp == null) || (!getRegexp().matcher(name).find())) 263 && !isIgnoredSetterParam(ast, name) 264 && !isIgnoredConstructorParam(ast) 265 && !isIgnoredParamOfAbstractMethod(ast)) 266 { 267 log(nameAST, "hidden.field", name); 268 } 269 } 270 } 271 272 /** 273 * Determines whether an AST node is in a static method or static 274 * initializer. 275 * @param ast the node to check. 276 * @return true if ast is in a static method or a static block; 277 */ 278 private static boolean inStatic(DetailAST ast) 279 { 280 DetailAST parent = ast.getParent(); 281 while (parent != null) { 282 switch (parent.getType()) { 283 case TokenTypes.STATIC_INIT: 284 return true; 285 case TokenTypes.METHOD_DEF: 286 final DetailAST mods = 287 parent.findFirstToken(TokenTypes.MODIFIERS); 288 return mods.branchContains(TokenTypes.LITERAL_STATIC); 289 default: 290 parent = parent.getParent(); 291 } 292 } 293 return false; 294 } 295 296 /** 297 * Decides whether to ignore an AST node that is the parameter of a 298 * setter method, where the property setter method for field 'xyz' has 299 * name 'setXyz', one parameter named 'xyz', and return type void 300 * (default behavior) or return type is name of the class in which 301 * such method is declared (allowed only if 302 * {@link #setSetterCanReturnItsClass(boolean)} is called with 303 * value <em>true</em>) 304 * 305 * @param ast the AST to check. 306 * @param name the name of ast. 307 * @return true if ast should be ignored because check property 308 * ignoreSetter is true and ast is the parameter of a setter method. 309 */ 310 private boolean isIgnoredSetterParam(DetailAST ast, String name) 311 { 312 if (ast.getType() == TokenTypes.PARAMETER_DEF && ignoreSetter) { 313 final DetailAST parametersAST = ast.getParent(); 314 final DetailAST methodAST = parametersAST.getParent(); 315 if (parametersAST.getChildCount() == 1 316 && methodAST.getType() == TokenTypes.METHOD_DEF 317 && isSetterMethod(methodAST, name)) 318 { 319 return true; 320 } 321 } 322 return false; 323 } 324 325 /** 326 * Determine if a specific method identified by methodAST and a single 327 * variable name aName is a setter. This recognition partially depends 328 * on mSetterCanReturnItsClass property. 329 * 330 * @param aMethodAST AST corresponding to a method call 331 * @param aName name of single parameter of this method. 332 * @return true of false indicating of method is a setter or not. 333 */ 334 private boolean isSetterMethod(DetailAST aMethodAST, String aName) 335 { 336 final String methodName = 337 aMethodAST.findFirstToken(TokenTypes.IDENT).getText(); 338 boolean isSetterMethod = false; 339 340 if (methodName.equals("set" + capitalize(aName))) { 341 // method name did match set${Name}(${anyType} ${aName}) 342 // where ${Name} is capitalized version of ${aName} 343 // therefore this method is potentially a setter 344 final DetailAST typeAST = aMethodAST.findFirstToken(TokenTypes.TYPE); 345 final String returnType = typeAST.getFirstChild().getText(); 346 if (typeAST.branchContains(TokenTypes.LITERAL_VOID) 347 || (setterCanReturnItsClass && currentFrame.embeddedIn(returnType))) 348 { 349 // this method has signature 350 // 351 // void set${Name}(${anyType} ${name}) 352 // 353 // and therefore considered to be a setter 354 // 355 // or 356 // 357 // return type is not void, but it is the same as the class 358 // where method is declared and and mSetterCanReturnItsClass 359 // is set to true 360 isSetterMethod = true; 361 } 362 } 363 364 return isSetterMethod; 365 } 366 367 /** 368 * Capitalizes a given property name the way we expect to see it in 369 * a setter name. 370 * @param name a property name 371 * @return capitalized property name 372 */ 373 private static String capitalize(final String name) 374 { 375 String setterName = name; 376 // we should not capitalize the first character if the second 377 // one is a capital one, since according to JavBeans spec 378 // setXYzz() is a setter for XYzz property, not for xYzz one. 379 if (name != null && name.length() > 0 380 && (name.length() > 1 && !Character.isUpperCase(name.charAt(1)))) 381 { 382 setterName = name.substring(0, 1).toUpperCase() + name.substring(1); 383 } 384 return setterName; 385 } 386 387 /** 388 * Decides whether to ignore an AST node that is the parameter of a 389 * constructor. 390 * @param ast the AST to check. 391 * @return true if ast should be ignored because check property 392 * ignoreConstructorParameter is true and ast is a constructor parameter. 393 */ 394 private boolean isIgnoredConstructorParam(DetailAST ast) 395 { 396 boolean result = false; 397 if ((ast.getType() == TokenTypes.PARAMETER_DEF) 398 && ignoreConstructorParameter) 399 { 400 final DetailAST parametersAST = ast.getParent(); 401 final DetailAST constructorAST = parametersAST.getParent(); 402 result = (constructorAST.getType() == TokenTypes.CTOR_DEF); 403 } 404 return result; 405 } 406 407 /** 408 * Decides whether to ignore an AST node that is the parameter of an 409 * abstract method. 410 * @param ast the AST to check. 411 * @return true if ast should be ignored because check property 412 * ignoreAbstactMethods is true and ast is a parameter of abstract 413 * methods. 414 */ 415 private boolean isIgnoredParamOfAbstractMethod(DetailAST ast) 416 { 417 boolean result = false; 418 if ((ast.getType() == TokenTypes.PARAMETER_DEF) 419 && ignoreAbstractMethods) 420 { 421 final DetailAST method = ast.getParent().getParent(); 422 if (method.getType() == TokenTypes.METHOD_DEF) { 423 final DetailAST mods = method.findFirstToken(TokenTypes.MODIFIERS); 424 result = ((mods != null) && mods.branchContains(TokenTypes.ABSTRACT)); 425 } 426 } 427 return result; 428 } 429 430 /** 431 * Set the ignore format to the specified regular expression. 432 * @param format a <code>String</code> value 433 * @throws ConversionException unable to parse format 434 */ 435 public void setIgnoreFormat(String format) 436 throws ConversionException 437 { 438 try { 439 regexp = Utils.getPattern(format); 440 } 441 catch (final PatternSyntaxException e) { 442 throw new ConversionException("unable to parse " + format, e); 443 } 444 } 445 446 /** 447 * Set whether to ignore the parameter of a property setter method. 448 * @param ignoreSetter decide whether to ignore the parameter of 449 * a property setter method. 450 */ 451 public void setIgnoreSetter(boolean ignoreSetter) 452 { 453 this.ignoreSetter = ignoreSetter; 454 } 455 456 /** 457 * Controls if setter can return only void (default behavior) or it 458 * can also return class in which it is declared. 459 * 460 * @param aSetterCanReturnItsClass if true then setter can return 461 * either void or class in which it is declared. If false then 462 * in order to be recognized as setter method (otherwise 463 * already recognized as a setter) must return void. Later is 464 * the default behavior. 465 */ 466 public void setSetterCanReturnItsClass( 467 boolean aSetterCanReturnItsClass) 468 { 469 setterCanReturnItsClass = aSetterCanReturnItsClass; 470 } 471 472 /** 473 * Set whether to ignore constructor parameters. 474 * @param ignoreConstructorParameter decide whether to ignore 475 * constructor parameters. 476 */ 477 public void setIgnoreConstructorParameter( 478 boolean ignoreConstructorParameter) 479 { 480 this.ignoreConstructorParameter = ignoreConstructorParameter; 481 } 482 483 /** 484 * Set whether to ignore parameters of abstract methods. 485 * @param ignoreAbstractMethods decide whether to ignore 486 * parameters of abstract methods. 487 */ 488 public void setIgnoreAbstractMethods( 489 boolean ignoreAbstractMethods) 490 { 491 this.ignoreAbstractMethods = ignoreAbstractMethods; 492 } 493 494 /** @return the regexp to match against */ 495 public Pattern getRegexp() 496 { 497 return regexp; 498 } 499 500 /** 501 * Holds the names of static and instance fields of a type. 502 * @author Rick Giles 503 * Describe class FieldFrame 504 * @author Rick Giles 505 * @version Oct 26, 2003 506 */ 507 private static class FieldFrame 508 { 509 /** type of the frame, such as TokenTypes.CLASS_DEF or TokenTypes.ENUM_DEF */ 510 private final Integer frameType; 511 512 /** name of the frame, such name of the class or enum declaration */ 513 private final String frameName; 514 515 /** is this a static inner type */ 516 private final boolean staticType; 517 518 /** parent frame. */ 519 private final FieldFrame parent; 520 521 /** set of instance field names */ 522 private final Set<String> instanceFields = Sets.newHashSet(); 523 524 /** set of static field names */ 525 private final Set<String> staticFields = Sets.newHashSet(); 526 527 /** 528 * Creates new frame. 529 * @param staticType is this a static inner type (class or enum). 530 * @param parent parent frame. 531 * @param frameType frameType derived from {@link TokenTypes} 532 * @param frameName name associated with the frame, which can be a 533 * class or enum name or null if no relevan information is available. 534 */ 535 public FieldFrame(FieldFrame parent, boolean staticType, 536 Integer frameType, String frameName) 537 { 538 this.parent = parent; 539 this.staticType = staticType; 540 this.frameType = frameType; 541 this.frameName = frameName; 542 } 543 544 /** 545 * Is this frame for static inner type. 546 * @return is this field frame for static inner type. 547 */ 548 boolean isStaticType() 549 { 550 return staticType; 551 } 552 553 /** 554 * Adds an instance field to this FieldFrame. 555 * @param field the name of the instance field. 556 */ 557 public void addInstanceField(String field) 558 { 559 instanceFields.add(field); 560 } 561 562 /** 563 * Adds a static field to this FieldFrame. 564 * @param field the name of the instance field. 565 */ 566 public void addStaticField(String field) 567 { 568 staticFields.add(field); 569 } 570 571 /** 572 * Determines whether this FieldFrame contains an instance field. 573 * @param field the field to check. 574 * @return true if this FieldFrame contains instance field field. 575 */ 576 public boolean containsInstanceField(String field) 577 { 578 return instanceFields.contains(field) 579 || !isStaticType() 580 && (parent != null) 581 && parent.containsInstanceField(field); 582 583 } 584 585 /** 586 * Determines whether this FieldFrame contains a static field. 587 * @param field the field to check. 588 * @return true if this FieldFrame contains static field field. 589 */ 590 public boolean containsStaticField(String field) 591 { 592 return staticFields.contains(field) 593 || (parent != null) 594 && parent.containsStaticField(field); 595 596 } 597 598 /** 599 * Getter for parent frame. 600 * @return parent frame. 601 */ 602 public FieldFrame getParent() 603 { 604 return parent; 605 } 606 607 /** 608 * Check if current frame is embedded in class or enum with 609 * specific name. 610 * 611 * @param classOrEnumName name of class or enum that we are looking 612 * for in the chain of field frames. 613 * 614 * @return true if current frame is embedded in class or enum 615 * with name classOrNameName 616 */ 617 private boolean embeddedIn(String classOrEnumName) 618 { 619 FieldFrame currentFrame = this; 620 while (currentFrame != null) { 621 if (Objects.equal(currentFrame.frameName, classOrEnumName)) { 622 return true; 623 } 624 currentFrame = currentFrame.parent; 625 } 626 return false; 627 } 628 } 629}