Java tutorial
//////////////////////////////////////////////////////////////////////////////// // 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; } } }