1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package com.puppycrawl.tools.checkstyle.checks.modifier;
21
22 import java.util.Arrays;
23 import java.util.HashMap;
24 import java.util.HashSet;
25 import java.util.Map;
26 import java.util.Set;
27
28 import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
29 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
30 import com.puppycrawl.tools.checkstyle.api.DetailAST;
31 import com.puppycrawl.tools.checkstyle.api.FullIdent;
32 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
33 import com.puppycrawl.tools.checkstyle.checks.naming.AccessModifierOption;
34 import com.puppycrawl.tools.checkstyle.utils.AnnotationUtil;
35 import com.puppycrawl.tools.checkstyle.utils.CheckUtil;
36 import com.puppycrawl.tools.checkstyle.utils.NullUtil;
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52 @FileStatefulCheck
53 public class AnnotatedDeclarationVisibilityCheck extends AbstractCheck {
54
55
56
57
58 public static final String MSG_KEY = "annotated.visibility.modifier";
59
60
61
62
63 private static final char DOT = '.';
64
65
66
67
68 private final Set<String> annotations = new HashSet<>(
69 Set.of("com.google.common.annotations.VisibleForTesting"));
70
71
72
73
74 private final Map<String, String> importedAnnotations = new HashMap<>();
75
76
77
78
79 private final Set<String> starImports = new HashSet<>();
80
81
82
83
84 private AccessModifierOption[] visibility = {
85 AccessModifierOption.PROTECTED,
86 AccessModifierOption.PACKAGE,
87 };
88
89
90
91
92 private String currentPackage;
93
94
95
96
97 public AnnotatedDeclarationVisibilityCheck() {
98
99 }
100
101
102
103
104
105
106
107 public void setAnnotations(String... values) {
108 annotations.clear();
109 annotations.addAll(Arrays.asList(values));
110 }
111
112
113
114
115
116
117
118
119
120 public void setVisibility(AccessModifierOption... values) {
121 visibility = Arrays.copyOf(values, values.length);
122 }
123
124 @Override
125 public void beginTree(DetailAST rootAST) {
126 currentPackage = "";
127 importedAnnotations.clear();
128 starImports.clear();
129 }
130
131 @Override
132 public int[] getDefaultTokens() {
133 return getAcceptableTokens();
134 }
135
136 @Override
137 public int[] getRequiredTokens() {
138 return new int[] {
139
140 TokenTypes.PACKAGE_DEF,
141 TokenTypes.IMPORT,
142 };
143 }
144
145 @Override
146 public int[] getAcceptableTokens() {
147 return new int[] {
148
149 TokenTypes.PACKAGE_DEF,
150 TokenTypes.IMPORT,
151
152 TokenTypes.CLASS_DEF,
153 TokenTypes.INTERFACE_DEF,
154 TokenTypes.ENUM_DEF,
155 TokenTypes.RECORD_DEF,
156 TokenTypes.METHOD_DEF,
157 TokenTypes.CTOR_DEF,
158 TokenTypes.VARIABLE_DEF,
159 TokenTypes.ANNOTATION_DEF,
160 };
161 }
162
163 @Override
164 public void visitToken(DetailAST ast) {
165 switch (ast.getType()) {
166 case TokenTypes.PACKAGE_DEF -> handlePackage(ast);
167 case TokenTypes.IMPORT -> handleImport(ast);
168 default -> checkAnnotatedVisibility(ast);
169 }
170 }
171
172
173
174
175
176
177 private void handlePackage(DetailAST ast) {
178 currentPackage =
179 FullIdent.createFullIdent(ast.getLastChild().getPreviousSibling()).getText();
180 }
181
182
183
184
185
186
187 private void handleImport(DetailAST ast) {
188 final String importText = FullIdent.createFullIdentBelow(ast).getText();
189 if (importText.endsWith(".*")) {
190 starImports.add(importText.substring(0, importText.length() - 2));
191 }
192 else {
193 final int lastDot = importText.lastIndexOf(DOT);
194 final String simple = importText.substring(lastDot + 1);
195 importedAnnotations.put(simple, importText);
196 }
197 }
198
199
200
201
202
203
204 private void checkAnnotatedVisibility(DetailAST ast) {
205 if ((ast.getType() != TokenTypes.VARIABLE_DEF
206 || ast.getParent().getType() == TokenTypes.OBJBLOCK)
207 && hasConfiguredAnnotation(ast)) {
208 final AccessModifierOption accessModifierOption =
209 CheckUtil.getAccessModifierFromModifiersToken(ast);
210 if (!isAllowedVisibility(accessModifierOption)) {
211 log(ast, MSG_KEY, getVisibilityName(accessModifierOption));
212 }
213 }
214 }
215
216
217
218
219
220
221
222 private boolean hasConfiguredAnnotation(DetailAST ast) {
223 boolean result = false;
224 final DetailAST modifiers =
225 NullUtil.notNull(ast.findFirstToken(TokenTypes.MODIFIERS));
226 DetailAST child = modifiers.getFirstChild();
227 while (child != null) {
228 final String annotationText =
229 AnnotationUtil.getAnnotationFullIdent(child);
230 final String resolved = resolveAnnotation(annotationText);
231 if (annotations.contains(resolved)) {
232 result = true;
233 }
234 child = child.getNextSibling();
235 }
236 return result;
237 }
238
239
240
241
242
243
244
245 private String resolveAnnotation(String name) {
246 String result = name;
247 final String pkgCandidate = currentPackage + DOT + name;
248 final String importCandidate = importedAnnotations.get(name);
249 final String javaLangCandidate = "java.lang." + name;
250 if (annotations.contains(pkgCandidate)) {
251 result = pkgCandidate;
252 }
253 else if (importCandidate != null) {
254 result = importCandidate;
255 }
256 else if (annotations.contains(javaLangCandidate)) {
257 result = javaLangCandidate;
258 }
259 else {
260 for (String starImport : starImports) {
261 final String starCandidate = starImport + DOT + name;
262 if (annotations.contains(starCandidate)) {
263 result = starCandidate;
264 }
265 }
266 }
267 return result;
268 }
269
270
271
272
273
274
275
276 private boolean isAllowedVisibility(AccessModifierOption accessModifier) {
277 return Arrays.stream(visibility)
278 .anyMatch(modifier -> modifier == accessModifier);
279 }
280
281
282
283
284
285
286
287 private static String getVisibilityName(AccessModifierOption accessModifier) {
288 final String result;
289 if (accessModifier == AccessModifierOption.PACKAGE) {
290 result = "package-private";
291 }
292 else {
293 result = accessModifier.toString();
294 }
295 return result;
296 }
297
298 }