KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > puppycrawl > tools > checkstyle > TreeWalker


1 ////////////////////////////////////////////////////////////////////////////////
2
// checkstyle: Checks Java source code for adherence to a set of rules.
3
// Copyright (C) 2001-2005 Oliver Burn
4
//
5
// This library is free software; you can redistribute it and/or
6
// modify it under the terms of the GNU Lesser General Public
7
// License as published by the Free Software Foundation; either
8
// version 2.1 of the License, or (at your option) any later version.
9
//
10
// This library is distributed in the hope that it will be useful,
11
// but WITHOUT ANY WARRANTY; without even the implied warranty of
12
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13
// Lesser General Public License for more details.
14
//
15
// You should have received a copy of the GNU Lesser General Public
16
// License along with this library; if not, write to the Free Software
17
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18
////////////////////////////////////////////////////////////////////////////////
19
package com.puppycrawl.tools.checkstyle;
20
21 import java.io.File JavaDoc;
22 import java.io.FileNotFoundException JavaDoc;
23 import java.io.IOException JavaDoc;
24 import java.io.Reader JavaDoc;
25 import java.util.ArrayList JavaDoc;
26 import java.util.Arrays JavaDoc;
27 import java.util.HashMap JavaDoc;
28 import java.util.HashSet JavaDoc;
29 import java.util.Iterator JavaDoc;
30 import java.util.Map JavaDoc;
31 import java.util.Set JavaDoc;
32
33 import antlr.RecognitionException;
34 import antlr.TokenStreamException;
35 import antlr.TokenStreamRecognitionException;
36 import antlr.TokenStream;
37
38 import com.puppycrawl.tools.checkstyle.api.AbstractFileSetCheck;
39 import com.puppycrawl.tools.checkstyle.api.Check;
40 import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
41 import com.puppycrawl.tools.checkstyle.api.Configuration;
42 import com.puppycrawl.tools.checkstyle.api.Context;
43 import com.puppycrawl.tools.checkstyle.api.DetailAST;
44 import com.puppycrawl.tools.checkstyle.api.FileContents;
45 import com.puppycrawl.tools.checkstyle.api.LocalizedMessage;
46 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
47 import com.puppycrawl.tools.checkstyle.api.Utils;
48 import com.puppycrawl.tools.checkstyle.grammars.GeneratedJavaRecognizer;
49 import com.puppycrawl.tools.checkstyle.grammars.GeneratedJavaLexer;
50
51 import org.apache.commons.logging.Log;
52 import org.apache.commons.logging.LogFactory;
53
54 /**
55  * Responsible for walking an abstract syntax tree and notifying interested
56  * checks at each each node.
57  *
58  * @author Oliver Burn
59  * @version 1.0
60  */

