com.puppycrawl.tools.checkstyle.XmlTreeWalker.java Source code

Java tutorial

Introduction

Here is the source code for com.puppycrawl.tools.checkstyle.XmlTreeWalker.java

Source

////////////////////////////////////////////////////////////////////////////////
// checkstyle: Checks Java source code for adherence to a set of rules.
// Copyright (C) 2001-2011  Oliver Burn
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
////////////////////////////////////////////////////////////////////////////////
package com.puppycrawl.tools.checkstyle;

import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
import com.puppycrawl.tools.checkstyle.api.AbstractFileSetCheck;
import com.puppycrawl.tools.checkstyle.api.Check;
import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
import com.puppycrawl.tools.checkstyle.api.Configuration;
import com.puppycrawl.tools.checkstyle.api.Context;
import com.puppycrawl.tools.checkstyle.api.DetailAST;
import com.puppycrawl.tools.checkstyle.api.FileContents;
import com.puppycrawl.tools.checkstyle.api.FileText;
import com.puppycrawl.tools.checkstyle.api.LocalizedMessage;
import com.puppycrawl.tools.checkstyle.api.TokenTypes;
import com.puppycrawl.tools.checkstyle.api.Utils;
import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import javax.xml.stream.XMLStreamException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.XMLReaderFactory;

/**
 * Responsible for walking an abstract syntax tree and notifying interested
 * checks at each each node.
 *
 * @author Yoann Ciabaud<y.ciabaud@gmail.com>
 * @see TreeWalker
 */
public class XmlTreeWalker extends AbstractFileSetCheck {

    /** Maps from token name to checks */
    private final Multimap<String, Check> mTokenToChecks = HashMultimap.create();
    /** All the registered checks */
    private final Set<Check> mAllChecks = Sets.newHashSet();

    /** A factory for creating submodules (i.e. the Checks) */
    private ModuleFactory mModuleFactory;

    /** Context of child components */
    private Context mChildContext;

    /** 
     * Controls whether we should use recursive or iterative
     * algorithm for tree processing.
     */
    private final boolean mRecursive;

    /** Cache file **/
    private PropertyCacheFile mCache = new PropertyCacheFile(null, null);

    /** Logger for debug purpose */
    private static final Log LOG = LogFactory.getLog("com.puppycrawl.tools.checkstyle.TreeWalker");

    /**
     * Creates a new <code>XmlTreeWalker</code> instance.
     */
    public XmlTreeWalker() {
        setFileExtensions(new String[] { "xml" });
        // Tree walker can use two possible algorithms for
        // tree processing (iterative and recursive.
        // Recursive is default for now.
        final String recursive = System.getProperty("checkstyle.use.recursive.algorithm", "false");
        mRecursive = "true".equals(recursive);
        if (mRecursive) {
            LOG.debug("XmlTreeWalker uses recursive algorithm");
        } else {
            LOG.debug("XmlTreeWalker uses iterative algorithm");
        }
    }

    /** @param aFileName the cache file */
    public void setCacheFile(String aFileName) {
        final Configuration configuration = getConfiguration();
        mCache = new PropertyCacheFile(configuration, aFileName);
    }

    /** {@inheritDoc} */
    @Override
    public void finishLocalSetup() {
        final DefaultContext checkContext = new DefaultContext();
        checkContext.add("messages", getMessageCollector());
        checkContext.add("severity", getSeverity());

        mChildContext = checkContext;
    }

    /** {@inheritDoc} */
    @Override
    public void setupChild(Configuration aChildConf) throws CheckstyleException {
        // TODO: improve the error handing
        final String name = aChildConf.getName();
        final Object module = mModuleFactory.createModule(name);
        if (!(module instanceof Check)) {
            throw new CheckstyleException("XmlTreeWalker is not allowed as a parent of " + name);
        }
        final Check c = (Check) module;
        c.contextualize(mChildContext);
        c.configure(aChildConf);
        c.init();

        registerCheck(c);
    }

    /**
     * Sets the module factory for creating child modules (Checks).
     * @param aModuleFactory the factory
     */
    public void setModuleFactory(ModuleFactory aModuleFactory) {
        mModuleFactory = aModuleFactory;
    }

    /** {@inheritDoc} */
    @Override
    protected void processFiltered(File aFile, List<String> aLines) {
        // check if already checked and passed the file
        final String fileName = aFile.getPath();
        final long timestamp = aFile.lastModified();
        if (mCache.alreadyChecked(fileName, timestamp)) {
            return;
        }

        try {
            final FileText text = FileText.fromLines(aFile, aLines);
            final FileContents contents = new FileContents(text);

            final String fullText = contents.getText().getFullText().toString();
            InputSource document = new InputSource();
            document.setCharacterStream(new StringReader(fullText));

            final DetailAST rootAST = XmlTreeWalker.parse(document, aFile);
            walk(rootAST, contents);
        } catch (final Throwable err) {
            err.printStackTrace();
            Utils.getExceptionLogger().debug("Throwable occured.", err);
            getMessageCollector().add(new LocalizedMessage(0, Defn.CHECKSTYLE_BUNDLE, "general.exception",
                    new String[] { "" + err }, getId(), this.getClass(), null));
        }

        if (getMessageCollector().size() == 0) {
            mCache.checkedOk(fileName, timestamp);
        }
    }

