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.design;
020
021import antlr.collections.AST;
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;
028import java.util.Set;
029import java.util.regex.Pattern;
030import java.util.regex.PatternSyntaxException;
031import org.apache.commons.beanutils.ConversionException;
032
033/**
034 * Checks visibility of class members. Only static final members may be public,
035 * other class members must be private unless allowProtected/Package is set.
036 * <p>
037 * Public members are not flagged if the name matches the public
038 * member regular expression (contains "^serialVersionUID$" by
039 * default).
040 * </p>
041 * Rationale: Enforce encapsulation.
042 *
043 * @author lkuehne
044 */
045public class VisibilityModifierCheck
046    extends Check
047{
048    /** whether protected members are allowed */
049    private boolean protectedAllowed;
050
051    /** whether package visible members are allowed */
052    private boolean packageAllowed;
053
054    /**
055     * pattern for public members that should be ignored.  Note:
056     * Earlier versions of checkstyle used ^f[A-Z][a-zA-Z0-9]*$ as the
057     * default to allow CMP for EJB 1.1 with the default settings.
058     * With EJB 2.0 it is not longer necessary to have public access
059     * for persistent fields.
060     */
061    private String publicMemberFormat = "^serialVersionUID$";
062
063    /** regexp for public members that should be ignored */
064    private Pattern publicMemberPattern;
065
066    /** Create an instance. */
067    public VisibilityModifierCheck()
068    {
069        setPublicMemberPattern(publicMemberFormat);
070    }
071
072    /** @return whether protected members are allowed */
073    public boolean isProtectedAllowed()
074    {
075        return protectedAllowed;
076    }
077
078    /**
079     * Set whether protected members are allowed.
080     * @param protectedAllowed whether protected members are allowed
081     */
082    public void setProtectedAllowed(boolean protectedAllowed)
083    {
084        this.protectedAllowed = protectedAllowed;
085    }
086
087    /** @return whether package visible members are allowed */
088    public boolean isPackageAllowed()
089    {
090        return packageAllowed;
091    }
092
093    /**
094     * Set whether package visible members are allowed.
095     * @param packageAllowed whether package visible members are allowed
096     */
097    public void setPackageAllowed(boolean packageAllowed)
098    {
099        this.packageAllowed = packageAllowed;
100    }
101
102    /**
103     * Set the pattern for public members to ignore.
104     * @param pattern pattern for public members to ignore.
105     */
106    public void setPublicMemberPattern(String pattern)
107    {
108        try {
109            publicMemberPattern = Utils.getPattern(pattern);
110            publicMemberFormat = pattern;
111        }
112        catch (final PatternSyntaxException e) {
113            throw new ConversionException("unable to parse " + pattern, e);
114        }
115    }
116
117    /**
118     * @return the regexp for public members to ignore.
119     */
120    private Pattern getPublicMemberRegexp()
121    {
122        return publicMemberPattern;
123    }
124
125    @Override
126    public int[] getDefaultTokens()
127    {
128        return new int[] {TokenTypes.VARIABLE_DEF};
129    }
130
131    @Override
132    public void visitToken(DetailAST ast)
133    {
134        if ((ast.getType() != TokenTypes.VARIABLE_DEF)
135            || (ast.getParent().getType() != TokenTypes.OBJBLOCK))
136        {
137            return;
138        }
139
140        final DetailAST varNameAST = getVarNameAST(ast);
141        final String varName = varNameAST.getText();
142        final boolean inInterfaceOrAnnotationBlock =
143            ScopeUtils.inInterfaceOrAnnotationBlock(ast);
144        final Set<String> mods = getModifiers(ast);
145        final String declaredScope = getVisibilityScope(mods);
146        final String variableScope =
147             inInterfaceOrAnnotationBlock ? "public" : declaredScope;
148
149        if (!("private".equals(variableScope)
150                || inInterfaceOrAnnotationBlock // implicitly static and final
151                || (mods.contains("static") && mods.contains("final"))
152                || ("package".equals(variableScope) && isPackageAllowed())
153                || ("protected".equals(variableScope) && isProtectedAllowed())
154                || ("public".equals(variableScope)
155                   && getPublicMemberRegexp().matcher(varName).find())))
156        {
157            log(varNameAST.getLineNo(), varNameAST.getColumnNo(),
158                    "variable.notPrivate", varName);
159        }
160    }
161
162    /**
163     * Returns the variable name in a VARIABLE_DEF AST.
164     * @param variableDefAST an AST where type == VARIABLE_DEF AST.
165     * @return the variable name in variableDefAST
166     */
167    private DetailAST getVarNameAST(DetailAST variableDefAST)
168    {
169        DetailAST ast = variableDefAST.getFirstChild();
170        while (ast != null) {
171            final DetailAST nextSibling = ast.getNextSibling();
172            if (ast.getType() == TokenTypes.TYPE) {
173                return nextSibling;
174            }
175            ast = nextSibling;
176        }
177        return null;
178    }
179
180    /**
181     * Returns the set of modifier Strings for a VARIABLE_DEF AST.
182     * @param variableDefAST AST for a vraiable definition
183     * @return the set of modifier Strings for variableDefAST
184     */
185    private Set<String> getModifiers(DetailAST variableDefAST)
186    {
187        final AST modifiersAST = variableDefAST.getFirstChild();
188        if (modifiersAST.getType() != TokenTypes.MODIFIERS) {
189            throw new IllegalStateException("Strange parse tree");
190        }
191        final Set<String> retVal = Sets.newHashSet();
192        AST modifier = modifiersAST.getFirstChild();
193        while (modifier != null) {
194            retVal.add(modifier.getText());
195            modifier = modifier.getNextSibling();
196        }
197        return retVal;
198
199    }
200
201    /**
202     * Returns the visibility scope specified with a set of modifiers.
203     * @param modifiers the set of modifier Strings
204     * @return one of "public", "private", "protected", "package"
205     */
206    private String getVisibilityScope(Set<String> modifiers)
207    {
208        final String[] explicitModifiers = {"public", "private", "protected"};
209        for (final String candidate : explicitModifiers) {
210            if (modifiers.contains(candidate)) {
211                return candidate;
212            }
213        }
214        return "package";
215    }
216}