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