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 * &lt;module name="HiddenField"/&gt;
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 * &lt;module name="HiddenField"&gt;
049 *    &lt;property name="tokens" value="VARIABLE_DEF"/&gt;
050 * &lt;/module&gt;
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 * &lt;module name="HiddenField"&gt;
058 *    &lt;property name="ignoreSetter" value="true"/&gt;
059 * &lt;/module&gt;
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 * &lt;module name="HiddenField"&gt;
067 *    &lt;property name="ignoreConstructorParameter" value="true"/&gt;
068 * &lt;/module&gt;
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}