Coverage Report - com.puppycrawl.tools.checkstyle.TreeWalker
 
Classes in this File Line Coverage Branch Coverage Complexity
TreeWalker
78%
120/152
71%
33/46
2.524
 
 1  
 ////////////////////////////////////////////////////////////////////////////////
 2  
 // checkstyle: Checks Java source code for adherence to a set of rules.
 3  
 // Copyright (C) 2001-2014  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 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  
  * Responsible for walking an abstract syntax tree and notifying interested
 53  
  * checks at each each node.
 54  
  *
 55  
  * @author Oliver Burn
 56  
  * @version 1.0
 57  
  */
 58  
 public final class TreeWalker
 59  
     extends AbstractFileSetCheck
 60  
 {
 61  
     /** default distance between tab stops */
 62  
     private static final int DEFAULT_TAB_WIDTH = 8;
 63  
 
 64  
     /** maps from token name to checks */
 65  546
     private final Multimap<String, Check> mTokenToChecks =
 66  
         HashMultimap.create();
 67  
     /** all the registered checks */
 68  546
     private final Set<Check> mAllChecks = Sets.newHashSet();
 69  
     /** the distance between tab stops */
 70  546
     private int mTabWidth = DEFAULT_TAB_WIDTH;
 71  
     /** cache file **/
 72  546
     private PropertyCacheFile mCache = new PropertyCacheFile(null, null);
 73  
 
 74  
     /** class loader to resolve classes with. **/
 75  
     private ClassLoader mClassLoader;
 76  
 
 77  
     /** context of child components */
 78  
     private Context mChildContext;
 79  
 
 80  
     /** a factory for creating submodules (i.e. the Checks) */
 81  
     private ModuleFactory mModuleFactory;
 82  
 
 83  
     /** controls whether we should use recursive or iterative
 84  
      * algorithm for tree processing.
 85  
      */
 86  
     private final boolean mRecursive;
 87  
 
 88  
     /** logger for debug purpose */
 89  1
     private static final Log LOG =
 90  
         LogFactory.getLog("com.puppycrawl.tools.checkstyle.TreeWalker");
 91  
 
 92  
     /**
 93  
      * Creates a new <code>TreeWalker</code> instance.
 94  
      */
 95  
     public TreeWalker()
 96  546
     {
 97  546
         setFileExtensions(new String[]{"java"});
 98  
         // Tree walker can use two possible algorithms for
 99  
         // tree processing (iterative and recursive.
 100  
         // Recursive is default for now.
 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  
     /** @param aTabWidth the distance between tab stops */
 113  
     public void setTabWidth(int aTabWidth)
 114  
     {
 115  0
         mTabWidth = aTabWidth;
 116  0
     }
 117  
 
 118  
     /** @param aFileName the cache file */
 119  
     public void setCacheFile(String aFileName)
 120  
     {
 121  0
         final Configuration configuration = getConfiguration();
 122  0
         mCache = new PropertyCacheFile(configuration, aFileName);
 123  0
     }
 124  
 
 125  
     /** @param aClassLoader class loader to resolve classes with. */
 126  
     public void setClassLoader(ClassLoader aClassLoader)
 127  
     {
 128  546
         mClassLoader = aClassLoader;
 129  546
     }
 130  
 
 131  
     /**
 132  
      * Sets the module factory for creating child modules (Checks).
 133  
      * @param aModuleFactory the factory
 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  
         // TODO: hmmm.. this looks less than elegant
 148  
         // we have just parsed the string,
 149  
         // now we're recreating it only to parse it again a few moments later
 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  
         // TODO: improve the error handing
 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  
         // check if already checked and passed the file
 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  
      * Register a check for a given configuration.
 261  
      * @param aCheck the check to register
 262  
      * @throws CheckstyleException if an error occurs
 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  
             //register configured tokens
 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  
                     // TODO: else log warning
 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  
      * Register a check for a specified token id.
 300  
      * @param aTokenID the id of the token
 301  
      * @param aCheck the check to register
 302  
      */
 303  
     private void registerCheck(int aTokenID, Check aCheck)
 304  
     {
 305  3464
         registerCheck(TokenTypes.getTokenName(aTokenID), aCheck);
 306  3464
     }
 307  
 
 308  
     /**
 309  
      * Register a check for a specified token name
 310  
      * @param aToken the name of the token
 311  
      * @param aCheck the check to register
 312  
      */
 313  
     private void registerCheck(String aToken, Check aCheck)
 314  
     {
 315  3525
         mTokenToChecks.put(aToken, aCheck);
 316  3525
     }
 317  
 
 318  
     /**
 319  
      * Initiates the walk of an AST.
 320  
      * @param aAST the root AST
 321  
      * @param aContents the contents of the file the AST was generated from
 322  
      */
 323  
     private void walk(DetailAST aAST, FileContents aContents)
 324  
     {
 325  542
         getMessageCollector().reset();
 326  542
         notifyBegin(aAST, aContents);
 327  
 
 328  
         // empty files are not flagged by javac, will yield aAST == null
 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  
      * Notify interested checks that about to begin walking a tree.
 344  
      * @param aRootAST the root of the tree
 345  
      * @param aContents the contents of the file the AST was generated from
 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  
      * Notify checks that finished walking a tree.
 357  
      * @param aRootAST the root of the tree
 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  
      * Recursively processes a node calling interested checks at each node.
 368  
      * Uses recursive algorithm.
 369  
      * @param aAST the node to start from
 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  
      * Notify interested checks that visiting a node.
 394  
      * @param aAST the node to notify for
 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  
      * Notify interested checks that leaving a node.
 407  
      *
 408  
      * @param aAST
 409  
      *                the node to notify for
 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  
      * Static helper method to parses a Java source file.
 422  
      *
 423  
      * @param aContents
 424  
      *                contains the contents of the file
 425  
      * @throws TokenStreamException
 426  
      *                 if lexing failed
 427  
      * @throws RecognitionException
 428  
      *                 if parsing failed
 429  
      * @return the root of the AST
 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  
      * @return true if we should use recursive algorithm
 463  
      *         for tree processing, false for iterative one.
 464  
      */
 465  
     private boolean useRecursiveAlgorithm()
 466  
     {
 467  542
         return mRecursive;
 468  
     }
 469  
 
 470  
     /**
 471  
      * Processes a node calling interested checks at each node.
 472  
      * Uses iterative algorithm.
 473  
      * @param aRoot the root of tree for process
 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  
 }