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 try {
313 final int tokenId = TokenTypes.getTokenId(token);
314 if (Arrays.binarySearch(acceptableTokens, tokenId) >= 0) {
315 registerCheck(token, check);
316 }
317
318 }
319 catch (final IllegalArgumentException ex) {
320 throw new CheckstyleException("illegal token \""
321 + token + "\" in check " + check, ex);
322 }
323 }
324 }
325 else {
326 tokens = check.getDefaultTokens();
327 }
328 for (int element : tokens) {
329 registerCheck(element, check);
330 }
331 if (check.isCommentNodesRequired()) {
332 commentChecks.add(check);
333 }
334 else {
335 ordinaryChecks.add(check);
336 }
337 }
338
339
340
341
342
343
344 private void registerCheck(int tokenID, Check check)
345 {
346 registerCheck(TokenTypes.getTokenName(tokenID), check);
347 }
348
349
350
351
352
353
354 private void registerCheck(String token, Check check)
355 {
356 if (check.isCommentNodesRequired()) {
357 tokenToCommentChecks.put(token, check);
358 }
359 else if (TokenTypes.isCommentType(token)) {
360 LOG.warn("Check '"
361 + check.getClass().getName()
362 + "' waits for comment type token ('"
363 + token
364 + "') and should override 'isCommentNodesRequred()'"
365 + " method to return 'true'");
366 }
367 else {
368 tokenToOrdinaryChecks.put(token, check);
369 }
370 }
371
372
373
374
375
376
377
378 private void walk(DetailAST ast, FileContents contents
379 , AstState astState)
380 {
381 notifyBegin(ast, contents, astState);
382
383
384 if (ast != null) {
385 processIter(ast, astState);
386 }
387
388 notifyEnd(ast, astState);
389 }
390
391
392
393
394
395
396
397 private void notifyBegin(DetailAST rootAST, FileContents contents
398 , AstState astState)
399 {
400 Set<Check> checks;
401
402 if (astState == AstState.WITH_COMMENTS) {
403 checks = commentChecks;
404 }
405 else {
406 checks = ordinaryChecks;
407 }
408
409 for (Check ch : checks) {
410 ch.setFileContents(contents);
411 ch.beginTree(rootAST);
412 }
413 }
414
415
416
417
418
419
420 private void notifyEnd(DetailAST rootAST, AstState astState)
421 {
422 Set<Check> checks;
423
424 if (astState == AstState.WITH_COMMENTS) {
425 checks = commentChecks;
426 }
427 else {
428 checks = ordinaryChecks;
429 }
430
431 for (Check ch : checks) {
432 ch.finishTree(rootAST);
433 }
434 }
435
436
437
438
439
440
441 private void notifyVisit(DetailAST ast, AstState astState)
442 {
443 Collection<Check> visitors;
444 final String tokenType = TokenTypes.getTokenName(ast.getType());
445
446 if (astState == AstState.WITH_COMMENTS) {
447 if (!tokenToCommentChecks.containsKey(tokenType)) {
448 return;
449 }
450 visitors = tokenToCommentChecks.get(tokenType);
451 }
452 else {
453 if (!tokenToOrdinaryChecks.containsKey(tokenType)) {
454 return;
455 }
456 visitors = tokenToOrdinaryChecks.get(tokenType);
457 }
458
459 for (Check c : visitors) {
460 c.visitToken(ast);
461 }
462 }
463
464
465
466
467
468
469
470 private void notifyLeave(DetailAST ast, AstState astState)
471 {
472 Collection<Check> visitors;
473 final String tokenType = TokenTypes.getTokenName(ast.getType());
474
475 if (astState == AstState.WITH_COMMENTS) {
476 if (!tokenToCommentChecks.containsKey(tokenType)) {
477 return;
478 }
479 visitors = tokenToCommentChecks.get(tokenType);
480 }
481 else {
482 if (!tokenToOrdinaryChecks.containsKey(tokenType)) {
483 return;
484 }
485 visitors = tokenToOrdinaryChecks.get(tokenType);
486 }
487
488 for (Check ch : visitors) {
489 ch.leaveToken(ast);
490 }
491 }
492
493
494
495
496
497
498
499
500
501
502
503
504 public static DetailAST parse(FileContents contents)
505 throws RecognitionException, TokenStreamException
506 {
507 final String fullText = contents.getText().getFullText().toString();
508 final Reader sr = new StringReader(fullText);
509 final GeneratedJavaLexer lexer = new GeneratedJavaLexer(sr);
510 lexer.setFilename(contents.getFilename());
511 lexer.setCommentListener(contents);
512 lexer.setTreatAssertAsKeyword(true);
513 lexer.setTreatEnumAsKeyword(true);
514 lexer.setTokenObjectClass("antlr.CommonHiddenStreamToken");
515
516 final TokenStreamHiddenTokenFilter filter =
517 new TokenStreamHiddenTokenFilter(lexer);
518 filter.hide(TokenTypes.SINGLE_LINE_COMMENT);
519 filter.hide(TokenTypes.BLOCK_COMMENT_BEGIN);
520
521 final GeneratedJavaRecognizer parser =
522 new GeneratedJavaRecognizer(filter);
523 parser.setFilename(contents.getFilename());
524 parser.setASTNodeClass(DetailAST.class.getName());
525 parser.compilationUnit();
526
527 return (DetailAST) parser.getAST();
528 }
529
530 @Override
531 public void destroy()
532 {
533 for (Check c : ordinaryChecks) {
534 c.destroy();
535 }
536 for (Check c : commentChecks) {
537 c.destroy();
538 }
539 cache.destroy();
540 super.destroy();
541 }
542
543
544
545
546
547
548
549 private void processIter(DetailAST root, AstState astState)
550 {
551 DetailAST curNode = root;
552 while (curNode != null) {
553 notifyVisit(curNode, astState);
554 DetailAST toVisit = curNode.getFirstChild();
555 while ((curNode != null) && (toVisit == null)) {
556 notifyLeave(curNode, astState);
557 toVisit = curNode.getNextSibling();
558 if (toVisit == null) {
559 curNode = curNode.getParent();
560 }
561 }
562 curNode = toVisit;
563 }
564 }
565
566
567
568
569
570
571
572
573
574 private static DetailAST appendHiddenCommentNodes(DetailAST root)
575 {
576 DetailAST result = root;
577 DetailAST curNode = root;
578 DetailAST lastNode = root;
579
580 while (curNode != null) {
581 if (isPositionGreater(curNode, lastNode)) {
582 lastNode = curNode;
583 }
584
585 CommonHiddenStreamToken tokenBefore = curNode.getHiddenBefore();
586 DetailAST currentSibling = curNode;
587 while (tokenBefore != null) {
588 final DetailAST newCommentNode =
589 createCommentAstFromToken(tokenBefore);
590
591 currentSibling.addPreviousSibling(newCommentNode);
592
593 if (currentSibling == result) {
594 result = newCommentNode;
595 }
596
597 currentSibling = newCommentNode;
598 tokenBefore = tokenBefore.getHiddenBefore();
599 }
600
601 DetailAST toVisit = curNode.getFirstChild();
602 while ((curNode != null) && (toVisit == null)) {
603 toVisit = curNode.getNextSibling();
604 if (toVisit == null) {
605 curNode = curNode.getParent();
606 }
607 }
608 curNode = toVisit;
609 }
610 if (lastNode != null) {
611 CommonHiddenStreamToken tokenAfter = lastNode.getHiddenAfter();
612 DetailAST currentSibling = lastNode;
613 while (tokenAfter != null) {
614 final DetailAST newCommentNode =
615 createCommentAstFromToken(tokenAfter);
616
617 currentSibling.addNextSibling(newCommentNode);
618
619 currentSibling = newCommentNode;
620 tokenAfter = tokenAfter.getHiddenAfter();
621 }
622 }
623 return result;
624 }
625
626
627
628
629
630
631
632
633
634
635
636 private static boolean isPositionGreater(DetailAST ast1, DetailAST ast2)
637 {
638 if (ast1.getLineNo() > ast2.getLineNo()) {
639 return true;
640 }
641 else if (ast1.getLineNo() < ast2.getLineNo()) {
642 return false;
643 }
644 else {
645 if (ast1.getColumnNo() > ast2.getColumnNo()) {
646 return true;
647 }
648 }
649 return false;
650 }
651
652
653
654
655
656
657
658
659 private static DetailAST createCommentAstFromToken(Token token)
660 {
661 switch (token.getType()) {
662 case TokenTypes.SINGLE_LINE_COMMENT:
663 return createSlCommentNode(token);
664 case TokenTypes.BLOCK_COMMENT_BEGIN:
665 return createBlockCommentNode(token);
666 default:
667 throw new IllegalArgumentException("Unknown comment type");
668 }
669 }
670
671
672
673
674
675
676
677 private static DetailAST createSlCommentNode(Token token)
678 {
679 final DetailAST slComment = new DetailAST();
680 slComment.setType(TokenTypes.SINGLE_LINE_COMMENT);
681 slComment.setText("//");
682
683
684 slComment.setColumnNo(token.getColumn() - 1);
685 slComment.setLineNo(token.getLine());
686
687 final DetailAST slCommentContent = new DetailAST();
688 slCommentContent.initialize(token);
689 slCommentContent.setType(TokenTypes.COMMENT_CONTENT);
690
691
692
693 slCommentContent.setColumnNo(token.getColumn() - 1 + 2);
694 slCommentContent.setLineNo(token.getLine());
695 slCommentContent.setText(token.getText());
696
697 slComment.addChild(slCommentContent);
698 return slComment;
699 }
700
701
702
703
704
705
706
707 private static DetailAST createBlockCommentNode(Token token)
708 {
709 final DetailAST blockComment = new DetailAST();
710 blockComment.initialize(TokenTypes.BLOCK_COMMENT_BEGIN, "/*");
711
712
713 blockComment.setColumnNo(token.getColumn() - 1);
714 blockComment.setLineNo(token.getLine());
715
716 final DetailAST blockCommentContent = new DetailAST();
717 blockCommentContent.initialize(token);
718 blockCommentContent.setType(TokenTypes.COMMENT_CONTENT);
719
720
721
722 blockCommentContent.setColumnNo(token.getColumn() - 1 + 2);
723 blockCommentContent.setLineNo(token.getLine());
724 blockCommentContent.setText(token.getText());
725
726 final DetailAST blockCommentClose = new DetailAST();
727 blockCommentClose.initialize(TokenTypes.BLOCK_COMMENT_END, "*/");
728
729 final Entry<Integer, Integer> linesColumns = countLinesColumns(
730 token.getText(), token.getLine(), token.getColumn());
731 blockCommentClose.setLineNo(linesColumns.getKey());
732 blockCommentClose.setColumnNo(linesColumns.getValue());
733
734 blockComment.addChild(blockCommentContent);
735 blockComment.addChild(blockCommentClose);
736 return blockComment;
737 }
738
739
740
741
742
743
744
745
746
747
748
749
750 private static Entry<Integer, Integer> countLinesColumns(
751 String text, int initialLinesCnt, int initialColumnsCnt)
752 {
753 int lines = initialLinesCnt;
754 int columns = initialColumnsCnt;
755 for (char c : text.toCharArray()) {
756 switch (c) {
757 case '\n':
758 lines++;
759 columns = 0;
760 break;
761 default:
762 columns++;
763 }
764 }
765 return new SimpleEntry<Integer, Integer>(lines, columns);
766 }
767
768 }