1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package com.puppycrawl.tools.checkstyle;
20
21 import java.io.File;
22 import java.io.Reader;
23 import java.io.StringReader;
24 import java.util.AbstractMap.SimpleEntry;
25 import java.util.Arrays;
26 import java.util.Collection;
27 import java.util.List;
28 import java.util.Map.Entry;
29 import java.util.Set;
30
31 import org.apache.commons.logging.Log;
32 import org.apache.commons.logging.LogFactory;
33
34 import antlr.CommonHiddenStreamToken;
35 import antlr.RecognitionException;
36 import antlr.Token;
37 import antlr.TokenStreamException;
38 import antlr.TokenStreamHiddenTokenFilter;
39 import antlr.TokenStreamRecognitionException;
40
41 import com.google.common.collect.HashMultimap;
42 import com.google.common.collect.Multimap;
43 import com.google.common.collect.Sets;
44 import com.puppycrawl.tools.checkstyle.api.AbstractFileSetCheck;
45 import com.puppycrawl.tools.checkstyle.api.Check;
46 import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
47 import com.puppycrawl.tools.checkstyle.api.Configuration;
48 import com.puppycrawl.tools.checkstyle.api.Context;
49 import com.puppycrawl.tools.checkstyle.api.DetailAST;
50 import com.puppycrawl.tools.checkstyle.api.FileContents;
51 import com.puppycrawl.tools.checkstyle.api.FileText;
52 import com.puppycrawl.tools.checkstyle.api.LocalizedMessage;
53 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
54 import com.puppycrawl.tools.checkstyle.api.Utils;
55 import com.puppycrawl.tools.checkstyle.grammars.GeneratedJavaLexer;
56 import com.puppycrawl.tools.checkstyle.grammars.GeneratedJavaRecognizer;
57
58 import static com.puppycrawl.tools.checkstyle.Utils.fileExtensionMatches;
59
60
61
62
63
64
65
66 public final class TreeWalker
67 extends AbstractFileSetCheck
68 {
69
70
71
72
73 private static enum AstState {
74
75
76
77 ORDINARY,
78
79
80
81
82 WITH_COMMENTS
83 }
84
85
86 private static final int DEFAULT_TAB_WIDTH = 8;
87
88
89 private final Multimap<String, Check> tokenToOrdinaryChecks =
90 HashMultimap.create();
91
92
93 private final Multimap<String, Check> tokenToCommentChecks =
94 HashMultimap.create();
95
96
97 private final Set<Check> ordinaryChecks = Sets.newHashSet();
98
99
100 private final Set<Check> commentChecks = Sets.newHashSet();
101
102
103 private int tabWidth = DEFAULT_TAB_WIDTH;
104
105
106 private PropertyCacheFile cache = new PropertyCacheFile(null, null);
107
108
109 private ClassLoader classLoader;
110
111
112 private Context childContext;
113
114
115 private ModuleFactory moduleFactory;
116
117
118 private static final Log LOG =
119 LogFactory.getLog("com.puppycrawl.tools.checkstyle.TreeWalker");
120
121
122 private String[] fileExtensions;
123
124
125
126
127 public TreeWalker()
128 {
129 setFileExtensions(new String[]{"java"});
130 }
131
132
133 public void setTabWidth(int tabWidth)
134 {
135 this.tabWidth = tabWidth;
136 }
137
138
139 public void setCacheFile(String fileName)
140 {
141 final Configuration configuration = getConfiguration();
142 cache = new PropertyCacheFile(configuration, fileName);
143 }
144
145
146 public void setClassLoader(ClassLoader classLoader)
147 {
148 this.classLoader = classLoader;
149 }
150
151
152
153
154
155 public void setModuleFactory(ModuleFactory moduleFactory)
156 {
157 this.moduleFactory = moduleFactory;
158 }
159
160 @Override
161 public void finishLocalSetup()
162 {
163 final DefaultContext checkContext = new DefaultContext();
164 checkContext.add("classLoader", classLoader);
165 checkContext.add("messages", getMessageCollector());
166 checkContext.add("severity", getSeverity());
167
168
169
170 checkContext.add("tabWidth", String.valueOf(tabWidth));
171
172 childContext = checkContext;
173 }
174
175 @Override
176 public void setupChild(Configuration childConf)
177 throws CheckstyleException
178 {
179
180 final String name = childConf.getName();
181 final Object module = moduleFactory.createModule(name);
182 if (!(module instanceof Check)) {
183 throw new CheckstyleException(
184 "TreeWalker is not allowed as a parent of " + name);
185 }
186 final Check c = (Check) module;
187 c.contextualize(childContext);
188 c.configure(childConf);
189 c.init();
190
191 registerCheck(c);
192 }
193
194 @Override
195 protected void processFiltered(File file, List<String> lines)
196 {
197
198 final String fileName = file.getPath();
199 final long timestamp = file.lastModified();
200 if (cache.alreadyChecked(fileName, timestamp)
201 || !fileExtensionMatches(file, fileExtensions))
202 {
203 return;
204 }
205
206 final String msg = "%s occurred during the analysis of file %s .";
207
208 try {
209 final FileText text = FileText.fromLines(file, lines);
210 final FileContents contents = new FileContents(text);
211 final DetailAST rootAST = TreeWalker.parse(contents);
212
213 getMessageCollector().reset();
214
215 walk(rootAST, contents, AstState.ORDINARY);
216
217 final DetailAST astWithComments = appendHiddenCommentNodes(rootAST);
218
219 walk(astWithComments, contents, AstState.WITH_COMMENTS);
220 }
221 catch (final RecognitionException re) {
222 final String exceptionMsg = String.format(msg, "RecognitionException", fileName);
223 Utils.getExceptionLogger().error(exceptionMsg);
224 getMessageCollector().add(
225 new LocalizedMessage(
226 re.getLine(),
227 re.getColumn(),
228 Defn.CHECKSTYLE_BUNDLE,
229 "general.exception",
230 new String[] {re.getMessage()},
231 getId(),
232 this.getClass(), null));
233 }
234 catch (final TokenStreamRecognitionException tre) {
235 final String exceptionMsg = String.format(msg, "TokenStreamRecognitionException",
236 fileName);
237 Utils.getExceptionLogger().error(exceptionMsg);
238 final RecognitionException re = tre.recog;
239 if (re != null) {
240 getMessageCollector().add(
241 new LocalizedMessage(
242 re.getLine(),
243 re.getColumn(),
244 Defn.CHECKSTYLE_BUNDLE,
245 "general.exception",
246 new String[] {re.getMessage()},
247 getId(),
248 this.getClass(), null));
249 }
250 else {
251 getMessageCollector().add(
252 new LocalizedMessage(
253 0,
254 Defn.CHECKSTYLE_BUNDLE,
255 "general.exception",
256 new String[]
257 {"TokenStreamRecognitionException occured."},
258 getId(),
259 this.getClass(), null));
260 }
261 }
262 catch (final TokenStreamException te) {
263 final String exceptionMsg = String.format(msg,
264 "TokenStreamException", fileName);
265 Utils.getExceptionLogger().error(exceptionMsg);
266 getMessageCollector().add(
267 new LocalizedMessage(
268 0,
269 Defn.CHECKSTYLE_BUNDLE,
270 "general.exception",
271 new String[] {te.getMessage()},
272 getId(),
273 this.getClass(), null));
274 }
275 catch (final Throwable err) {
276 final String exceptionMsg = String.format(msg, "Exception", fileName);
277 Utils.getExceptionLogger().error(exceptionMsg);
278 err.printStackTrace();
279 getMessageCollector().add(
280 new LocalizedMessage(
281 0,
282 Defn.CHECKSTYLE_BUNDLE,
283 "general.exception",
284 new String[] {"" + err},
285 getId(),
286 this.getClass(), null));
287 }
288
289 if (getMessageCollector().size() == 0) {
290 cache.checkedOk(fileName, timestamp);
291 }
292 }
293
294
295
296
297
298
299 private void registerCheck(Check check)
300 throws CheckstyleException
301 {
302 final int[] tokens;
303 final Set<String> checkTokens = check.getTokenNames();
304 if (!checkTokens.isEmpty()) {
305 tokens = check.getRequiredTokens();
306
307
308 final int[] acceptableTokens = check.getAcceptableTokens();
309 Arrays.sort(acceptableTokens);
310 for (String token : checkTokens) {
311 final int tokenId = TokenTypes.getTokenId(token);
312 if (Arrays.binarySearch(acceptableTokens, tokenId) >= 0) {
313 registerCheck(token, check);
314 }
315 else {
316 throw new CheckstyleException("Token \""
317 + token + "\" was not found in Acceptable tokens list"
318 + " in check " + check);
319 }
320 }
321 }
322 else {
323 tokens = check.getDefaultTokens();
324 }
325 for (int element : tokens) {
326 registerCheck(element, check);
327 }
328 if (check.isCommentNodesRequired()) {
329 commentChecks.add(check);
330 }
331 else {
332 ordinaryChecks.add(check);
333 }
334 }
335
336
337
338
339
340
341 private void registerCheck(int tokenID, Check check)
342 {
343 registerCheck(TokenTypes.getTokenName(tokenID), check);
344 }
345
346
347
348
349
350
351 private void registerCheck(String token, Check check)
352 {
353 if (check.isCommentNodesRequired()) {
354 tokenToCommentChecks.put(token, check);
355 }
356 else if (TokenTypes.isCommentType(token)) {
357 LOG.warn("Check '"
358 + check.getClass().getName()
359 + "' waits for comment type token ('"
360 + token
361 + "') and should override 'isCommentNodesRequred()'"
362 + " method to return 'true'");
363 }
364 else {
365 tokenToOrdinaryChecks.put(token, check);
366 }
367 }
368
369
370
371
372
373
374
375 private void walk(DetailAST ast, FileContents contents
376 , AstState astState)
377 {
378 notifyBegin(ast, contents, astState);
379
380
381 if (ast != null) {
382 processIter(ast, astState);
383 }
384
385 notifyEnd(ast, astState);
386 }
387
388
389
390
391
392
393
394 private void notifyBegin(DetailAST rootAST, FileContents contents
395 , AstState astState)
396 {
397 Set<Check> checks;
398
399 if (astState == AstState.WITH_COMMENTS) {
400 checks = commentChecks;
401 }
402 else {
403 checks = ordinaryChecks;
404 }
405
406 for (Check ch : checks) {
407 ch.setFileContents(contents);
408 ch.beginTree(rootAST);
409 }
410 }
411
412
413
414
415
416
417 private void notifyEnd(DetailAST rootAST, AstState astState)
418 {
419 Set<Check> checks;
420
421 if (astState == AstState.WITH_COMMENTS) {
422 checks = commentChecks;
423 }
424 else {
425 checks = ordinaryChecks;
426 }
427
428 for (Check ch : checks) {
429 ch.finishTree(rootAST);
430 }
431 }
432
433
434
435
436
437
438 private void notifyVisit(DetailAST ast, AstState astState)
439 {
440 Collection<Check> visitors;
441 final String tokenType = TokenTypes.getTokenName(ast.getType());
442
443 if (astState == AstState.WITH_COMMENTS) {
444 if (!tokenToCommentChecks.containsKey(tokenType)) {
445 return;
446 }
447 visitors = tokenToCommentChecks.get(tokenType);
448 }
449 else {
450 if (!tokenToOrdinaryChecks.containsKey(tokenType)) {
451 return;
452 }
453 visitors = tokenToOrdinaryChecks.get(tokenType);
454 }
455
456 for (Check c : visitors) {
457 c.visitToken(ast);
458 }
459 }
460
461
462
463
464
465
466
467 private void notifyLeave(DetailAST ast, AstState astState)
468 {
469 Collection<Check> visitors;
470 final String tokenType = TokenTypes.getTokenName(ast.getType());
471
472 if (astState == AstState.WITH_COMMENTS) {
473 if (!tokenToCommentChecks.containsKey(tokenType)) {
474 return;
475 }
476 visitors = tokenToCommentChecks.get(tokenType);
477 }
478 else {
479 if (!tokenToOrdinaryChecks.containsKey(tokenType)) {
480 return;
481 }
482 visitors = tokenToOrdinaryChecks.get(tokenType);
483 }
484
485 for (Check ch : visitors) {
486 ch.leaveToken(ast);
487 }
488 }
489
490
491
492
493
494
495
496
497
498
499
500
501 public static DetailAST parse(FileContents contents)
502 throws RecognitionException, TokenStreamException
503 {
504 final String fullText = contents.getText().getFullText().toString();
505 final Reader sr = new StringReader(fullText);
506 final GeneratedJavaLexer lexer = new GeneratedJavaLexer(sr);
507 lexer.setFilename(contents.getFilename());
508 lexer.setCommentListener(contents);
509 lexer.setTreatAssertAsKeyword(true);
510 lexer.setTreatEnumAsKeyword(true);
511 lexer.setTokenObjectClass("antlr.CommonHiddenStreamToken");
512
513 final TokenStreamHiddenTokenFilter filter =
514 new TokenStreamHiddenTokenFilter(lexer);
515 filter.hide(TokenTypes.SINGLE_LINE_COMMENT);
516 filter.hide(TokenTypes.BLOCK_COMMENT_BEGIN);
517
518 final GeneratedJavaRecognizer parser =
519 new GeneratedJavaRecognizer(filter);
520 parser.setFilename(contents.getFilename());
521 parser.setASTNodeClass(DetailAST.class.getName());
522 parser.compilationUnit();
523
524 return (DetailAST) parser.getAST();
525 }
526
527 @Override
528 public void destroy()
529 {
530 for (Check c : ordinaryChecks) {
531 c.destroy();
532 }
533 for (Check c : commentChecks) {
534 c.destroy();
535 }
536 cache.destroy();
537 super.destroy();
538 }
539
540
541
542
543
544
545
546 private void processIter(DetailAST root, AstState astState)
547 {
548 DetailAST curNode = root;
549 while (curNode != null) {
550 notifyVisit(curNode, astState);
551 DetailAST toVisit = curNode.getFirstChild();
552 while (curNode != null && toVisit == null) {
553 notifyLeave(curNode, astState);
554 toVisit = curNode.getNextSibling();
555 if (toVisit == null) {
556 curNode = curNode.getParent();
557 }
558 }
559 curNode = toVisit;
560 }
561 }
562
563
564
565
566
567
568
569
570
571 private static DetailAST appendHiddenCommentNodes(DetailAST root)
572 {
573 DetailAST result = root;
574 DetailAST curNode = root;
575 DetailAST lastNode = root;
576
577 while (curNode != null) {
578 if (isPositionGreater(curNode, lastNode)) {
579 lastNode = curNode;
580 }
581
582 CommonHiddenStreamToken tokenBefore = curNode.getHiddenBefore();
583 DetailAST currentSibling = curNode;
584 while (tokenBefore != null) {
585 final DetailAST newCommentNode =
586 createCommentAstFromToken(tokenBefore);
587
588 currentSibling.addPreviousSibling(newCommentNode);
589
590 if (currentSibling == result) {
591 result = newCommentNode;
592 }
593
594 currentSibling = newCommentNode;
595 tokenBefore = tokenBefore.getHiddenBefore();
596 }
597
598 DetailAST toVisit = curNode.getFirstChild();
599 while (curNode != null && toVisit == null) {
600 toVisit = curNode.getNextSibling();
601 if (toVisit == null) {
602 curNode = curNode.getParent();
603 }
604 }
605 curNode = toVisit;
606 }
607 if (lastNode != null) {
608 CommonHiddenStreamToken tokenAfter = lastNode.getHiddenAfter();
609 DetailAST currentSibling = lastNode;
610 while (tokenAfter != null) {
611 final DetailAST newCommentNode =
612 createCommentAstFromToken(tokenAfter);
613
614 currentSibling.addNextSibling(newCommentNode);
615
616 currentSibling = newCommentNode;
617 tokenAfter = tokenAfter.getHiddenAfter();
618 }
619 }
620 return result;
621 }
622
623
624
625
626
627
628
629
630
631
632
633 private static boolean isPositionGreater(DetailAST ast1, DetailAST ast2)
634 {
635 if (ast1.getLineNo() > ast2.getLineNo()) {
636 return true;
637 }
638 else if (ast1.getLineNo() < ast2.getLineNo()) {
639 return false;
640 }
641 else {
642 if (ast1.getColumnNo() > ast2.getColumnNo()) {
643 return true;
644 }
645 }
646 return false;
647 }
648
649
650
651
652
653
654
655
656 private static DetailAST createCommentAstFromToken(Token token)
657 {
658 switch (token.getType()) {
659 case TokenTypes.SINGLE_LINE_COMMENT:
660 return createSlCommentNode(token);
661 case TokenTypes.BLOCK_COMMENT_BEGIN:
662 return createBlockCommentNode(token);
663 default:
664 throw new IllegalArgumentException("Unknown comment type");
665 }
666 }
667
668
669
670
671
672
673
674 private static DetailAST createSlCommentNode(Token token)
675 {
676 final DetailAST slComment = new DetailAST();
677 slComment.setType(TokenTypes.SINGLE_LINE_COMMENT);
678 slComment.setText("//");
679
680
681 slComment.setColumnNo(token.getColumn() - 1);
682 slComment.setLineNo(token.getLine());
683
684 final DetailAST slCommentContent = new DetailAST();
685 slCommentContent.initialize(token);
686 slCommentContent.setType(TokenTypes.COMMENT_CONTENT);
687
688
689
690 slCommentContent.setColumnNo(token.getColumn() - 1 + 2);
691 slCommentContent.setLineNo(token.getLine());
692 slCommentContent.setText(token.getText());
693
694 slComment.addChild(slCommentContent);
695 return slComment;
696 }
697
698
699
700
701
702
703
704 private static DetailAST createBlockCommentNode(Token token)
705 {
706 final DetailAST blockComment = new DetailAST();
707 blockComment.initialize(TokenTypes.BLOCK_COMMENT_BEGIN, "/*");
708
709
710 blockComment.setColumnNo(token.getColumn() - 1);
711 blockComment.setLineNo(token.getLine());
712
713 final DetailAST blockCommentContent = new DetailAST();
714 blockCommentContent.initialize(token);
715 blockCommentContent.setType(TokenTypes.COMMENT_CONTENT);
716
717
718
719 blockCommentContent.setColumnNo(token.getColumn() - 1 + 2);
720 blockCommentContent.setLineNo(token.getLine());
721 blockCommentContent.setText(token.getText());
722
723 final DetailAST blockCommentClose = new DetailAST();
724 blockCommentClose.initialize(TokenTypes.BLOCK_COMMENT_END, "*/");
725
726 final Entry<Integer, Integer> linesColumns = countLinesColumns(
727 token.getText(), token.getLine(), token.getColumn());
728 blockCommentClose.setLineNo(linesColumns.getKey());
729 blockCommentClose.setColumnNo(linesColumns.getValue());
730
731 blockComment.addChild(blockCommentContent);
732 blockComment.addChild(blockCommentClose);
733 return blockComment;
734 }
735
736
737
738
739
740
741
742
743
744
745
746
747 private static Entry<Integer, Integer> countLinesColumns(
748 String text, int initialLinesCnt, int initialColumnsCnt)
749 {
750 int lines = initialLinesCnt;
751 int columns = initialColumnsCnt;
752 for (char c : text.toCharArray()) {
753 switch (c) {
754 case '\n':
755 lines++;
756 columns = 0;
757 break;
758 default:
759 columns++;
760 }
761 }
762 return new SimpleEntry<>(lines, columns);
763 }
764
765 }