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;
21
22 import java.io.File;
23 import java.util.Arrays;
24 import java.util.Collection;
25 import java.util.HashMap;
26 import java.util.HashSet;
27 import java.util.Locale;
28 import java.util.Map;
29 import java.util.Set;
30 import java.util.SortedSet;
31 import java.util.TreeSet;
32
33 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
34 import com.puppycrawl.tools.checkstyle.api.AbstractFileSetCheck;
35 import com.puppycrawl.tools.checkstyle.api.AutomaticBean;
36 import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
37 import com.puppycrawl.tools.checkstyle.api.Configuration;
38 import com.puppycrawl.tools.checkstyle.api.Context;
39 import com.puppycrawl.tools.checkstyle.api.DetailAST;
40 import com.puppycrawl.tools.checkstyle.api.ExternalResourceHolder;
41 import com.puppycrawl.tools.checkstyle.api.FileContents;
42 import com.puppycrawl.tools.checkstyle.api.FileText;
43 import com.puppycrawl.tools.checkstyle.api.LocalizedMessage;
44 import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
45
46
47
48
49
50
51 @FileStatefulCheck
52 public final class TreeWalker extends AbstractFileSetCheck implements ExternalResourceHolder {
53
54
55 private final Map<String, Set<AbstractCheck>> tokenToOrdinaryChecks =
56 new HashMap<>();
57
58
59 private final Map<String, Set<AbstractCheck>> tokenToCommentChecks =
60 new HashMap<>();
61
62
63 private final Set<AbstractCheck> ordinaryChecks = new HashSet<>();
64
65
66 private final Set<AbstractCheck> commentChecks = new HashSet<>();
67
68
69 private final Set<TreeWalkerFilter> filters = new HashSet<>();
70
71
72 private final SortedSet<LocalizedMessage> messages = new TreeSet<>();
73
74
75 private ClassLoader classLoader;
76
77
78 private Context childContext;
79
80
81 private ModuleFactory moduleFactory;
82
83
84
85
86 public TreeWalker() {
87 setFileExtensions("java");
88 }
89
90
91
92
93
94 public void setClassLoader(ClassLoader classLoader) {
95 this.classLoader = classLoader;
96 }
97
98
99
100
101
102 public void setModuleFactory(ModuleFactory moduleFactory) {
103 this.moduleFactory = moduleFactory;
104 }
105
106 @Override
107 public void finishLocalSetup() {
108 final DefaultContext checkContext = new DefaultContext();
109 checkContext.add("classLoader", classLoader);
110 checkContext.add("severity", getSeverity());
111 checkContext.add("tabWidth", String.valueOf(getTabWidth()));
112
113 childContext = checkContext;
114 }
115
116
117
118
119
120 @Override
121 public void setupChild(Configuration childConf)
122 throws CheckstyleException {
123 final String name = childConf.getName();
124 final Object module;
125
126 try {
127 module = moduleFactory.createModule(name);
128 if (module instanceof AutomaticBean) {
129 final AutomaticBean bean = (AutomaticBean) module;
130 bean.contextualize(childContext);
131 bean.configure(childConf);
132 }
133 }
134 catch (final CheckstyleException ex) {
135 throw new CheckstyleException("cannot initialize module " + name
136 + " - " + ex.getMessage(), ex);
137 }
138 if (module instanceof AbstractCheck) {
139 final AbstractCheck check = (AbstractCheck) module;
140 check.init();
141 registerCheck(check);
142 }
143 else if (module instanceof TreeWalkerFilter) {
144 final TreeWalkerFilter filter = (TreeWalkerFilter) module;
145 filters.add(filter);
146 }
147 else {
148 throw new CheckstyleException(
149 "TreeWalker is not allowed as a parent of " + name
150 + " Please review 'Parent Module' section for this Check in web"
151 + " documentation if Check is standard.");
152 }
153 }
154
155 @Override
156 protected void processFiltered(File file, FileText fileText) throws CheckstyleException {
157
158 if (!ordinaryChecks.isEmpty() || !commentChecks.isEmpty()) {
159 final FileContents contents = getFileContents();
160 final DetailAST rootAST = JavaParser.parse(contents);
161 if (!ordinaryChecks.isEmpty()) {
162 walk(rootAST, contents, AstState.ORDINARY);
163 }
164 if (!commentChecks.isEmpty()) {
165 final DetailAST astWithComments = JavaParser.appendHiddenCommentNodes(rootAST);
166 walk(astWithComments, contents, AstState.WITH_COMMENTS);
167 }
168 if (filters.isEmpty()) {
169 addMessages(messages);
170 }
171 else {
172 final SortedSet<LocalizedMessage> filteredMessages =
173 getFilteredMessages(file.getAbsolutePath(), contents, rootAST);
174 addMessages(filteredMessages);
175 }
176 messages.clear();
177 }
178 }
179
180
181
182
183
184
185
186
187 private SortedSet<LocalizedMessage> getFilteredMessages(
188 String fileName, FileContents fileContents, DetailAST rootAST) {
189 final SortedSet<LocalizedMessage> result = new TreeSet<>(messages);
190 for (LocalizedMessage element : messages) {
191 final TreeWalkerAuditEvent event =
192 new TreeWalkerAuditEvent(fileContents, fileName, element, rootAST);
193 for (TreeWalkerFilter filter : filters) {
194 if (!filter.accept(event)) {
195 result.remove(element);
196 break;
197 }
198 }
199 }
200 return result;
201 }
202
203
204
205
206
207
208 private void registerCheck(AbstractCheck check)
209 throws CheckstyleException {
210 validateDefaultTokens(check);
211 final int[] tokens;
212 final Set<String> checkTokens = check.getTokenNames();
213 if (checkTokens.isEmpty()) {
214 tokens = check.getDefaultTokens();
215 }
216 else {
217 tokens = check.getRequiredTokens();
218
219
220 final int[] acceptableTokens = check.getAcceptableTokens();
221 Arrays.sort(acceptableTokens);
222 for (String token : checkTokens) {
223 final int tokenId = TokenUtil.getTokenId(token);
224 if (Arrays.binarySearch(acceptableTokens, tokenId) >= 0) {
225 registerCheck(token, check);
226 }
227 else {
228 final String message = String.format(Locale.ROOT, "Token \"%s\" was "
229 + "not found in Acceptable tokens list in check %s",
230 token, check.getClass().getName());
231 throw new CheckstyleException(message);
232 }
233 }
234 }
235 for (int element : tokens) {
236 registerCheck(element, check);
237 }
238 if (check.isCommentNodesRequired()) {
239 commentChecks.add(check);
240 }
241 else {
242 ordinaryChecks.add(check);
243 }
244 }
245
246
247
248
249
250
251
252 private void registerCheck(int tokenId, AbstractCheck check) throws CheckstyleException {
253 registerCheck(TokenUtil.getTokenName(tokenId), check);
254 }
255
256
257
258
259
260
261
262 private void registerCheck(String token, AbstractCheck check) throws CheckstyleException {
263 if (check.isCommentNodesRequired()) {
264 tokenToCommentChecks.computeIfAbsent(token, empty -> new HashSet<>()).add(check);
265 }
266 else if (TokenUtil.isCommentType(token)) {
267 final String message = String.format(Locale.ROOT, "Check '%s' waits for comment type "
268 + "token ('%s') and should override 'isCommentNodesRequired()' "
269 + "method to return 'true'", check.getClass().getName(), token);
270 throw new CheckstyleException(message);
271 }
272 else {
273 tokenToOrdinaryChecks.computeIfAbsent(token, empty -> new HashSet<>()).add(check);
274 }
275 }
276
277
278
279
280
281
282 private static void validateDefaultTokens(AbstractCheck check) throws CheckstyleException {
283 final int[] defaultTokens = check.getDefaultTokens();
284 Arrays.sort(defaultTokens);
285 for (final int token : check.getRequiredTokens()) {
286 if (Arrays.binarySearch(defaultTokens, token) < 0) {
287 final String message = String.format(Locale.ROOT, "Token \"%s\" from required "
288 + "tokens was not found in default tokens list in check %s",
289 token, check.getClass().getName());
290 throw new CheckstyleException(message);
291 }
292 }
293 }
294
295
296
297
298
299
300
301 private void walk(DetailAST ast, FileContents contents,
302 AstState astState) {
303 notifyBegin(ast, contents, astState);
304 processIter(ast, astState);
305 notifyEnd(ast, astState);
306 }
307
308
309
310
311
312
313
314 private void notifyBegin(DetailAST rootAST, FileContents contents,
315 AstState astState) {
316 final Set<AbstractCheck> checks;
317
318 if (astState == AstState.WITH_COMMENTS) {
319 checks = commentChecks;
320 }
321 else {
322 checks = ordinaryChecks;
323 }
324
325 for (AbstractCheck check : checks) {
326 check.setFileContents(contents);
327 check.clearMessages();
328 check.beginTree(rootAST);
329 }
330 }
331
332
333
334
335
336
337 private void notifyEnd(DetailAST rootAST, AstState astState) {
338 final Set<AbstractCheck> checks;
339
340 if (astState == AstState.WITH_COMMENTS) {
341 checks = commentChecks;
342 }
343 else {
344 checks = ordinaryChecks;
345 }
346
347 for (AbstractCheck check : checks) {
348 check.finishTree(rootAST);
349 messages.addAll(check.getMessages());
350 }
351 }
352
353
354
355
356
357
358 private void notifyVisit(DetailAST ast, AstState astState) {
359 final Collection<AbstractCheck> visitors = getListOfChecks(ast, astState);
360
361 if (visitors != null) {
362 for (AbstractCheck check : visitors) {
363 check.visitToken(ast);
364 }
365 }
366 }
367
368
369
370
371
372
373
374 private void notifyLeave(DetailAST ast, AstState astState) {
375 final Collection<AbstractCheck> visitors = getListOfChecks(ast, astState);
376
377 if (visitors != null) {
378 for (AbstractCheck check : visitors) {
379 check.leaveToken(ast);
380 }
381 }
382 }
383
384
385
386
387
388
389
390
391
392
393 private Collection<AbstractCheck> getListOfChecks(DetailAST ast, AstState astState) {
394 final Collection<AbstractCheck> visitors;
395 final String tokenType = TokenUtil.getTokenName(ast.getType());
396
397 if (astState == AstState.WITH_COMMENTS) {
398 visitors = tokenToCommentChecks.get(tokenType);
399 }
400 else {
401 visitors = tokenToOrdinaryChecks.get(tokenType);
402 }
403 return visitors;
404 }
405
406 @Override
407 public void destroy() {
408 ordinaryChecks.forEach(AbstractCheck::destroy);
409 commentChecks.forEach(AbstractCheck::destroy);
410 super.destroy();
411 }
412
413 @Override
414 public Set<String> getExternalResourceLocations() {
415 final Set<String> ordinaryChecksResources =
416 getExternalResourceLocationsOfChecks(ordinaryChecks);
417 final Set<String> commentChecksResources =
418 getExternalResourceLocationsOfChecks(commentChecks);
419 final Set<String> filtersResources =
420 getExternalResourceLocationsOfFilters();
421 final int resultListSize = commentChecksResources.size()
422 + ordinaryChecksResources.size()
423 + filtersResources.size();
424 final Set<String> resourceLocations = new HashSet<>(resultListSize);
425 resourceLocations.addAll(ordinaryChecksResources);
426 resourceLocations.addAll(commentChecksResources);
427 resourceLocations.addAll(filtersResources);
428 return resourceLocations;
429 }
430
431
432
433
434
435 private Set<String> getExternalResourceLocationsOfFilters() {
436 final Set<String> externalConfigurationResources = new HashSet<>();
437 filters.stream().filter(filter -> filter instanceof ExternalResourceHolder)
438 .forEach(filter -> {
439 final Set<String> checkExternalResources =
440 ((ExternalResourceHolder) filter).getExternalResourceLocations();
441 externalConfigurationResources.addAll(checkExternalResources);
442 });
443 return externalConfigurationResources;
444 }
445
446
447
448
449
450
451 private static Set<String> getExternalResourceLocationsOfChecks(Set<AbstractCheck> checks) {
452 final Set<String> externalConfigurationResources = new HashSet<>();
453 checks.stream().filter(check -> check instanceof ExternalResourceHolder).forEach(check -> {
454 final Set<String> checkExternalResources =
455 ((ExternalResourceHolder) check).getExternalResourceLocations();
456 externalConfigurationResources.addAll(checkExternalResources);
457 });
458 return externalConfigurationResources;
459 }
460
461
462
463
464
465
466
467 private void processIter(DetailAST root, AstState astState) {
468 DetailAST curNode = root;
469 while (curNode != null) {
470 notifyVisit(curNode, astState);
471 DetailAST toVisit = curNode.getFirstChild();
472 while (curNode != null && toVisit == null) {
473 notifyLeave(curNode, astState);
474 toVisit = curNode.getNextSibling();
475 curNode = curNode.getParent();
476 }
477 curNode = toVisit;
478 }
479 }
480
481
482
483
484
485 private enum AstState {
486
487
488
489
490 ORDINARY,
491
492
493
494
495 WITH_COMMENTS,
496
497 }
498
499 }