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}