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 antlr.RecognitionException; |
22 | |
import antlr.TokenStreamException; |
23 | |
import antlr.TokenStreamRecognitionException; |
24 | |
|
25 | |
import com.google.common.collect.HashMultimap; |
26 | |
import com.google.common.collect.Multimap; |
27 | |
import com.google.common.collect.Sets; |
28 | |
import com.puppycrawl.tools.checkstyle.api.AbstractFileSetCheck; |
29 | |
import com.puppycrawl.tools.checkstyle.api.Check; |
30 | |
import com.puppycrawl.tools.checkstyle.api.CheckstyleException; |
31 | |
import com.puppycrawl.tools.checkstyle.api.Configuration; |
32 | |
import com.puppycrawl.tools.checkstyle.api.Context; |
33 | |
import com.puppycrawl.tools.checkstyle.api.DetailAST; |
34 | |
import com.puppycrawl.tools.checkstyle.api.FileContents; |
35 | |
import com.puppycrawl.tools.checkstyle.api.FileText; |
36 | |
import com.puppycrawl.tools.checkstyle.api.LocalizedMessage; |
37 | |
import com.puppycrawl.tools.checkstyle.api.TokenTypes; |
38 | |
import com.puppycrawl.tools.checkstyle.api.Utils; |
39 | |
import com.puppycrawl.tools.checkstyle.grammars.GeneratedJavaLexer; |
40 | |
import com.puppycrawl.tools.checkstyle.grammars.GeneratedJavaRecognizer; |
41 | |
import java.io.File; |
42 | |
import java.io.Reader; |
43 | |
import java.io.StringReader; |
44 | |
import java.util.Arrays; |
45 | |
import java.util.Collection; |
46 | |
import java.util.List; |
47 | |
import java.util.Set; |
48 | |
import org.apache.commons.logging.Log; |
49 | |
import org.apache.commons.logging.LogFactory; |
50 | |
|
51 | |
|
52 | |
|
53 | |
|
54 | |
|
55 | |
|
56 | |
|
57 | |
|
58 | |
public final class TreeWalker |
59 | |
extends AbstractFileSetCheck |
60 | |
{ |
61 | |
|
62 | |
private static final int DEFAULT_TAB_WIDTH = 8; |
63 | |
|
64 | |
|
65 | 546 | private final Multimap<String, Check> mTokenToChecks = |
66 | |
HashMultimap.create(); |
67 | |
|
68 | 546 | private final Set<Check> mAllChecks = Sets.newHashSet(); |
69 | |
|
70 | 546 | private int mTabWidth = DEFAULT_TAB_WIDTH; |
71 | |
|
72 | 546 | private PropertyCacheFile mCache = new PropertyCacheFile(null, null); |
73 | |
|
74 | |
|
75 | |
private ClassLoader mClassLoader; |
76 | |
|
77 | |
|
78 | |
private Context mChildContext; |
79 | |
|
80 | |
|
81 | |
private ModuleFactory mModuleFactory; |
82 | |
|
83 | |
|
84 | |
|
85 | |
|
86 | |
private final boolean mRecursive; |
87 | |
|
88 | |
|
89 | 1 | private static final Log LOG = |
90 | |
LogFactory.getLog("com.puppycrawl.tools.checkstyle.TreeWalker"); |
91 | |
|
92 | |
|
93 | |
|
94 | |
|
95 | |
public TreeWalker() |
96 | 546 | { |
97 | 546 | setFileExtensions(new String[]{"java"}); |
98 | |
|
99 | |
|
100 | |
|
101 | 546 | final String recursive = |
102 | |
System.getProperty("checkstyle.use.recursive.algorithm", "false"); |
103 | 546 | mRecursive = "true".equals(recursive); |
104 | 546 | if (mRecursive) { |
105 | 0 | LOG.debug("TreeWalker uses recursive algorithm"); |
106 | |
} |
107 | |
else { |
108 | 546 | LOG.debug("TreeWalker uses iterative algorithm"); |
109 | |
} |
110 | 546 | } |
111 | |
|
112 | |
|
113 | |
public void setTabWidth(int aTabWidth) |
114 | |
{ |
115 | 0 | mTabWidth = aTabWidth; |
116 | 0 | } |
117 | |
|
118 | |
|
119 | |
public void setCacheFile(String aFileName) |
120 | |
{ |
121 | 0 | final Configuration configuration = getConfiguration(); |
122 | 0 | mCache = new PropertyCacheFile(configuration, aFileName); |
123 | 0 | } |
124 | |
|
125 | |
|
126 | |
public void setClassLoader(ClassLoader aClassLoader) |
127 | |
{ |
128 | 546 | mClassLoader = aClassLoader; |
129 | 546 | } |
130 | |
|
131 | |
|
132 | |
|
133 | |
|
134 | |
|
135 | |
public void setModuleFactory(ModuleFactory aModuleFactory) |
136 | |
{ |
137 | 546 | mModuleFactory = aModuleFactory; |
138 | 546 | } |
139 | |
|
140 | |
@Override |
141 | |
public void finishLocalSetup() |
142 | |
{ |
143 | 546 | final DefaultContext checkContext = new DefaultContext(); |
144 | 546 | checkContext.add("classLoader", mClassLoader); |
145 | 546 | checkContext.add("messages", getMessageCollector()); |
146 | 546 | checkContext.add("severity", getSeverity()); |
147 | |
|
148 | |
|
149 | |
|
150 | 546 | checkContext.add("tabWidth", String.valueOf(mTabWidth)); |
151 | |
|
152 | 546 | mChildContext = checkContext; |
153 | 546 | } |
154 | |
|
155 | |
@Override |
156 | |
public void setupChild(Configuration aChildConf) |
157 | |
throws CheckstyleException |
158 | |
{ |
159 | |
|
160 | 605 | final String name = aChildConf.getName(); |
161 | 605 | final Object module = mModuleFactory.createModule(name); |
162 | 605 | if (!(module instanceof Check)) { |
163 | 0 | throw new CheckstyleException( |
164 | |
"TreeWalker is not allowed as a parent of " + name); |
165 | |
} |
166 | 605 | final Check c = (Check) module; |
167 | 605 | c.contextualize(mChildContext); |
168 | 605 | c.configure(aChildConf); |
169 | 602 | c.init(); |
170 | |
|
171 | 602 | registerCheck(c); |
172 | 602 | } |
173 | |
|
174 | |
@Override |
175 | |
protected void processFiltered(File aFile, List<String> aLines) |
176 | |
{ |
177 | |
|
178 | 543 | final String fileName = aFile.getPath(); |
179 | 543 | final long timestamp = aFile.lastModified(); |
180 | 543 | if (mCache.alreadyChecked(fileName, timestamp)) { |
181 | 0 | return; |
182 | |
} |
183 | |
|
184 | |
try { |
185 | 543 | final FileText text = FileText.fromLines(aFile, aLines); |
186 | 543 | final FileContents contents = new FileContents(text); |
187 | 543 | final DetailAST rootAST = TreeWalker.parse(contents); |
188 | 542 | walk(rootAST, contents); |
189 | |
} |
190 | 0 | catch (final RecognitionException re) { |
191 | 0 | Utils.getExceptionLogger() |
192 | |
.debug("RecognitionException occured.", re); |
193 | 0 | getMessageCollector().add( |
194 | |
new LocalizedMessage( |
195 | |
re.getLine(), |
196 | |
re.getColumn(), |
197 | |
Defn.CHECKSTYLE_BUNDLE, |
198 | |
"general.exception", |
199 | |
new String[] {re.getMessage()}, |
200 | |
getId(), |
201 | |
this.getClass(), null)); |
202 | |
} |
203 | 1 | catch (final TokenStreamRecognitionException tre) { |
204 | 1 | Utils.getExceptionLogger() |
205 | |
.debug("TokenStreamRecognitionException occured.", tre); |
206 | 1 | final RecognitionException re = tre.recog; |
207 | 1 | if (re != null) { |
208 | 1 | getMessageCollector().add( |
209 | |
new LocalizedMessage( |
210 | |
re.getLine(), |
211 | |
re.getColumn(), |
212 | |
Defn.CHECKSTYLE_BUNDLE, |
213 | |
"general.exception", |
214 | |
new String[] {re.getMessage()}, |
215 | |
getId(), |
216 | |
this.getClass(), null)); |
217 | |
} |
218 | |
else { |
219 | 0 | getMessageCollector().add( |
220 | |
new LocalizedMessage( |
221 | |
0, |
222 | |
Defn.CHECKSTYLE_BUNDLE, |
223 | |
"general.exception", |
224 | |
new String[] |
225 | |
{"TokenStreamRecognitionException occured."}, |
226 | |
getId(), |
227 | |
this.getClass(), null)); |
228 | |
} |
229 | |
} |
230 | 0 | catch (final TokenStreamException te) { |
231 | 0 | Utils.getExceptionLogger() |
232 | |
.debug("TokenStreamException occured.", te); |
233 | 0 | getMessageCollector().add( |
234 | |
new LocalizedMessage( |
235 | |
0, |
236 | |
Defn.CHECKSTYLE_BUNDLE, |
237 | |
"general.exception", |
238 | |
new String[] {te.getMessage()}, |
239 | |
getId(), |
240 | |
this.getClass(), null)); |
241 | |
} |
242 | 0 | catch (final Throwable err) { |
243 | 0 | Utils.getExceptionLogger().debug("Throwable occured.", err); |
244 | 0 | getMessageCollector().add( |
245 | |
new LocalizedMessage( |
246 | |
0, |
247 | |
Defn.CHECKSTYLE_BUNDLE, |
248 | |
"general.exception", |
249 | |
new String[] {"" + err}, |
250 | |
getId(), |
251 | |
this.getClass(), null)); |
252 | 543 | } |
253 | |
|
254 | 543 | if (getMessageCollector().size() == 0) { |
255 | 120 | mCache.checkedOk(fileName, timestamp); |
256 | |
} |
257 | 543 | } |
258 | |
|
259 | |
|
260 | |
|
261 | |
|
262 | |
|
263 | |
|
264 | |
private void registerCheck(Check aCheck) |
265 | |
throws CheckstyleException |
266 | |
{ |
267 | |
final int[] tokens; |
268 | 602 | final Set<String> checkTokens = aCheck.getTokenNames(); |
269 | 602 | if (!checkTokens.isEmpty()) { |
270 | 44 | tokens = aCheck.getRequiredTokens(); |
271 | |
|
272 | |
|
273 | 44 | final int acceptableTokens[] = aCheck.getAcceptableTokens(); |
274 | 44 | Arrays.sort(acceptableTokens); |
275 | 44 | for (String token : checkTokens) { |
276 | |
try { |
277 | 61 | final int tokenId = TokenTypes.getTokenId(token); |
278 | 61 | if (Arrays.binarySearch(acceptableTokens, tokenId) >= 0) { |
279 | 61 | registerCheck(token, aCheck); |
280 | |
} |
281 | |
|
282 | |
} |
283 | 0 | catch (final IllegalArgumentException ex) { |
284 | 0 | throw new CheckstyleException("illegal token \"" |
285 | |
+ token + "\" in check " + aCheck, ex); |
286 | 61 | } |
287 | |
} |
288 | 44 | } |
289 | |
else { |
290 | 558 | tokens = aCheck.getDefaultTokens(); |
291 | |
} |
292 | 4066 | for (int element : tokens) { |
293 | 3464 | registerCheck(element, aCheck); |
294 | |
} |
295 | 602 | mAllChecks.add(aCheck); |
296 | 602 | } |
297 | |
|
298 | |
|
299 | |
|
300 | |
|
301 | |
|
302 | |
|
303 | |
private void registerCheck(int aTokenID, Check aCheck) |
304 | |
{ |
305 | 3464 | registerCheck(TokenTypes.getTokenName(aTokenID), aCheck); |
306 | 3464 | } |
307 | |
|
308 | |
|
309 | |
|
310 | |
|
311 | |
|
312 | |
|
313 | |
private void registerCheck(String aToken, Check aCheck) |
314 | |
{ |
315 | 3525 | mTokenToChecks.put(aToken, aCheck); |
316 | 3525 | } |
317 | |
|
318 | |
|
319 | |
|
320 | |
|
321 | |
|
322 | |
|
323 | |
private void walk(DetailAST aAST, FileContents aContents) |
324 | |
{ |
325 | 542 | getMessageCollector().reset(); |
326 | 542 | notifyBegin(aAST, aContents); |
327 | |
|
328 | |
|
329 | 542 | if (aAST != null) { |
330 | 542 | if (useRecursiveAlgorithm()) { |
331 | 0 | processRec(aAST); |
332 | |
} |
333 | |
else { |
334 | 542 | processIter(aAST); |
335 | |
} |
336 | |
} |
337 | |
|
338 | 542 | notifyEnd(aAST); |
339 | 542 | } |
340 | |
|
341 | |
|
342 | |
|
343 | |
|
344 | |
|
345 | |
|
346 | |
|
347 | |
private void notifyBegin(DetailAST aRootAST, FileContents aContents) |
348 | |
{ |
349 | 542 | for (Check ch : mAllChecks) { |
350 | 601 | ch.setFileContents(aContents); |
351 | 601 | ch.beginTree(aRootAST); |
352 | |
} |
353 | 542 | } |
354 | |
|
355 | |
|
356 | |
|
357 | |
|
358 | |
|
359 | |
private void notifyEnd(DetailAST aRootAST) |
360 | |
{ |
361 | 542 | for (Check ch : mAllChecks) { |
362 | 601 | ch.finishTree(aRootAST); |
363 | |
} |
364 | 542 | } |
365 | |
|
366 | |
|
367 | |
|
368 | |
|
369 | |
|
370 | |
|
371 | |
private void processRec(DetailAST aAST) |
372 | |
{ |
373 | 0 | if (aAST == null) { |
374 | 0 | return; |
375 | |
} |
376 | |
|
377 | 0 | notifyVisit(aAST); |
378 | |
|
379 | 0 | final DetailAST child = aAST.getFirstChild(); |
380 | 0 | if (child != null) { |
381 | 0 | processRec(child); |
382 | |
} |
383 | |
|
384 | 0 | notifyLeave(aAST); |
385 | |
|
386 | 0 | final DetailAST sibling = aAST.getNextSibling(); |
387 | 0 | if (sibling != null) { |
388 | 0 | processRec(sibling); |
389 | |
} |
390 | 0 | } |
391 | |
|
392 | |
|
393 | |
|
394 | |
|
395 | |
|
396 | |
private void notifyVisit(DetailAST aAST) |
397 | |
{ |
398 | 185354 | final Collection<Check> visitors = |
399 | |
mTokenToChecks.get(TokenTypes.getTokenName(aAST.getType())); |
400 | 185354 | for (Check c : visitors) { |
401 | 12026 | c.visitToken(aAST); |
402 | |
} |
403 | 185354 | } |
404 | |
|
405 | |
|
406 | |
|
407 | |
|
408 | |
|
409 | |
|
410 | |
|
411 | |
private void notifyLeave(DetailAST aAST) |
412 | |
{ |
413 | 185354 | final Collection<Check> visitors = |
414 | |
mTokenToChecks.get(TokenTypes.getTokenName(aAST.getType())); |
415 | 185354 | for (Check ch : visitors) { |
416 | 12026 | ch.leaveToken(aAST); |
417 | |
} |
418 | 185354 | } |
419 | |
|
420 | |
|
421 | |
|
422 | |
|
423 | |
|
424 | |
|
425 | |
|
426 | |
|
427 | |
|
428 | |
|
429 | |
|
430 | |
|
431 | |
public static DetailAST parse(FileContents aContents) |
432 | |
throws RecognitionException, TokenStreamException |
433 | |
{ |
434 | 749 | final String fullText = aContents.getText().getFullText().toString(); |
435 | 749 | final Reader sr = new StringReader(fullText); |
436 | 749 | final GeneratedJavaLexer lexer = new GeneratedJavaLexer(sr); |
437 | 749 | lexer.setFilename(aContents.getFilename()); |
438 | 749 | lexer.setCommentListener(aContents); |
439 | 749 | lexer.setTreatAssertAsKeyword(true); |
440 | 749 | lexer.setTreatEnumAsKeyword(true); |
441 | |
|
442 | 749 | final GeneratedJavaRecognizer parser = |
443 | |
new GeneratedJavaRecognizer(lexer); |
444 | 749 | parser.setFilename(aContents.getFilename()); |
445 | 749 | parser.setASTNodeClass(DetailAST.class.getName()); |
446 | 749 | parser.compilationUnit(); |
447 | |
|
448 | 748 | return (DetailAST) parser.getAST(); |
449 | |
} |
450 | |
|
451 | |
@Override |
452 | |
public void destroy() |
453 | |
{ |
454 | 543 | for (Check c : mAllChecks) { |
455 | 602 | c.destroy(); |
456 | |
} |
457 | 543 | mCache.destroy(); |
458 | 543 | super.destroy(); |
459 | 543 | } |
460 | |
|
461 | |
|
462 | |
|
463 | |
|
464 | |
|
465 | |
private boolean useRecursiveAlgorithm() |
466 | |
{ |
467 | 542 | return mRecursive; |
468 | |
} |
469 | |
|
470 | |
|
471 | |
|
472 | |
|
473 | |
|
474 | |
|
475 | |
private void processIter(DetailAST aRoot) |
476 | |
{ |
477 | 542 | DetailAST curNode = aRoot; |
478 | 185896 | while (curNode != null) { |
479 | 185354 | notifyVisit(curNode); |
480 | 185354 | DetailAST toVisit = curNode.getFirstChild(); |
481 | 370708 | while ((curNode != null) && (toVisit == null)) { |
482 | 185354 | notifyLeave(curNode); |
483 | 185354 | toVisit = curNode.getNextSibling(); |
484 | 185354 | if (toVisit == null) { |
485 | 76732 | curNode = curNode.getParent(); |
486 | |
} |
487 | |
} |
488 | 185354 | curNode = toVisit; |
489 | 185354 | } |
490 | 542 | } |
491 | |
} |