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