Skip to content

Commit 76c9cd6

Browse files
committed
Issue #598: Forbid field access check implemented
1 parent bd5ca6b commit 76c9cd6

File tree

13 files changed

+415
-36
lines changed

13 files changed

+415
-36
lines changed

eclipsecs-sevntu-plugin/src/com/github/sevntu/checkstyle/checks/coding/checkstyle-metadata.properties

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,12 @@ ForbidCertainMethodCheck.desc = Forbids certain method usage. <br/><br/>You can
5757
ForbidCertainMethodCheck.methodName = Regex to match name of the forbidden method. When blank or unspecified, all the methods will be allowed.
5858
ForbidCertainMethodCheck.argumentCount = Number or range to match number of arguments the forbidden method takes. Multiple ranges are separated by comma. When unspecified, only method name will be used for check.
5959
60+
ForbidFieldAccessCheck.name = Forbid certain field access
61+
ForbidFieldAccessCheck.desc = <p>Checks that certain fields are not used. This can be used to enforce that fields like e.g. {@link java.util.Locale#ROOT} are not used.</p>
62+
ForbidFieldAccessCheck.packageName = The field package name to be forbidden. (default 'java.util')
63+
ForbidFieldAccessCheck.className = The field class name to be forbidden. (default 'Locale')
64+
ForbidFieldAccessCheck.fieldName = The field name to be forbidden. (default 'ROOT')
65+
6066
ForbidInstantiationCheck.name = Forbid Instantiation
6167
ForbidInstantiationCheck.desc = Forbids instantiation of certain object types by their full classname.<br><p>For example:<br>"java.lang.NullPointerException" will forbid the NPE instantiation.</p><p>Note: className should to be full: use "java.lang.NullPointerException" instead of "NullpointerException".</p>
6268
ForbidInstantiationCheck.forbiddenClasses = ClassNames for objects that are forbidden to instantiate.

eclipsecs-sevntu-plugin/src/com/github/sevntu/checkstyle/checks/coding/checkstyle-metadata.xml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,22 @@
192192
<message-key key="forbid.certain.method"/>
193193
</rule-metadata>
194194

195+
<rule-metadata name="%ForbidFieldAccessCheck.name" internal-name="ForbidFieldAccessCheck" parent="TreeWalker">
196+
<alternative-name internal-name="com.github.sevntu.checkstyle.checks.coding.ForbidFieldAccessCheck"/>
197+
<description>%ForbidFieldAccessCheck.desc</description>
198+
<property-metadata name="packageName" datatype="String" default-value="java.util">
199+
<description>%ForbidFieldAccessCheck.packageName</description>
200+
</property-metadata>
201+
<property-metadata name="className" datatype="String" default-value="Locale">
202+
<description>%ForbidFieldAccessCheck.className</description>
203+
</property-metadata>
204+
<property-metadata name="fieldName" datatype="String" default-value="ROOT">
205+
<description>%ForbidFieldAccessCheck.fieldName</description>
206+
</property-metadata>
207+
208+
<message-key key="forbid.field.access"/>
209+
</rule-metadata>
210+
195211
<rule-metadata name="%ForbidInstantiationCheck.name" internal-name="ForbidInstantiationCheck" parent="TreeWalker">
196212
<alternative-name internal-name="com.github.sevntu.checkstyle.checks.coding.ForbidInstantiationCheck"/>
197213
<description>%ForbidInstantiationCheck.desc</description>

sevntu-checks/sevntu-checks.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,11 @@
8484
<property name="methodName" value="assert(True|False)"/>
8585
<property name="argumentCount" value="1"/>
8686
</module>
87+
<module name="com.github.sevntu.checkstyle.checks.coding.ForbidFieldAccessCheck">
88+
<property name="packageName" value="java.util"/>
89+
<property name="className" value="Locale"/>
90+
<property name="fieldName" value="ROOT"/>
91+
</module>
8792
<module name="com.github.sevntu.checkstyle.checks.coding.OverridableMethodInConstructorCheck">
8893
<property name="checkCloneMethod" value="false"/>
8994
<property name="checkReadObjectMethod" value="false"/>

sevntu-checks/src/main/java/com/github/sevntu/checkstyle/SevntuUtil.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121

2222
import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
2323
import com.puppycrawl.tools.checkstyle.api.DetailAST;
24+
import com.puppycrawl.tools.checkstyle.api.FullIdent;
25+
import com.puppycrawl.tools.checkstyle.api.TokenTypes;
2426
import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
2527

2628
/**
@@ -76,4 +78,30 @@ public static DetailAST getNextSubTreeNode(DetailAST node, DetailAST subTreeRoot
7678
return toVisitAst;
7779
}
7880

81+
/**
82+
* Gets package/import text representation from node of PACKAGE_DEF or IMPORT type.
83+
* @param packageDefOrImportNode
84+
* - DetailAST node is pointing to package or import definition
85+
* (should be a PACKAGE_DEF or IMPORT type).
86+
* @return The fully qualified name of package or import without
87+
* "package"/"import" words or semicolons.
88+
*/
89+
public static String getText(DetailAST packageDefOrImportNode) {
90+
String result = null;
91+
92+
final DetailAST identNode = packageDefOrImportNode.findFirstToken(TokenTypes.IDENT);
93+
94+
if (identNode == null) {
95+
final DetailAST parentDotAST = packageDefOrImportNode.findFirstToken(TokenTypes.DOT);
96+
final FullIdent dottedPathIdent = FullIdent
97+
.createFullIdentBelow(parentDotAST);
98+
final DetailAST nameAST = parentDotAST.getLastChild();
99+
result = dottedPathIdent.getText() + "." + nameAST.getText();
100+
}
101+
else {
102+
result = identNode.getText();
103+
}
104+
return result;
105+
}
106+
79107
}

