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.collect.Sets; 022import com.puppycrawl.tools.checkstyle.api.Check; 023import com.puppycrawl.tools.checkstyle.api.DetailAST; 024import com.puppycrawl.tools.checkstyle.api.ScopeUtils; 025import com.puppycrawl.tools.checkstyle.api.TokenTypes; 026import com.puppycrawl.tools.checkstyle.api.Utils; 027 028import java.util.Set; 029import java.util.regex.Pattern; 030import java.util.regex.PatternSyntaxException; 031import org.apache.commons.beanutils.ConversionException; 032 033/** 034 * <p>Checks that a local variable or a parameter does not shadow 035 * a field that is defined in the same class. 036 * </p> 037 * <p> 038 * An example of how to configure the check is: 039 * </p> 040 * <pre> 041 * <module name="HiddenField"/> 042 * </pre> 043 * <p> 044 * An example of how to configure the check so that it checks variables but not 045 * parameters is: 046 * </p> 047 * <pre> 048 * <module name="HiddenField"> 049 * <property name="tokens" value="VARIABLE_DEF"/> 050 * </module> 051 * </pre> 052 * <p> 053 * An example of how to configure the check so that it ignores the parameter of 054 * a setter method is: 055 * </p> 056 * <pre> 057 * <module name="HiddenField"> 058 * <property name="ignoreSetter" value="true"/> 059 * </module> 060 * </pre> 061 * <p> 062 * An example of how to configure the check so that it ignores constructor 063 * parameters is: 064 * </p> 065 * <pre> 066 * <module name="HiddenField"> 067 * <property name="ignoreConstructorParameter" value="true"/> 068 * </module> 069 * </pre> 070 * @author Rick Giles 071 * @version 1.0 072 */ 073public class HiddenFieldCheck 074 extends Check 075{ 076 /** stack of sets of field names, 077 * one for each class of a set of nested classes. 078 */ 079 private FieldFrame mCurrentFrame; 080 081 /** the regexp to match against */ 082 private Pattern mRegexp; 083 084 /** controls whether to check the parameter of a property setter method */ 085 private boolean mIgnoreSetter; 086 087 /** controls whether to check the parameter of a constructor */ 088 private boolean mIgnoreConstructorParameter; 089 090 /** controls whether to check the parameter of abstract methods. */ 091 private boolean mIgnoreAbstractMethods; 092 093 @Override 094 public int[] getDefaultTokens() 095 { 096 return new int[] { 097 TokenTypes.VARIABLE_DEF, 098 TokenTypes.PARAMETER_DEF, 099 TokenTypes.CLASS_DEF, 100 TokenTypes.ENUM_DEF, 101 TokenTypes.ENUM_CONSTANT_DEF, 102 }; 103 } 104 105 @Override 106 public int[] getAcceptableTokens() 107 { 108 return new int[] { 109 TokenTypes.VARIABLE_DEF, 110 TokenTypes.PARAMETER_DEF, 111 }; 112 } 113 114 @Override 115 public int[] getRequiredTokens() 116 { 117 return new int[] { 118 TokenTypes.CLASS_DEF, 119 TokenTypes.ENUM_DEF, 120 TokenTypes.ENUM_CONSTANT_DEF, 121 }; 122 } 123 124 @Override 125 public void beginTree(DetailAST aRootAST) 126 { 127 mCurrentFrame = new FieldFrame(null, true); 128 } 129 130 @Override 131 public void visitToken(DetailAST aAST) 132 { 133 if ((aAST.getType() == TokenTypes.VARIABLE_DEF) 134 || (aAST.getType() == TokenTypes.PARAMETER_DEF)) 135 { 136 processVariable(aAST); 137 return; 138 } 139 140 //A more thorough check of enum constant class bodies is 141 //possible (checking for hidden fields against the enum 142 //class body in addition to enum constant class bodies) 143 //but not attempted as it seems out of the scope of this 144 //check. 145 final DetailAST typeMods = aAST.findFirstToken(TokenTypes.MODIFIERS); 146 final boolean isStaticInnerType = 147 (typeMods != null) 148 && typeMods.branchContains(TokenTypes.LITERAL_STATIC); 149 final FieldFrame frame = 150 new FieldFrame(mCurrentFrame, isStaticInnerType); 151 152 //add fields to container 153 final DetailAST objBlock = aAST.findFirstToken(TokenTypes.OBJBLOCK); 154 // enum constants may not have bodies 155 if (objBlock != null) { 156 DetailAST child = objBlock.getFirstChild(); 157 while (child != null) { 158 if (child.getType() == TokenTypes.VARIABLE_DEF) { 159 final String name = 160 child.findFirstToken(TokenTypes.IDENT).getText(); 161 final DetailAST mods = 162 child.findFirstToken(TokenTypes.MODIFIERS); 163 if (mods.branchContains(TokenTypes.LITERAL_STATIC)) { 164 frame.addStaticField(name); 165 } 166 else { 167 frame.addInstanceField(name); 168 } 169 } 170 child = child.getNextSibling(); 171 } 172 } 173 // push container 174 mCurrentFrame = frame; 175 } 176 177 @Override 178 public void leaveToken(DetailAST aAST) 179 { 180 if ((aAST.getType() == TokenTypes.CLASS_DEF) 181 || (aAST.getType() == TokenTypes.ENUM_DEF) 182 || (aAST.getType() == TokenTypes.ENUM_CONSTANT_DEF)) 183 { 184 //pop 185 mCurrentFrame = mCurrentFrame.getParent(); 186 } 187 } 188 189 /** 190 * Process a variable token. 191 * Check whether a local variable or parameter shadows a field. 192 * Store a field for later comparison with local variables and parameters. 193 * @param aAST the variable token. 194 */ 195 private void processVariable(DetailAST aAST) 196 { 197 if (ScopeUtils.inInterfaceOrAnnotationBlock(aAST) 198 || (!ScopeUtils.isLocalVariableDef(aAST) 199 && (aAST.getType() != TokenTypes.PARAMETER_DEF))) 200 { 201 // do nothing 202 return; 203 } 204 //local variable or parameter. Does it shadow a field? 205 final DetailAST nameAST = aAST.findFirstToken(TokenTypes.IDENT); 206 final String name = nameAST.getText(); 207 if ((mCurrentFrame.containsStaticField(name) 208 || (!inStatic(aAST) && mCurrentFrame.containsInstanceField(name))) 209 && ((mRegexp == null) || (!getRegexp().matcher(name).find())) 210 && !isIgnoredSetterParam(aAST, name) 211 && !isIgnoredConstructorParam(aAST) 212 && !isIgnoredParamOfAbstractMethod(aAST)) 213 { 214 log(nameAST, "hidden.field", name); 215 } 216 } 217 218 /** 219 * Determines whether an AST node is in a static method or static 220 * initializer. 221 * @param aAST the node to check. 222 * @return true if aAST is in a static method or a static block; 223 */ 224 private static boolean inStatic(DetailAST aAST) 225 { 226 DetailAST parent = aAST.getParent(); 227 while (parent != null) { 228 switch (parent.getType()) { 229 case TokenTypes.STATIC_INIT: 230 return true; 231 case TokenTypes.METHOD_DEF: 232 final DetailAST mods = 233 parent.findFirstToken(TokenTypes.MODIFIERS); 234 return mods.branchContains(TokenTypes.LITERAL_STATIC); 235 default: 236 parent = parent.getParent(); 237 } 238 } 239 return false; 240 } 241 242 /** 243 * Decides whether to ignore an AST node that is the parameter of a 244 * setter method, where the property setter method for field 'xyz' has 245 * name 'setXyz', one parameter named 'xyz', and return type void. 246 * @param aAST the AST to check. 247 * @param aName the name of aAST. 248 * @return true if aAST should be ignored because check property 249 * ignoreSetter is true and aAST is the parameter of a setter method. 250 */ 251 private boolean isIgnoredSetterParam(DetailAST aAST, String aName) 252 { 253 if (aAST.getType() != TokenTypes.PARAMETER_DEF 254 || !mIgnoreSetter) 255 { 256 return false; 257 } 258 //single parameter? 259 final DetailAST parametersAST = aAST.getParent(); 260 if (parametersAST.getChildCount() != 1) { 261 return false; 262 } 263 //method parameter, not constructor parameter? 264 final DetailAST methodAST = parametersAST.getParent(); 265 if (methodAST.getType() != TokenTypes.METHOD_DEF) { 266 return false; 267 } 268 //void? 269 final DetailAST typeAST = methodAST.findFirstToken(TokenTypes.TYPE); 270 if (!typeAST.branchContains(TokenTypes.LITERAL_VOID)) { 271 return false; 272 } 273 274 //property setter name? 275 final String methodName = 276 methodAST.findFirstToken(TokenTypes.IDENT).getText(); 277 final String expectedName = "set" + capitalize(aName); 278 return methodName.equals(expectedName); 279 } 280 281 /** 282 * Capitalizes a given property name the way we expect to see it in 283 * a setter name. 284 * @param aName a property name 285 * @return capitalized property name 286 */ 287 private static String capitalize(final String aName) 288 { 289 if (aName == null || aName.length() == 0) { 290 return aName; 291 } 292 // we should not capitalize the first character if the second 293 // one is a capital one, since according to JavaBeans spec 294 // setXYzz() is a setter for XYzz property, not for xYzz one. 295 if (aName.length() > 1 && Character.isUpperCase(aName.charAt(1))) { 296 return aName; 297 } 298 return aName.substring(0, 1).toUpperCase() + aName.substring(1); 299 } 300 301 /** 302 * Decides whether to ignore an AST node that is the parameter of a 303 * constructor. 304 * @param aAST the AST to check. 305 * @return true if aAST should be ignored because check property 306 * ignoreConstructorParameter is true and aAST is a constructor parameter. 307 */ 308 private boolean isIgnoredConstructorParam(DetailAST aAST) 309 { 310 if ((aAST.getType() != TokenTypes.PARAMETER_DEF) 311 || !mIgnoreConstructorParameter) 312 { 313 return false; 314 } 315 final DetailAST parametersAST = aAST.getParent(); 316 final DetailAST constructorAST = parametersAST.getParent(); 317 return (constructorAST.getType() == TokenTypes.CTOR_DEF); 318 } 319 320 /** 321 * Decides whether to ignore an AST node that is the parameter of an 322 * abstract method. 323 * @param aAST the AST to check. 324 * @return true if aAST should be ignored because check property 325 * ignoreAbstactMethods is true and aAST is a parameter of abstract 326 * methods. 327 */ 328 private boolean isIgnoredParamOfAbstractMethod(DetailAST aAST) 329 { 330 if ((aAST.getType() != TokenTypes.PARAMETER_DEF) 331 || !mIgnoreAbstractMethods) 332 { 333 return false; 334 } 335 final DetailAST method = aAST.getParent().getParent(); 336 if (method.getType() != TokenTypes.METHOD_DEF) { 337 return false; 338 } 339 final DetailAST mods = method.findFirstToken(TokenTypes.MODIFIERS); 340 return ((mods != null) && mods.branchContains(TokenTypes.ABSTRACT)); 341 } 342 343 /** 344 * Set the ignore format to the specified regular expression. 345 * @param aFormat a <code>String</code> value 346 * @throws ConversionException unable to parse aFormat 347 */ 348 public void setIgnoreFormat(String aFormat) 349 throws ConversionException 350 { 351 try { 352 mRegexp = Utils.getPattern(aFormat); 353 } 354 catch (final PatternSyntaxException e) { 355 throw new ConversionException("unable to parse " + aFormat, e); 356 } 357 } 358 359 /** 360 * Set whether to ignore the parameter of a property setter method. 361 * @param aIgnoreSetter decide whether to ignore the parameter of 362 * a property setter method. 363 */ 364 public void setIgnoreSetter(boolean aIgnoreSetter) 365 { 366 mIgnoreSetter = aIgnoreSetter; 367 } 368 369 /** 370 * Set whether to ignore constructor parameters. 371 * @param aIgnoreConstructorParameter decide whether to ignore 372 * constructor parameters. 373 */ 374 public void setIgnoreConstructorParameter( 375 boolean aIgnoreConstructorParameter) 376 { 377 mIgnoreConstructorParameter = aIgnoreConstructorParameter; 378 } 379 380 /** 381 * Set whether to ignore parameters of abstract methods. 382 * @param aIgnoreAbstractMethods decide whether to ignore 383 * parameters of abstract methods. 384 */ 385 public void setIgnoreAbstractMethods( 386 boolean aIgnoreAbstractMethods) 387 { 388 mIgnoreAbstractMethods = aIgnoreAbstractMethods; 389 } 390 391 /** @return the regexp to match against */ 392 public Pattern getRegexp() 393 { 394 return mRegexp; 395 } 396 397 /** 398 * Holds the names of static and instance fields of a type. 399 * @author Rick Giles 400 * Describe class FieldFrame 401 * @author Rick Giles 402 * @version Oct 26, 2003 403 */ 404 private static class FieldFrame 405 { 406 /** is this a static inner type */ 407 private final boolean mStaticType; 408 409 /** parent frame. */ 410 private final FieldFrame mParent; 411 412 /** set of instance field names */ 413 private final Set<String> mInstanceFields = Sets.newHashSet(); 414 415 /** set of static field names */ 416 private final Set<String> mStaticFields = Sets.newHashSet(); 417 418 /** Creates new frame. 419 * @param aStaticType is this a static inner type (class or enum). 420 * @param aParent parent frame. 421 */ 422 public FieldFrame(FieldFrame aParent, boolean aStaticType) 423 { 424 mParent = aParent; 425 mStaticType = aStaticType; 426 } 427 428 /** Is this frame for static inner type. 429 * @return is this field frame for static inner type. 430 */ 431 boolean isStaticType() 432 { 433 return mStaticType; 434 } 435 436 /** 437 * Adds an instance field to this FieldFrame. 438 * @param aField the name of the instance field. 439 */ 440 public void addInstanceField(String aField) 441 { 442 mInstanceFields.add(aField); 443 } 444 445 /** 446 * Adds a static field to this FieldFrame. 447 * @param aField the name of the instance field. 448 */ 449 public void addStaticField(String aField) 450 { 451 mStaticFields.add(aField); 452 } 453 454 /** 455 * Determines whether this FieldFrame contains an instance field. 456 * @param aField the field to check. 457 * @return true if this FieldFrame contains instance field aField. 458 */ 459 public boolean containsInstanceField(String aField) 460 { 461 return mInstanceFields.contains(aField) 462 || !isStaticType() 463 && (mParent != null) 464 && mParent.containsInstanceField(aField); 465 466 } 467 468 /** 469 * Determines whether this FieldFrame contains a static field. 470 * @param aField the field to check. 471 * @return true if this FieldFrame contains static field aField. 472 */ 473 public boolean containsStaticField(String aField) 474 { 475 return mStaticFields.contains(aField) 476 || (mParent != null) 477 && mParent.containsStaticField(aField); 478 479 } 480 481 /** 482 * Getter for parent frame. 483 * @return parent frame. 484 */ 485 public FieldFrame getParent() 486 { 487 return mParent; 488 } 489 } 490}