61 public final class TreeWalker
62     extends AbstractFileSetCheck
63 {
64     /**
65      * Overrides ANTLR error reporting so we completely control
66      * checkstyle's output during parsing. This is important because
67      * we try parsing with several grammers (with/without support for
68      * <code>assert</code>). We must not write any error messages when
69      * parsing fails because with the next grammar it might succeed
70      * and the user will be confused.
71      */

72     private static final class SilentJavaRecognizer
73         extends GeneratedJavaRecognizer
74     {
75         /**
76          * Creates a new <code>SilentJavaRecognizer</code> instance.
77          *
78          * @param aLexer the tokenstream the recognizer operates on.
79          */

80         public SilentJavaRecognizer(TokenStream aLexer)
81         {
82             super(aLexer);
83         }
84
85         /**
86          * Parser error-reporting function, does nothing.
87          * @param aRex the exception to be reported
88          */

89         public void reportError(RecognitionException aRex)
90         {
91         }
92
93         /**
94          * Parser error-reporting function, does nothing.
95          * @param aMsg the error message
96          */

97         public void reportError(String JavaDoc aMsg)
98         {
99         }
100
101         /**
102          * Parser warning-reporting function, does nothing.
103          * @param aMsg the error message
104          */

105         public void reportWarning(String JavaDoc aMsg)
106         {
107         }
108     }
109
110     /** default distance between tab stops */
111     private static final int DEFAULT_TAB_WIDTH = 8;
112
113     /** maps from token name to checks */
114     private final Map JavaDoc mTokenToChecks = new HashMap JavaDoc();
115     /** all the registered checks */
116     private final Set JavaDoc mAllChecks = new HashSet JavaDoc();
117     /** the distance between tab stops */
118     private int mTabWidth = DEFAULT_TAB_WIDTH;
119     /** cache file **/
120     private PropertyCacheFile mCache = new PropertyCacheFile(null, null);
121
122     /** class loader to resolve classes with. **/
123     private ClassLoader JavaDoc mClassLoader;
124
125     /** context of child components */
126     private Context mChildContext;
127
128     /** a factory for creating submodules (i.e. the Checks) */
129     private ModuleFactory mModuleFactory;
130
131     /** controls whether we should use recursive or iterative
132      * algorithm for tree processing.
133      */

134     private final boolean mRecursive;
135
136     /** logger for debug purpose */
137     private static final Log LOG =
138         LogFactory.getLog("com.puppycrawl.tools.checkstyle.TreeWalker");
139
140     /**
141      * Creates a new <code>TreeWalker</code> instance.
142      */

143     public TreeWalker()
144     {
145         setFileExtensions(new String JavaDoc[]{"java"});
146         // Tree walker can use two possible algorithms for
147
// tree processing (iterative and recursive.
148
// Recursive is default for now.
149
final String JavaDoc recursive =
150             System.getProperty("checkstyle.use.recursive.algorithm", "false");
151         mRecursive = "true".equals(recursive);
152         if (mRecursive) {
153             LOG.debug("TreeWalker uses recursive algorithm");
154         }
155         else {
156             LOG.debug("TreeWalker uses iterative algorithm");
157         }
158     }
159
160     /** @param aTabWidth the distance between tab stops */
161     public void setTabWidth(int aTabWidth)
162     {
163         mTabWidth = aTabWidth;
164     }
165
166     /** @param aFileName the cache file */
167     public void setCacheFile(String JavaDoc aFileName)
168     {
169         final Configuration configuration = getConfiguration();
170         mCache = new PropertyCacheFile(configuration, aFileName);
171     }
172
173     /** @param aClassLoader class loader to resolve classes with. */
174     public void setClassLoader(ClassLoader JavaDoc aClassLoader)
175     {
176         mClassLoader = aClassLoader;
177     }
178
179     /**
180      * Sets the module factory for creating child modules (Checks).
181      * @param aModuleFactory the factory
182      */

183     public void setModuleFactory(ModuleFactory aModuleFactory)
184     {
185         mModuleFactory = aModuleFactory;
186     }
187
188     /** @see com.puppycrawl.tools.checkstyle.api.Configurable */
189     public void finishLocalSetup()
190     {
191         final DefaultContext checkContext = new DefaultContext();
192         checkContext.add("classLoader", mClassLoader);
193         checkContext.add("messages", getMessageCollector());
194         checkContext.add("severity", getSeverity());
195         // TODO: hmmm.. this looks less than elegant
196
// we have just parsed the string,
197
// now we're recreating it only to parse it again a few moments later
198
checkContext.add("tabWidth", String.valueOf(mTabWidth));
199
200         mChildContext = checkContext;
201     }
202
203     /**
204      * Instantiates, configures and registers a Check that is specified
205      * in the provided configuration.
206      * {@inheritDoc}
207      */

208     public void setupChild(Configuration aChildConf)
209         throws CheckstyleException
210     {
211         // TODO: improve the error handing
212
final String JavaDoc name = aChildConf.getName();
213         final Object JavaDoc module = mModuleFactory.createModule(name);
214         if (!(module instanceof Check)) {
215             throw new CheckstyleException(
216                 "TreeWalker is not allowed as a parent of " + name);
217         }
218         final Check c = (Check) module;
219         c.contextualize(mChildContext);
220         c.configure(aChildConf);
221         c.init();
222
223         registerCheck(c);
224     }
225
226     /**
227      * Processes a specified file and reports all errors found.
228      * @param aFile the file to process
229      **/

230     private void process(File JavaDoc aFile)
231     {
232         // check if already checked and passed the file
233
final String JavaDoc fileName = aFile.getPath();
234         final long timestamp = aFile.lastModified();
235         if (mCache.alreadyChecked(fileName, timestamp)) {
236             return;
237         }
238
239         try {
240             getMessageDispatcher().fireFileStarted(fileName);
241             final String JavaDoc[] lines = Utils.getLines(fileName, getCharset());
242             final FileContents contents = new FileContents(fileName, lines);
243             final DetailAST rootAST = TreeWalker.parse(contents);
244             walk(rootAST, contents);
245         }
246         catch (final FileNotFoundException JavaDoc fnfe) {
247             Utils.getExceptionLogger()
248                 .debug("FileNotFoundException occured.", fnfe);
249             getMessageCollector().add(
250                 new LocalizedMessage(
251                     0,
252                     Defn.CHECKSTYLE_BUNDLE,
253                     "general.fileNotFound",
254                     null,
255                     getId(),
256                     this.getClass()));
257         }
258         catch (final IOException JavaDoc ioe) {
259             Utils.getExceptionLogger().debug("IOException occured.", ioe);
260             getMessageCollector().add(
261                 new LocalizedMessage(
262                     0,
263                     Defn.CHECKSTYLE_BUNDLE,
264                     "general.exception",
265                     new String JavaDoc[] {ioe.getMessage()},
266                     getId(),
267                     this.getClass()));
268         }
269         catch (final RecognitionException re) {
270             Utils.getExceptionLogger()
271                 .debug("RecognitionException occured.", re);
272             getMessageCollector().add(
273                 new LocalizedMessage(
274                     re.getLine(),
275                     re.getColumn(),
276                     Defn.CHECKSTYLE_BUNDLE,
277                     "general.exception",
278                     new String JavaDoc[] {re.getMessage()},
279                     getId(),
280                     this.getClass()));
281         }
282         catch (final TokenStreamRecognitionException tre) {
283             Utils.getExceptionLogger()
284                 .debug("TokenStreamRecognitionException occured.", tre);
285             final RecognitionException re = tre.recog;
286             if (re != null) {
287                 getMessageCollector().add(
288                     new LocalizedMessage(
289                         re.getLine(),
290                         re.getColumn(),
291                         Defn.CHECKSTYLE_BUNDLE,
292                         "general.exception",
293                         new String JavaDoc[] {re.getMessage()},
294                         getId(),
295                         this.getClass()));
296             }
297             else {
298                 getMessageCollector().add(
299                     new LocalizedMessage(
300                         0,
301                         Defn.CHECKSTYLE_BUNDLE,
302                         "general.exception",
303                         new String JavaDoc[]
304                         {"TokenStreamRecognitionException occured."},
305                         getId(),
306                         this.getClass()));
307             }
308         }
309         catch (final TokenStreamException te) {
310             Utils.getExceptionLogger()
311                 .debug("TokenStreamException occured.", te);
312             getMessageCollector().add(
313                 new LocalizedMessage(
314                     0,
315                     Defn.CHECKSTYLE_BUNDLE,
316                     "general.exception",
317                     new String JavaDoc[] {te.getMessage()},
318                     getId(),
319                     this.getClass()));
320         }
321         catch (final Throwable JavaDoc err) {
322             Utils.getExceptionLogger().debug("Throwable occured.", err);
323             getMessageCollector().add(
324                 new LocalizedMessage(
325                     0,
326                     Defn.CHECKSTYLE_BUNDLE,
327                     "general.exception",
328                     new String JavaDoc[] {"" + err},
329                     getId(),
330                     this.getClass()));
331         }
332
333         if (getMessageCollector().size() == 0) {
334             mCache.checkedOk(fileName, timestamp);
335         }
336         else {
337             fireErrors(fileName);
338         }
339
340         getMessageDispatcher().fireFileFinished(fileName);
341     }
342
343     /**
344      * Register a check for a given configuration.
345      * @param aCheck the check to register
346      * @throws CheckstyleException if an error occurs
347      */

348     private void registerCheck(Check aCheck)
349         throws CheckstyleException
350     {
351         int[] tokens = new int[] {}; //safety initialization
352
final Set JavaDoc checkTokens = aCheck.getTokenNames();
353         if (!checkTokens.isEmpty()) {
354             tokens = aCheck.getRequiredTokens();
355
356             //register configured tokens
357
final int acceptableTokens[] = aCheck.getAcceptableTokens();
358             Arrays.sort(acceptableTokens);
359             final Iterator JavaDoc it = checkTokens.iterator();
360             while (it.hasNext()) {
361                 final String JavaDoc token = (String JavaDoc) it.next();
362                 try {
363                     final int tokenId = TokenTypes.getTokenId(token);
364                     if (Arrays.binarySearch(acceptableTokens, tokenId) >= 0) {
365                         registerCheck(token, aCheck);
366                     }
367                     // TODO: else log warning
368
}
369                 catch (final IllegalArgumentException JavaDoc ex) {
370                     throw new CheckstyleException("illegal token \""
371                         + token + "\" in check " + aCheck, ex);
372                 }
373             }
374         }
375         else {
376             tokens = aCheck.getDefaultTokens();
377         }
378         for (int i = 0; i < tokens.length; i++) {
379             registerCheck(tokens[i], aCheck);
380         }
381         mAllChecks.add(aCheck);
382     }
383
384     /**
385      * Register a check for a specified token id.
386      * @param aTokenID the id of the token
387      * @param aCheck the check to register
388      */

389     private void registerCheck(int aTokenID, Check aCheck)
390     {
391         registerCheck(TokenTypes.getTokenName(aTokenID), aCheck);
392     }
393
394     /**
395      * Register a check for a specified token name
396      * @param aToken the name of the token
397      * @param aCheck the check to register
398      */

399     private void registerCheck(String JavaDoc aToken, Check aCheck)
400     {
401         ArrayList JavaDoc visitors = (ArrayList JavaDoc) mTokenToChecks.get(aToken);
402         if (visitors == null) {
403             visitors = new ArrayList JavaDoc();
404             mTokenToChecks.put(aToken, visitors);
405         }
406
407         visitors.add(aCheck);
408     }
409
410     /**
411      * Initiates the walk of an AST.
412      * @param aAST the root AST
413      * @param aContents the contents of the file the AST was generated from
414      */

415     private void walk(DetailAST aAST, FileContents aContents)
416     {
417         getMessageCollector().reset();
418         notifyBegin(aAST, aContents);
419
420         // empty files are not flagged by javac, will yield aAST == null
421
if (aAST != null) {
422             if (useRecursiveAlgorithm()) {
423                 processRec(aAST);
424             }
425             else {
426                 processIter(aAST);
427             }
428         }
429
430         notifyEnd(aAST);
431     }
432
433
434     /**
435      * Notify interested checks that about to begin walking a tree.
436      * @param aRootAST the root of the tree
437      * @param aContents the contents of the file the AST was generated from
438      */

439     private void notifyBegin(DetailAST aRootAST, FileContents aContents)
440     {
441         final Iterator JavaDoc it = mAllChecks.iterator();
442         while (it.hasNext()) {
443             final Check check = (Check) it.next();
444             check.setFileContents(aContents);
445             check.beginTree(aRootAST);
446         }
447     }
448
449     /**
450      * Notify checks that finished walking a tree.
451      * @param aRootAST the root of the tree
452      */

453     private void notifyEnd(DetailAST aRootAST)
454     {
455         final Iterator JavaDoc it = mAllChecks.iterator();
456         while (it.hasNext()) {
457             final Check check = (Check) it.next();
458             check.finishTree(aRootAST);
459         }
460     }
461
462     /**
463      * Recursively processes a node calling interested checks at each node.
464      * Uses recursive algorithm.
465      * @param aAST the node to start from
466      */

467     private void processRec(DetailAST aAST)
468     {
469         if (aAST == null) {
470             return;
471         }
472
473         notifyVisit(aAST);
474
475         final DetailAST child = (DetailAST) aAST.getFirstChild();
476         if (child != null) {
477             processRec(child);
478         }
479
480         notifyLeave(aAST);
481
482         final DetailAST sibling = (DetailAST) aAST.getNextSibling();
483         if (sibling != null) {
484             processRec(sibling);
485         }
486     }
487
488     /**
489      * Notify interested checks that visiting a node.
490      * @param aAST the node to notify for
491      */

492     private void notifyVisit(DetailAST aAST)
493     {
494         final ArrayList JavaDoc visitors =
495             (ArrayList JavaDoc) mTokenToChecks.get(
496                 TokenTypes.getTokenName(aAST.getType()));
497         if (visitors != null) {
498             for (int i = 0; i < visitors.size(); i++) {
499                 final Check check = (Check) visitors.get(i);
500                 check.visitToken(aAST);
501             }
502         }
503     }
504
505     /**
506      * Notify interested checks that leaving a node.
507      * @param aAST the node to notify for
508      */

509     private void notifyLeave(DetailAST aAST)
510     {
511         final ArrayList JavaDoc visitors =
512             (ArrayList JavaDoc) mTokenToChecks.get(
513                 TokenTypes.getTokenName(aAST.getType()));
514         if (visitors != null) {
515             for (int i = 0; i < visitors.size(); i++) {
516                 final Check check = (Check) visitors.get(i);
517                 check.leaveToken(aAST);
518             }
519         }
520     }
521
522     /**
523      * Static helper method to parses a Java source file.
524      * @param aContents contains the contents of the file
525      * @throws TokenStreamException if lexing failed
526      * @throws RecognitionException if parsing failed
527      * @return the root of the AST
528      */

529     public static DetailAST parse(FileContents aContents)
530         throws RecognitionException, TokenStreamException
531     {
532         DetailAST rootAST = null;
533
534         try {
535             rootAST = parse(aContents, true, true, true);
536         }
537         catch (final RecognitionException exception) {
538             try {
539                 rootAST = parse(aContents, true, true, false);
540             }
541             catch (final RecognitionException exception2) {
542                 rootAST = parse(aContents, false, false, false);
543             }
544         }
545         return rootAST;
546     }
547
548     /**
549      * Static helper method to parses a Java source file with a given
550      * lexer class and parser class.
551      * @param aContents contains the contents of the file
552      * @param aSilentlyConsumeErrors flag to output errors to stdout or not
553      * @param aTreatAssertAsKeyword flag to treat 'assert' as a keyowrd
554      * @param aTreatEnumAsKeyword flag to treat 'enum' as a keyowrd
555      * @throws TokenStreamException if lexing failed
556      * @throws RecognitionException if parsing failed
557      * @return the root of the AST
558      */

559     private static DetailAST parse(
560         FileContents aContents,
561         boolean aSilentlyConsumeErrors,
562         boolean aTreatAssertAsKeyword,
563         boolean aTreatEnumAsKeyword)
564         throws RecognitionException, TokenStreamException
565     {
566         final Reader JavaDoc sar = new StringArrayReader(aContents.getLines());
567         final GeneratedJavaLexer lexer = new GeneratedJavaLexer(sar);
568         lexer.setFilename(aContents.getFilename());
569         lexer.setCommentListener(aContents);
570         lexer.setTreatAssertAsKeyword(aTreatAssertAsKeyword);
571         lexer.setTreatEnumAsKeyword(aTreatEnumAsKeyword);
572
573         final GeneratedJavaRecognizer parser =
574             aSilentlyConsumeErrors
575                 ? new SilentJavaRecognizer(lexer)
576                 : new GeneratedJavaRecognizer(lexer);
577         parser.setFilename(aContents.getFilename());
578         parser.setASTNodeClass(DetailAST.class.getName());
579         parser.compilationUnit();
580
581         return (DetailAST) parser.getAST();
582     }
583
584     /** {@inheritDoc} */
585     public void process(File JavaDoc[] aFiles)
586     {
587         final File JavaDoc[] javaFiles = filter(aFiles);
588
589         for (int i = 0; i < javaFiles.length; i++) {
590             process(javaFiles[i]);
591         }
592     }
593
594     /**
595      * @see com.puppycrawl.tools.checkstyle.api.FileSetCheck
596      */

597     public void destroy()
598     {
599         for (final Iterator JavaDoc it = mAllChecks.iterator(); it.hasNext();) {
600             final Check c = (Check) it.next();
601             c.destroy();
602         }
603         mCache.destroy();
604         super.destroy();
605     }
606
607     /**
608      * @return true if we should use recursive algorithm
609      * for tree processing, false for iterative one.
610      */

611     private boolean useRecursiveAlgorithm()
612     {
613         return mRecursive;
614     }
615
616     /**
617      * Processes a node calling interested checks at each node.
618      * Uses iterative algorithm.
619      * @param aRoot the root of tree for process
620      */

621     private void processIter(DetailAST aRoot)
622     {
623         DetailAST curNode = aRoot;
624         while (curNode != null) {
625             notifyVisit(curNode);
626             DetailAST toVisit = (DetailAST) curNode.getFirstChild();
627             while ((curNode != null) && (toVisit == null)) {
628                 notifyLeave(curNode);
629                 toVisit = (DetailAST) curNode.getNextSibling();
630                 if (toVisit == null) {
631                     curNode = curNode.getParent();
632                 }
633             }
634             curNode = toVisit;
635         }
636     }
637 }
638
Popular Tags