sevntu-checks/src/main/java/com/github/sevntu/checkstyle/checks/coding/ForbidCertainImportsCheck.java

Lines changed: 3 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
import com.github.sevntu.checkstyle.SevntuUtil;
2525
import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
2626
import com.puppycrawl.tools.checkstyle.api.DetailAST;
27-
import com.puppycrawl.tools.checkstyle.api.FullIdent;
2827
import com.puppycrawl.tools.checkstyle.api.TokenTypes;
2928

3029
/**
@@ -160,20 +159,20 @@ public void visitToken(DetailAST ast) {
160159
switch (ast.getType()) {
161160
case TokenTypes.PACKAGE_DEF:
162161
if (packageNamesRegexp != null) {
163-
final String packageQualifiedName = getText(ast);
162+
final String packageQualifiedName = SevntuUtil.getText(ast);
164163
packageMatches = packageNamesRegexp.matcher(packageQualifiedName)
165164
.matches();
166165
}
167166
break;
168167
case TokenTypes.IMPORT:
169-
final String importQualifiedText = getText(ast);
168+
final String importQualifiedText = SevntuUtil.getText(ast);
170169
if (isImportForbidden(importQualifiedText)) {
171170
log(ast, importQualifiedText);
172171
}
173172
break;
174173
case TokenTypes.LITERAL_NEW:
175174
if (ast.findFirstToken(TokenTypes.DOT) != null) {
176-
final String classQualifiedText = getText(ast);
175+
final String classQualifiedText = SevntuUtil.getText(ast);
177176
if (isImportForbidden(classQualifiedText)) {
178177
log(ast, classQualifiedText);
179178
}
@@ -211,30 +210,4 @@ private void log(DetailAST nodeToWarn, String importText) {
211210
getForbiddenImportRegexp(), importText);
212211
}
213212

214-
/**
215-
* Gets package/import text representation from node of PACKAGE_DEF or IMPORT type.
216-
* @param packageDefOrImportNode
217-
* - DetailAST node is pointing to package or import definition
218-
* (should be a PACKAGE_DEF or IMPORT type).
219-
* @return The fully qualified name of package or import without
220-
* "package"/"import" words or semicolons.
221-
*/
222-
private static String getText(DetailAST packageDefOrImportNode) {
223-
String result = null;
224-
225-
final DetailAST identNode = packageDefOrImportNode.findFirstToken(TokenTypes.IDENT);
226-
227-
if (identNode == null) {
228-
final DetailAST parentDotAST = packageDefOrImportNode.findFirstToken(TokenTypes.DOT);
229-
final FullIdent dottedPathIdent = FullIdent
230-
.createFullIdentBelow(parentDotAST);
231-
final DetailAST nameAST = parentDotAST.getLastChild();
232-
result = dottedPathIdent.getText() + "." + nameAST.getText();
233-
}
234-
else {
235-
result = identNode.getText();
236-
}
237-
return result;
238-
}
239-
240213
}
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
////////////////////////////////////////////////////////////////////////////////
2+
// checkstyle: Checks Java source code for adherence to a set of rules.
3+
// Copyright (C) 2001-2020 the original author or authors.
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+
20+
package com.github.sevntu.checkstyle.checks.coding;
21+
22+
import com.github.sevntu.checkstyle.SevntuUtil;
23+
import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
24+
import com.puppycrawl.tools.checkstyle.api.DetailAST;
25+
import com.puppycrawl.tools.checkstyle.api.FullIdent;
26+
import com.puppycrawl.tools.checkstyle.api.TokenTypes;
27+
28+
/**
29+
* <p>
30+
* Checks that certain fields are not used. This can be used to enforce that fields like e.g.
31+
* {@link java.util.Locale#ROOT}.
32+
* </p>
33+
*
34+
* <p>
35+
* Parameters are:
36+
* </p>
37+
*
38+
* <ul>
39+
* <li><b>packageName</b> - The field package name to be forbidden.</li>
40+
* <li><b>className</b> - The field class name to be forbidden.</li>
41+
* <li><b>fieldName</b> - The field name to be forbidden.</li>
42+
* </ul>
43+
*
44+
* <p>
45+
* Together, these three parameters forms the field fully qualified name.
46+
* </p>
47+
*
48+
* <p>
49+
* Default parameters are:
50+
* </p>
51+
*
52+
* <ul>
53+
* <li><b>packageName</b>java.util</li>
54+
* <li><b>className</b>Locale</li>
55+
* <li><b>fieldName</b>ROOT</li>
56+
* </ul>
57+
*
58+
* <p>
59+
* which forbids the usage of {@link java.util.Locale#ROOT}.
60+
* </p>
61+
*
62+
* @author <a href="mailto:[email protected]">Yasser Aziza</a>
63+
* @since 1.38.0
64+
*/
65+
public class ForbidFieldAccessCheck extends AbstractCheck {
66+
67+
/**
68+
* Warning message key.
69+
*/
70+
public static final String MSG_KEY = "forbid.field.access";
71+
72+
/**
73+
* '.' character used as separator between FQN elements.
74+
*/
75+
private static final char DOT = '.';
76+
77+
/**
78+
* Package name.
79+
*/
80+
private String packageName = "java.util";
81+
82+
/**
83+
* Class name.
84+
*/
85+
private String className = "Locale";
86+
87+
/**
88+
* Field name.
89+
*/
90+
private String fieldName = "ROOT";
91+
92+
/**
93+
* Whether the field class was imported.
94+
*/
95+
private boolean wasPackageImported;
96+
97+
/**
98+
* Sets the package name, in which the field is declared.
99+
* @param packageName the field name
100+
*/
101+
public void setPackageName(String packageName) {
102+
this.packageName = packageName;
103+
}
104+
105+
/**
106+
* Sets the class name, which declares the field.
107+
* @param className the class name
108+
*/
109+
public void setClassName(String className) {
110+
this.className = className;
111+
}
112+
113+
/**
114+
* Sets the Field name, which should be forbidden to use.
115+
* @param fieldName the field name
116+
*/
117+
public void setFieldName(String fieldName) {
118+
this.fieldName = fieldName;
119+
}
120+
121+
/**
122+
* Gets the field fully qualified name.
123+
* @return {@link String} containing the field FQN
124+
*/
125+
private String getFieldFullyQualifiedName() {
126+
return packageName + DOT + className + DOT + fieldName;
127+
}
128+
129+
@Override
130+
public int[] getDefaultTokens() {
131+
return new int[] {
132+
TokenTypes.STATIC_IMPORT,
133+
TokenTypes.IMPORT,
134+
TokenTypes.IDENT,
135+
};
136+
}
137+
138+
@Override
139+
public int[] getAcceptableTokens() {
140+
return getDefaultTokens();
141+
}
142+
143+
@Override
144+
public int[] getRequiredTokens() {
145+
return getDefaultTokens();
146+
}
147+
148+
@Override
149+
public void visitToken(DetailAST ast) {
150+
if (isStaticImported(ast)) {
151+
log(ast, MSG_KEY, getFieldFullyQualifiedName());
152+
}
153+
else if (isPackageImported(ast)) {
154+
// Mark forbidden field package as imported
155+
wasPackageImported = true;
156+
}
157+
else if (wasPackageImported && TokenTypes.IDENT == ast.getType() && isSameType(ast)) {
158+
log(ast.getPreviousSibling(), MSG_KEY, getFieldFullyQualifiedName());
159+
}
160+
}
161+
162+
/**
163+
* Checks whether the field is static imported.
164+
*
165+
* @param ast the {@link TokenTypes#STATIC_IMPORT} node to be checked
166+
* @return {@code true} if the field was static imported, {@code false} otherwise
167+
*/
168+
private boolean isStaticImported(DetailAST ast) {
169+
return TokenTypes.STATIC_IMPORT == ast.getType()
170+
&& getFieldFullyQualifiedName().equals(SevntuUtil.getText(ast));
171+
}
172+
173+
/**
174+
* Checks whether the field package is imported.
175+
*
176+
* @param ast the {@link TokenTypes#IMPORT} node to be checked
177+
* @return {@code true} if the field was imported, {@code false} otherwise
178+
*/
179+
private boolean isPackageImported(DetailAST ast) {
180+
final String importName = packageName + DOT + className;
181+
182+
return TokenTypes.IMPORT == ast.getType()
183+
&& importName.equals(FullIdent.createFullIdentBelow(ast).getText());
184+
}
185+
186+
/**
187+
* Checks if the given {@link TokenTypes#IDENT} node has the same type as the forbidden field.
188+
*
189+
* @param ast the {@link TokenTypes#IDENT} node to be checked
190+
* @return {@code true} if the field has the same FQN, {@code false} otherwise
191+
*/
192+
private boolean isSameType(DetailAST ast) {
193+
return fieldName.equals(ast.getText())
194+
&& ast.getPreviousSibling() != null
195+
&& className.equals(ast.getPreviousSibling().getText());
196+
}
197+
198+
}

sevntu-checks/src/main/resources/com/github/sevntu/checkstyle/checks/coding/messages.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ finalize.implementation.useless=finalize() method is useless: it does nothing ex
2121
forbid.c.comments.in.the.method.body=C-style comments (/*...*/) inside method body are not allowed.
2222
forbid.certain.imports=Import ''{1}'' should not match ''{0}'' pattern, it is forbidden.
2323
forbid.certain.method=Call to ''{0}'' method (matches pattern ''{1}'') with ''{2}'' arguments (matches pattern ''{3}'') is forbidden.
24+
forbid.field.access=Field access of type ''{0}'' is forbidden
2425
forbid.instantiation=Instantiation of ''{0}'' is not allowed.
2526
forbid.return.in.finally.block=Finally block should not contain return statements.
2627
forbid.throw.anonymous.exception=Avoid throwing anonymous exception.

0 commit comments

Comments
 (0)