////////////////////////////////////////////////////////////////////////////////
// checkstyle: Checks Java source code for adherence to a set of rules.
// Copyright (C) 2001-2008 Oliver Burn
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
////////////////////////////////////////////////////////////////////////////////
package com.puppycrawl.tools.checkstyle.checks.coding;
import com.google.common.collect.Sets;
import com.puppycrawl.tools.checkstyle.api.Check;
import com.puppycrawl.tools.checkstyle.api.DetailAST;
import com.puppycrawl.tools.checkstyle.api.ScopeUtils;
import com.puppycrawl.tools.checkstyle.api.TokenTypes;
import com.puppycrawl.tools.checkstyle.api.Utils;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import org.apache.commons.beanutils.ConversionException;
/**
* <p>Checks that a local variable or a parameter does not shadow
* a field that is defined in the same class.
* </p>
* <p>
* An example of how to configure the check is:
* </p>
* <pre>
* <module name="HiddenField"/>
* </pre>
* <p>
* An example of how to configure the check so that it checks variables but not
* parameters is:
* </p>
* <pre>
* <module name="HiddenField">
* <property name="tokens" value="VARIABLE_DEF"/>
* </module>
* </pre>
* <p>
* An example of how to configure the check so that it ignores the parameter of
* a setter method is:
* </p>
* <pre>
* <module name="HiddenField">
* <property name="ignoreSetter" value="true"/>
* </module>
* </pre>
* <p>
* An example of how to configure the check so that it ignores constructor
* parameters is:
* </p>
* <pre>
* <module name="HiddenField">
* <property name="ignoreConstructorParameter" value="true"/>
* </module>
* </pre>
* @author Rick Giles
* @version 1.0
*/
public class HiddenFieldCheck
extends Check
{
/** stack of sets of field names,
* one for each class of a set of nested classes.
*/
private FieldFrame mCurrentFrame;
/** the regexp to match against */
private Pattern mRegexp;
/** controls whether to check the parameter of a property setter method */
private boolean mIgnoreSetter;
/** controls whether to check the parameter of a constructor */
private boolean mIgnoreConstructorParameter;
/** controls whether to check the parameter of abstract methods. */
private boolean mIgnoreAbstractMethods;
@Override
public int[] getDefaultTokens()
{
return new int[] {
TokenTypes.VARIABLE_DEF,
TokenTypes.PARAMETER_DEF,
TokenTypes.CLASS_DEF,
TokenTypes.ENUM_DEF,
TokenTypes.ENUM_CONSTANT_DEF,
};
}
@Override
public int[] getAcceptableTokens()
{
return new int[] {
TokenTypes.VARIABLE_DEF,
TokenTypes.PARAMETER_DEF,
};
}
@Override
public int[] getRequiredTokens()
{
return new int[] {
TokenTypes.CLASS_DEF,
TokenTypes.ENUM_DEF,
TokenTypes.ENUM_CONSTANT_DEF,
};
}
@Override
public void beginTree(DetailAST aRootAST)
{
mCurrentFrame = new FieldFrame(null, true);
}
@Override
public void visitToken(DetailAST aAST)
{
if ((aAST.getType() == TokenTypes.VARIABLE_DEF)
|| (aAST.getType() == TokenTypes.PARAMETER_DEF))
{
processVariable(aAST);
return;
}
//A more thorough check of enum constant class bodies is
//possible (checking for hidden fields against the enum
//class body in addition to enum constant class bodies)
//but not attempted as it seems out of the scope of this
//check.
final DetailAST typeMods = aAST.findFirstToken(TokenTypes.MODIFIERS);
final boolean isStaticInnerType =
(typeMods != null)
&& typeMods.branchContains(TokenTypes.LITERAL_STATIC);
final FieldFrame frame =
new FieldFrame(mCurrentFrame, isStaticInnerType);
//add fields to container
final DetailAST objBlock = aAST.findFirstToken(TokenTypes.OBJBLOCK);
// enum constants may not have bodies
if (objBlock != null) {
DetailAST child = (DetailAST) objBlock.getFirstChild();
while (child != null) {
if (child.getType() == TokenTypes.VARIABLE_DEF) {
final String name =
child.findFirstToken(TokenTypes.IDENT).getText();
final DetailAST mods =
child.findFirstToken(TokenTypes.MODIFIERS);
if (mods.branchContains(TokenTypes.LITERAL_STATIC)) {
frame.addStaticField(name);
}
else {
frame.addInstanceField(name);
}
}
child = (DetailAST) child.getNextSibling();
}
}
// push container
mCurrentFrame = frame;
}
@Override
public void leaveToken(DetailAST aAST)
{
if ((aAST.getType() == TokenTypes.CLASS_DEF)
|| (aAST.getType() == TokenTypes.ENUM_DEF)
|| (aAST.getType() == TokenTypes.ENUM_CONSTANT_DEF))
{
//pop
mCurrentFrame = mCurrentFrame.getParent();
}
}
/**
* Process a variable token.
* Check whether a local variable or parameter shadows a field.
* Store a field for later comparison with local variables and parameters.
* @param aAST the variable token.
*/
private void processVariable(DetailAST aAST)
{
if (ScopeUtils.inInterfaceOrAnnotationBlock(aAST)
|| (!ScopeUtils.isLocalVariableDef(aAST)
&& (aAST.getType() != TokenTypes.PARAMETER_DEF)))
{
// do nothing
return;
}
//local variable or parameter. Does it shadow a field?
final DetailAST nameAST = aAST.findFirstToken(TokenTypes.IDENT);
final String name = nameAST.getText();
if ((mCurrentFrame.containsStaticField(name)
|| (!inStatic(aAST) && mCurrentFrame.containsInstanceField(name)))
&& ((mRegexp == null) || (!getRegexp().matcher(name).find()))
&& !isIgnoredSetterParam(aAST, name)
&& !isIgnoredConstructorParam(aAST)
&& !isIgnoredParamOfAbstractMethod(aAST))
{
log(nameAST, "hidden.field", name);
}
}
/**
* Determines whether an AST node is in a static method or static
* initializer.
* @param aAST the node to check.
* @return true if aAST is in a static method or a static block;
*/
private static boolean inStatic(DetailAST aAST)
{
DetailAST parent = aAST.getParent();
while (parent != null) {
switch (parent.getType()) {
case TokenTypes.STATIC_INIT:
return true;
case TokenTypes.METHOD_DEF:
final DetailAST mods =
parent.findFirstToken(TokenTypes.MODIFIERS);
return mods.branchContains(TokenTypes.LITERAL_STATIC);
default:
parent = parent.getParent();
}
}
return false;
}
/**
* Decides whether to ignore an AST node that is the parameter of a
* setter method, where the property setter method for field 'xyz' has
* name 'setXyz', one parameter named 'xyz', and return type void.
* @param aAST the AST to check.
* @param aName the name of aAST.
* @return true if aAST should be ignored because check property
* ignoreSetter is true and aAST is the parameter of a setter method.
*/
private boolean isIgnoredSetterParam(DetailAST aAST, String aName)
{
if (!(aAST.getType() == TokenTypes.PARAMETER_DEF)
|| !mIgnoreSetter)
{
return false;
}
//single parameter?
final DetailAST parametersAST = aAST.getParent();
if (parametersAST.getChildCount() != 1) {
return false;
}
//method parameter, not constructor parameter?
final DetailAST methodAST = parametersAST.getParent();
if (methodAST.getType() != TokenTypes.METHOD_DEF) {
return false;
}
//property setter name?
final String expectedName =
"set" + aName.substring(0, 1).toUpperCase() + aName.substring(1);
final DetailAST methodNameAST =
methodAST.findFirstToken(TokenTypes.IDENT);
final String methodName = methodNameAST.getText();
if (!methodName.equals(expectedName)) {
return false;
}
//void?
final DetailAST typeAST = methodAST.findFirstToken(TokenTypes.TYPE);
return typeAST.branchContains(TokenTypes.LITERAL_VOID);
}
/**
* Decides whether to ignore an AST node that is the parameter of a
* constructor.
* @param aAST the AST to check.
* @return true if aAST should be ignored because check property
* ignoreConstructorParameter is true and aAST is a constructor parameter.
*/
private boolean isIgnoredConstructorParam(DetailAST aAST)
{
if ((aAST.getType() != TokenTypes.PARAMETER_DEF)
|| !mIgnoreConstructorParameter)
{
return false;
}
final DetailAST parametersAST = aAST.getParent();
final DetailAST constructorAST = parametersAST.getParent();
return (constructorAST.getType() == TokenTypes.CTOR_DEF);
}
/**
* Decides whether to ignore an AST node that is the parameter of an
* abstract method.
* @param aAST the AST to check.
* @return true if aAST should be ignored because check property
* ignoreAbstactMethods is true and aAST is a parameter of abstract
* methods.
*/
private boolean isIgnoredParamOfAbstractMethod(DetailAST aAST)
{
if ((aAST.getType() != TokenTypes.PARAMETER_DEF)
|| !mIgnoreAbstractMethods)
{
return false;
}
final DetailAST method = aAST.getParent().getParent();
if (method.getType() != TokenTypes.METHOD_DEF) {
return false;
}
final DetailAST mods = method.findFirstToken(TokenTypes.MODIFIERS);
return ((mods != null) && mods.branchContains(TokenTypes.ABSTRACT));
}
/**
* Set the ignore format to the specified regular expression.
* @param aFormat a <code>String</code> value
* @throws ConversionException unable to parse aFormat
*/
public void setIgnoreFormat(String aFormat)
throws ConversionException
{
try {
mRegexp = Utils.getPattern(aFormat);
}
catch (final PatternSyntaxException e) {
throw new ConversionException("unable to parse " + aFormat, e);
}
}
/**
* Set whether to ignore the parameter of a property setter method.
* @param aIgnoreSetter decide whether to ignore the parameter of
* a property setter method.
*/
public void setIgnoreSetter(boolean aIgnoreSetter)
{
mIgnoreSetter = aIgnoreSetter;
}
/**
* Set whether to ignore constructor parameters.
* @param aIgnoreConstructorParameter decide whether to ignore
* constructor parameters.
*/
public void setIgnoreConstructorParameter(
boolean aIgnoreConstructorParameter)
{
mIgnoreConstructorParameter = aIgnoreConstructorParameter;
}
/**
* Set whether to ignore parameters of abstract methods.
* @param aIgnoreAbstractMethods decide whether to ignore
* parameters of abstract methods.
*/
public void setIgnoreAbstractMethods(
boolean aIgnoreAbstractMethods)
{
mIgnoreAbstractMethods = aIgnoreAbstractMethods;
}
/** @return the regexp to match against */
public Pattern getRegexp()
{
return mRegexp;
}
/**
* Holds the names of static and instance fields of a type.
* @author Rick Giles
* Describe class FieldFrame
* @author Rick Giles
* @version Oct 26, 2003
*/
private static class FieldFrame
{
/** is this a static inner type */
private final boolean mStaticType;
/** parent frame. */
private final FieldFrame mParent;
/** set of instance field names */
private final Set<String> mInstanceFields = Sets.newHashSet();
/** set of static field names */
private final Set<String> mStaticFields = Sets.newHashSet();
/** Creates new frame.
* @param aStaticType is this a static inner type (class or enum).
* @param aParent parent frame.
*/
public FieldFrame(FieldFrame aParent, boolean aStaticType)
{
mParent = aParent;
mStaticType = aStaticType;
}
/** Is this frame for static inner type.
* @return is this field frame for static inner type.
*/
boolean isStaticType()
{
return mStaticType;
}
/**
* Adds an instance field to this FieldFrame.
* @param aField the name of the instance field.
*/
public void addInstanceField(String aField)
{
mInstanceFields.add(aField);
}
/**
* Adds a static field to this FieldFrame.
* @param aField the name of the instance field.
*/
public void addStaticField(String aField)
{
mStaticFields.add(aField);
}
/**
* Determines whether this FieldFrame contains an instance field.
* @param aField the field to check.
* @return true if this FieldFrame contains instance field aField.
*/
public boolean containsInstanceField(String aField)
{
if (mInstanceFields.contains(aField)) {
return true;
}
if (mStaticType) {
return false;
}
return (mParent != null) && mParent.containsInstanceField(aField);
}
/**
* Determines whether this FieldFrame contains a static field.
* @param aField the field to check.
* @return true if this FieldFrame contains static field aField.
*/
public boolean containsStaticField(String aField)
{
if (mStaticFields.contains(aField)) {
return true;
}
return (mParent != null) && mParent.containsStaticField(aField);
}
/**
* Getter for parent frame.
* @return parent frame.
*/
public FieldFrame getParent()
{
return mParent;
}
}
}