Coverage Report - com.puppycrawl.tools.checkstyle.checks.coding.HiddenFieldCheck
 
Classes in this File Line Coverage Branch Coverage Complexity
HiddenFieldCheck
96%
84/87
96%
80/83
3.458
HiddenFieldCheck$FieldFrame
100%
14/14
92%
13/14
3.458
 
 1  
 ////////////////////////////////////////////////////////////////////////////////
 2  
 // checkstyle: Checks Java source code for adherence to a set of rules.
 3  
 // Copyright (C) 2001-2014  Oliver Burn
 4  
 //
 5  
 // This library is free software; you can redistribute it and/or
 6  
 // modify it under the terms of the GNU Lesser General Public
 7  
 // License as published by the Free Software Foundation; either
 8  
 // version 2.1 of the License, or (at your option) any later version.
 9  
 //
 10  
 // This library is distributed in the hope that it will be useful,
 11  
 // but WITHOUT ANY WARRANTY; without even the implied warranty of
 12  
 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 13  
 // Lesser General Public License for more details.
 14  
 //
 15  
 // You should have received a copy of the GNU Lesser General Public
 16  
 // License along with this library; if not, write to the Free Software
 17  
 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 18  
 ////////////////////////////////////////////////////////////////////////////////
 19  
 package com.puppycrawl.tools.checkstyle.checks.coding;
 20  
 
 21  
 import com.google.common.collect.Sets;
 22  
 import com.puppycrawl.tools.checkstyle.api.Check;
 23  
 import com.puppycrawl.tools.checkstyle.api.DetailAST;
 24  
 import com.puppycrawl.tools.checkstyle.api.ScopeUtils;
 25  
 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
 26  
 import com.puppycrawl.tools.checkstyle.api.Utils;
 27  
 
 28  
 import java.util.Set;
 29  
 import java.util.regex.Pattern;
 30  
 import java.util.regex.PatternSyntaxException;
 31  
 import org.apache.commons.beanutils.ConversionException;
 32  
 
 33  
 /**
 34  
  * <p>Checks that a local variable or a parameter does not shadow
 35  
  * a field that is defined in the same class.
 36  
  * </p>
 37  
  * <p>
 38  
  * An example of how to configure the check is:
 39  
  * </p>
 40  
  * <pre>
 41  
  * &lt;module name="HiddenField"/&gt;
 42  
  * </pre>
 43  
  * <p>
 44  
  * An example of how to configure the check so that it checks variables but not
 45  
  * parameters is:
 46  
  * </p>
 47  
  * <pre>
 48  
  * &lt;module name="HiddenField"&gt;
 49  
  *    &lt;property name="tokens" value="VARIABLE_DEF"/&gt;
 50  
  * &lt;/module&gt;
 51  
  * </pre>
 52  
  * <p>
 53  
  * An example of how to configure the check so that it ignores the parameter of
 54  
  * a setter method is:
 55  
  * </p>
 56  
  * <pre>
 57  
  * &lt;module name="HiddenField"&gt;
 58  
  *    &lt;property name="ignoreSetter" value="true"/&gt;
 59  
  * &lt;/module&gt;
 60  
  * </pre>
 61  
  * <p>
 62  
  * An example of how to configure the check so that it ignores constructor
 63  
  * parameters is:
 64  
  * </p>
 65  
  * <pre>
 66  
  * &lt;module name="HiddenField"&gt;
 67  
  *    &lt;property name="ignoreConstructorParameter" value="true"/&gt;
 68  
  * &lt;/module&gt;
 69  
  * </pre>
 70  
  * @author Rick Giles
 71  
  * @version 1.0
 72  
  */
 73  7
 public class HiddenFieldCheck
 74  
     extends Check
 75  
 {
 76  
     /** stack of sets of field names,
 77  
      * one for each class of a set of nested classes.
 78  
      */
 79  
     private FieldFrame mCurrentFrame;
 80  
 
 81  
     /** the regexp to match against */
 82  
     private Pattern mRegexp;
 83  
 
 84  
     /** controls whether to check the parameter of a property setter method */
 85  
     private boolean mIgnoreSetter;
 86  
 
 87  
     /** controls whether to check the parameter of a constructor */
 88  
     private boolean mIgnoreConstructorParameter;
 89  
 
 90  
     /** controls whether to check the parameter of abstract methods. */
 91  
     private boolean mIgnoreAbstractMethods;
 92  
 
 93  
     @Override
 94  
     public int[] getDefaultTokens()
 95  
     {
 96  6
         return new int[] {
 97  
             TokenTypes.VARIABLE_DEF,
 98  
             TokenTypes.PARAMETER_DEF,
 99  
             TokenTypes.CLASS_DEF,
 100  
             TokenTypes.ENUM_DEF,
 101  
             TokenTypes.ENUM_CONSTANT_DEF,
 102  
         };
 103  
     }
 104  
 
 105  
     @Override
 106  
     public int[] getAcceptableTokens()
 107  
     {
 108  1
         return new int[] {
 109  
             TokenTypes.VARIABLE_DEF,
 110  
             TokenTypes.PARAMETER_DEF,
 111  
         };
 112  
     }
 113  
 
 114  
     @Override
 115  
     public int[] getRequiredTokens()
 116  
     {
 117  1
         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  7
         mCurrentFrame = new FieldFrame(null, true);
 128  7
     }
 129  
 
 130  
     @Override
 131  
     public void visitToken(DetailAST aAST)
 132  
     {
 133  395
         if ((aAST.getType() == TokenTypes.VARIABLE_DEF)
 134  
             || (aAST.getType() == TokenTypes.PARAMETER_DEF))
 135  
         {
 136  311
             processVariable(aAST);
 137  311
             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  84
         final DetailAST typeMods = aAST.findFirstToken(TokenTypes.MODIFIERS);
 146  84
         final boolean isStaticInnerType =
 147  
                 (typeMods != null)
 148  
                         && typeMods.branchContains(TokenTypes.LITERAL_STATIC);
 149  84
         final FieldFrame frame =
 150  
                 new FieldFrame(mCurrentFrame, isStaticInnerType);
 151  
 
 152  
         //add fields to container
 153  84
         final DetailAST objBlock = aAST.findFirstToken(TokenTypes.OBJBLOCK);
 154  
         // enum constants may not have bodies
 155  84
         if (objBlock != null) {
 156  70
             DetailAST child = objBlock.getFirstChild();
 157  544
             while (child != null) {
 158  474
                 if (child.getType() == TokenTypes.VARIABLE_DEF) {
 159  83
                     final String name =
 160  
                         child.findFirstToken(TokenTypes.IDENT).getText();
 161  83
                     final DetailAST mods =
 162  
                         child.findFirstToken(TokenTypes.MODIFIERS);
 163  83
                     if (mods.branchContains(TokenTypes.LITERAL_STATIC)) {
 164  19
                         frame.addStaticField(name);
 165  
                     }
 166  
                     else {
 167  64
                         frame.addInstanceField(name);
 168  
                     }
 169  
                 }
 170  474
                 child = child.getNextSibling();
 171  
             }
 172  
         }
 173  
         // push container
 174  84
         mCurrentFrame = frame;
 175  84
     }
 176  
 
 177  
     @Override
 178  
     public void leaveToken(DetailAST aAST)
 179  
     {
 180  395
         if ((aAST.getType() == TokenTypes.CLASS_DEF)
 181  
             || (aAST.getType() == TokenTypes.ENUM_DEF)
 182  
             || (aAST.getType() == TokenTypes.ENUM_CONSTANT_DEF))
 183  
         {
 184  
             //pop
 185  84
             mCurrentFrame = mCurrentFrame.getParent();
 186  
         }
 187  395
     }
 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  311
         if (ScopeUtils.inInterfaceOrAnnotationBlock(aAST)
 198  
             || (!ScopeUtils.isLocalVariableDef(aAST)
 199  
             && (aAST.getType() != TokenTypes.PARAMETER_DEF)))
 200  
         {
 201  
             // do nothing
 202  96
             return;
 203  
         }
 204  
         //local variable or parameter. Does it shadow a field?
 205  215
         final DetailAST nameAST = aAST.findFirstToken(TokenTypes.IDENT);
 206  215
         final String name = nameAST.getText();
 207  215
         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  181
             log(nameAST, "hidden.field", name);
 215  
         }
 216  215
     }
 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  179
         DetailAST parent = aAST.getParent();
 227  613
         while (parent != null) {
 228  560
             switch (parent.getType()) {
 229  
             case TokenTypes.STATIC_INIT:
 230  6
                 return true;
 231  
             case TokenTypes.METHOD_DEF:
 232  120
                 final DetailAST mods =
 233  
                     parent.findFirstToken(TokenTypes.MODIFIERS);
 234  120
                 return mods.branchContains(TokenTypes.LITERAL_STATIC);
 235  
             default:
 236  434
                 parent = parent.getParent();
 237  
             }
 238  
         }
 239  53
         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  187
         if (aAST.getType() != TokenTypes.PARAMETER_DEF
 254  
             || !mIgnoreSetter)
 255  
         {
 256  174
             return false;
 257  
         }
 258  
         //single parameter?
 259  13
         final DetailAST parametersAST = aAST.getParent();
 260  13
         if (parametersAST.getChildCount() != 1) {
 261  3
             return false;
 262  
         }
 263  
         //method parameter, not constructor parameter?
 264  10
         final DetailAST methodAST = parametersAST.getParent();
 265  10
         if (methodAST.getType() != TokenTypes.METHOD_DEF) {
 266  3
             return false;
 267  
         }
 268  
         //void?
 269  7
         final DetailAST typeAST = methodAST.findFirstToken(TokenTypes.TYPE);
 270  7
         if (!typeAST.branchContains(TokenTypes.LITERAL_VOID)) {
 271  1
             return false;
 272  
         }
 273  
 
 274  
         //property setter name?
 275  6
         final String methodName =
 276  
                 methodAST.findFirstToken(TokenTypes.IDENT).getText();
 277  6
         final String expectedName = "set" + capitalize(aName);
 278  6
         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  6
         if (aName == null || aName.length() == 0) {
 290  0
             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  6
         if (aName.length() > 1 && Character.isUpperCase(aName.charAt(1))) {
 296  1
             return aName;
 297  
         }
 298  5
         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  185
         if ((aAST.getType() != TokenTypes.PARAMETER_DEF)
 311  
             || !mIgnoreConstructorParameter)
 312  
         {
 313  172
             return false;
 314  
         }
 315  13
         final DetailAST parametersAST = aAST.getParent();
 316  13
         final DetailAST constructorAST = parametersAST.getParent();
 317  13
         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  182
         if ((aAST.getType() != TokenTypes.PARAMETER_DEF)
 331  
             || !mIgnoreAbstractMethods)
 332  
         {
 333  169
             return false;
 334  
         }
 335  13
         final DetailAST method = aAST.getParent().getParent();
 336  13
         if (method.getType() != TokenTypes.METHOD_DEF) {
 337  3
             return false;
 338  
         }
 339  10
         final DetailAST mods = method.findFirstToken(TokenTypes.MODIFIERS);
 340  10
         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  1
             mRegexp = Utils.getPattern(aFormat);
 353  
         }
 354  0
         catch (final PatternSyntaxException e) {
 355  0
             throw new ConversionException("unable to parse " + aFormat, e);
 356  1
         }
 357  1
     }
 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  1
         mIgnoreSetter = aIgnoreSetter;
 367  1
     }
 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  1
         mIgnoreConstructorParameter = aIgnoreConstructorParameter;
 378  1
     }
 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  1
         mIgnoreAbstractMethods = aIgnoreAbstractMethods;
 389  1
     }
 390  
 
 391  
     /** @return the regexp to match against */
 392  
     public Pattern getRegexp()
 393  
     {
 394  31
         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  7
     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  91
         private final Set<String> mInstanceFields = Sets.newHashSet();
 414  
 
 415  
         /** set of static field names */
 416  91
         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  91
         {
 424  91
             mParent = aParent;
 425  91
             mStaticType = aStaticType;
 426  91
         }
 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  42
             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  64
             mInstanceFields.add(aField);
 443  64
         }
 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  19
             mStaticFields.add(aField);
 452  19
         }
 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  199
             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  478
             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  84
             return mParent;
 488  
         }
 489  
     }
 490  
 }