    /**
     * Register a check for a given configuration.
     * @param aCheck the check to register
     * @throws CheckstyleException if an error occurs
     */
    private void registerCheck(Check aCheck) throws CheckstyleException {
        final int[] tokens;
        final Set<String> checkTokens = aCheck.getTokenNames();
        if (!checkTokens.isEmpty()) {
            tokens = aCheck.getRequiredTokens();

            //register configured tokens
            final int acceptableTokens[] = aCheck.getAcceptableTokens();
            Arrays.sort(acceptableTokens);
            for (String token : checkTokens) {
                try {
                    final int tokenId = TokenTypes.getTokenId(token);
                    if (Arrays.binarySearch(acceptableTokens, tokenId) >= 0) {
                        registerCheck(token, aCheck);
                    }
                    // TODO: else log warning
                } catch (final IllegalArgumentException ex) {
                    throw new CheckstyleException("illegal token \"" + token + "\" in check " + aCheck, ex);
                }
            }
        } else {
            tokens = aCheck.getDefaultTokens();
        }
        for (int element : tokens) {
            registerCheck(element, aCheck);
        }
        mAllChecks.add(aCheck);
    }

    /**
     * Register a check for a specified token id.
     * @param aTokenID the id of the token
     * @param aCheck the check to register
     */
    private void registerCheck(int aTokenID, Check aCheck) {
        registerCheck(TokenTypes.getTokenName(aTokenID), aCheck);
    }

    /**
     * Register a check for a specified token name
     * @param aToken the name of the token
     * @param aCheck the check to register
     */
    private void registerCheck(String aToken, Check aCheck) {
        mTokenToChecks.put(aToken, aCheck);
    }

    /**
     * Initiates the walk of an AST.
     * @param aAST the root AST
     * @param aContents the contents of the file the AST was generated from
     */
    private void walk(DetailAST aAST, FileContents aContents) throws CheckstyleException {
        getMessageCollector().reset();
        notifyBegin(aAST, aContents);

        // empty files are not flagged by sax, will yield aAST == null
        if (aAST != null) {
            if (useRecursiveAlgorithm()) {
                processRec(aAST);
            } else {
                processIter(aAST);
            }
        }

        notifyEnd(aAST);
    }

    /**
     * Notify interested checks that about to begin walking a tree.
     * @param aRootAST the root of the tree
     * @param aContents the contents of the file the AST was generated from
     */
    private void notifyBegin(DetailAST aRootAST, FileContents aContents) throws CheckstyleException {
        for (Check ch : mAllChecks) {
            ch.setFileContents(aContents);
            ch.beginTree(aRootAST);
        }
    }

    /**
     * Notify checks that finished walking a tree.
     * @param aRootAST the root of the tree
     */
    private void notifyEnd(DetailAST aRootAST) {
        for (Check ch : mAllChecks) {
            ch.finishTree(aRootAST);
        }
    }

    /**
     * Recursively processes a node calling interested checks at each node.
     * Uses recursive algorithm.
     * @param aAST the node to start from
     */
    private void processRec(DetailAST aAST) {
        if (aAST == null) {
            return;
        }

        notifyVisit(aAST);

        final DetailAST child = aAST.getFirstChild();
        if (child != null) {
            processRec(child);
        }

        notifyLeave(aAST);

        final DetailAST sibling = aAST.getNextSibling();
        if (sibling != null) {
            processRec(sibling);
        }
    }

    /**
     * Notify interested checks that visiting a node.
     * @param aAST the node to notify for
     */
    private void notifyVisit(DetailAST aAST) {
        if (aAST.getType() != 0) {
            final Collection<Check> visitors = mTokenToChecks.get(TokenTypes.getTokenName(aAST.getType()));
            for (Check c : visitors) {
                c.visitToken(aAST);
            }
        }
    }

    /**
     * Notify interested checks that leaving a node.
     *
     * @param aAST
     *                the node to notify for
     */
    private void notifyLeave(DetailAST aAST) {
        if (aAST.getType() != 0) {
            final Collection<Check> visitors = mTokenToChecks.get(TokenTypes.getTokenName(aAST.getType()));
            for (Check ch : visitors) {
                ch.leaveToken(aAST);
            }
        }
    }

    /**
     * Static helper method to parses a Java source file.
     *
     * @param source
     *                contains the contents of the file
     * @throws TokenStreamException
     *                 if lexing failed
     * @throws RecognitionException
     *                 if parsing failed
     * @return the root of the AST
     */
    public static DetailAST parse(InputSource source, File file)
            throws IOException, XMLStreamException, SAXException {

        XMLReader reader = XMLReaderFactory.createXMLReader();
        XmlContentHandler contentHandler = new XmlContentHandler(file);
        reader.setContentHandler(contentHandler);

        reader.parse(source);

        return contentHandler.getAST();
    }

    /** {@inheritDoc} */
    @Override
    public void destroy() {
        for (Check c : mAllChecks) {
            c.destroy();
        }
        mCache.destroy();
        super.destroy();
    }

    /**
     * @return true if we should use recursive algorithm
     *         for tree processing, false for iterative one.
     */
    private boolean useRecursiveAlgorithm() {
        return mRecursive;
    }

    /**
     * Processes a node calling interested checks at each node.
     * Uses iterative algorithm.
     * @param aRoot the root of tree for process
     */
    private void processIter(DetailAST aRoot) throws CheckstyleException {
        DetailAST curNode = aRoot;
        while (curNode != null) {
            notifyVisit(curNode);
            DetailAST toVisit = curNode.getFirstChild();
            while ((curNode != null) && (toVisit == null)) {
                notifyLeave(curNode);
                toVisit = curNode.getNextSibling();
                if (toVisit == null) {
                    curNode = curNode.getParent();
                }
            }
            curNode = toVisit;
        }
    }

}