Source code: com/puppycrawl/tools/checkstyle/checks/design/VisibilityModifierCheck.java
1 ////////////////////////////////////////////////////////////////////////////////
2 // checkstyle: Checks Java source code for adherence to a set of rules.
3 // Copyright (C) 2001-2003 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.design;
20
21 import java.util.HashSet;
22 import java.util.Set;
23
24 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
25 import com.puppycrawl.tools.checkstyle.api.Check;
26 import com.puppycrawl.tools.checkstyle.api.DetailAST;
27 import com.puppycrawl.tools.checkstyle.api.Utils;
28 import org.apache.commons.beanutils.ConversionException;
29 import org.apache.regexp.RE;
30 import org.apache.regexp.RESyntaxException;
31 import antlr.collections.AST;
32
33 /**
34 * Checks visibility of class members. Only static final members may be public,
35 * other class members must be private unless allowProtected/Package is set.
36 * <p>
37 * Public members are not flagged if the name matches the public
38 * member regular expression (contains "^serialVersionUID$" by
39 * default).
40 * </p>
41 * Rationale: Enforce encapsulation.
42 *
43 * @author lkuehne
44 */
45 public class VisibilityModifierCheck
46 extends Check
47 {
48 /** whether protected members are allowed */
49 private boolean mProtectedAllowed;
50
51 /** whether package visible members are allowed */
52 private boolean mPackageAllowed;
53
54 /**
55 * pattern for public members that should be ignored. Note:
56 * Earlier versions of checkstyle used ^f[A-Z][a-zA-Z0-9]*$ as the
57 * default to allow CMP for EJB 1.1 with the default settings.
58 * With EJB 2.0 it is not longer necessary to have public access
59 * for persistent fields.
60 */
61 private String mPublicMemberPattern = "^serialVersionUID$";
62
63 /** regexp for public members that should be ignored */
64 private RE mPublicMemberRE;
65
66 /** Create an instance. */
67 public VisibilityModifierCheck()
68 {
69 setPublicMemberPattern(mPublicMemberPattern);
70 }
71
72 /** @return whether protected members are allowed */
73 public boolean isProtectedAllowed()
74 {
75 return mProtectedAllowed;
76 }
77
78 /**
79 * Set whether protected members are allowed.
80 * @param aProtectedAllowed whether protected members are allowed
81 */
82 public void setProtectedAllowed(boolean aProtectedAllowed)
83 {
84 mProtectedAllowed = aProtectedAllowed;
85 }
86
87 /** @return whether package visible members are allowed */
88 public boolean isPackageAllowed()
89 {
90 return mPackageAllowed;
91 }
92
93 /**
94 * Set whether package visible members are allowed.
95 * @param aPackageAllowed whether package visible members are allowed
96 */
97 public void setPackageAllowed(boolean aPackageAllowed)
98 {
99 mPackageAllowed = aPackageAllowed;
100 }
101
102 /**
103 * Set the pattern for public members to ignore.
104 * @param aPattern pattern for public members to ignore.
105 */
106 public void setPublicMemberPattern(String aPattern)
107 {
108 try {
109 mPublicMemberRE = Utils.getRE(aPattern);
110 mPublicMemberPattern = aPattern;
111 }
112 catch (RESyntaxException e) {
113 throw new ConversionException("unable to parse " + aPattern, e);
114 }
115 }
116
117 /**
118 * @return the regexp for public members to ignore.
119 */
120 private RE getPublicMemberRegexp()
121 {
122 return mPublicMemberRE;
123 }
124
125 /** @see Check */
126 public int[] getDefaultTokens()
127 {
128 return new int[] {TokenTypes.VARIABLE_DEF};
129 }
130
131 /** @see Check */
132 public void visitToken(DetailAST aAST)
133 {
134 if ((aAST.getType() != TokenTypes.VARIABLE_DEF)
135 || (aAST.getParent().getType() != TokenTypes.OBJBLOCK))
136 {
137 return;
138 }
139
140 final DetailAST varNameAST = getVarNameAST(aAST);
141 String varName = varNameAST.getText();
142 boolean inInterfaceBlock = inInterfaceBlock(aAST);
143 final Set mods = getModifiers(aAST);
144 final String declaredScope = getVisibilityScope(mods);
145 final String variableScope =
146 inInterfaceBlock ? "public" : declaredScope;
147
148 if (!("private".equals(variableScope)
149 || inInterfaceBlock // implicitly static and final
150 || mods.contains("static") && mods.contains("final")
151 || "package".equals(variableScope) && isPackageAllowed()
152 || "protected".equals(variableScope) && isProtectedAllowed()
153 || "public".equals(variableScope)
154 && getPublicMemberRegexp().match(varName)))
155 {
156 log(varNameAST.getLineNo(), varNameAST.getColumnNo(),
157 "variable.notPrivate", varName);
158 }
159 }
160
161 /**
162 * Returns the variable name in a VARIABLE_DEF AST.
163 * @param aVariableDefAST an AST where type == VARIABLE_DEF AST.
164 * @return the variable name in aVariableDefAST
165 */
166 private DetailAST getVarNameAST(DetailAST aVariableDefAST)
167 {
168 AST ast = aVariableDefAST.getFirstChild();
169 while (ast != null) {
170 AST nextSibling = ast.getNextSibling();
171 if (ast.getType() == TokenTypes.TYPE) {
172 return (DetailAST) nextSibling;
173 }
174 ast = nextSibling;
175 }
176 return null;
177 }
178
179 /**
180 * Returns whether an AST is in an interface block.
181 * @param aAST the AST to check for
182 * @return true iff aAST is in an interface def with no class def in between
183 */
184 private boolean inInterfaceBlock(DetailAST aAST)
185 {
186 DetailAST ast = aAST.getParent();
187 while (ast != null) {
188 switch (ast.getType()) {
189 case TokenTypes.INTERFACE_DEF:
190 return true;
191 case TokenTypes.CLASS_DEF:
192 return false;
193 default:
194 ast = ast.getParent();
195 }
196 }
197 return false;
198 }
199
200 /**
201 * Returns the set of modifier Strings for a VARIABLE_DEF AST.
202 * @param aVariableDefAST AST for a vraiable definition
203 * @return the set of modifier Strings for variableDefAST
204 */
205 private Set getModifiers(DetailAST aVariableDefAST)
206 {
207 final AST modifiersAST = aVariableDefAST.getFirstChild();
208 if (modifiersAST.getType() != TokenTypes.MODIFIERS) {
209 throw new IllegalStateException("Strange parse tree");
210 }
211 final Set retVal = new HashSet();
212 AST modifier = modifiersAST.getFirstChild();
213 while (modifier != null) {
214 retVal.add(modifier.getText());
215 modifier = modifier.getNextSibling();
216 }
217 return retVal;
218
219 }
220
221 /**
222 * Returns the visibility scope specified with a set of modifiers.
223 * @param aModifiers the set of modifier Strings
224 * @return one of "public", "private", "protected", "package"
225 */
226 private String getVisibilityScope(Set aModifiers)
227 {
228 final String[] explicitModifiers = {"public", "private", "protected"};
229 for (int i = 0; i < explicitModifiers.length; i++) {
230 String candidate = explicitModifiers[i];
231 if (aModifiers.contains(candidate)) {
232 return candidate;
233 }
234 }
235 return "package";
236 }
237 }