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 * &lt;module name="HiddenField"/&gt;
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 * &lt;module name="HiddenField"&gt;
047 *    &lt;property name="tokens" value="VARIABLE_DEF"/&gt;
048 * &lt;/module&gt;
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 * &lt;module name="HiddenField"&gt;
055 *    &lt;property name="ignoreSetter" value="true"/&gt;
056 * &lt;/module&gt;
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 * &lt;module name="HiddenField"&gt;
087 *    &lt;property name="ignoreSetter" value="true"/&gt;
088 *    &lt;property name="setterCanReturnItsClass" value="true"/&gt;
089 * &lt;/module&gt;
090 * </pre>
091 * <p>
092 * An example of how to configure the check so that it ignores constructor
093 * parameters is:
094 * <pre>
095 * &lt;module name="HiddenField"&gt;
096 *    &lt;property name="ignoreConstructorParameter" value="true"/&gt;
097 * &lt;/module&gt;
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}