Java tutorial
//////////////////////////////////////////////////////////////////////////////// // // adapted by Daniel Zuberbuehler // Copyright (C) 2005 Siemens Schweiz AG, Transportation Systems // Copyright (C) 2001-2005 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 // // $Id$ // //////////////////////////////////////////////////////////////////////////////// package net.sourceforge.checkstyle4ada; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.Reader; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.Vector; import antlr.RecognitionException; import antlr.TokenStreamException; import antlr.TokenStreamRecognitionException; import antlr.TokenStream; import com.puppycrawl.tools.checkstyle.DefaultContext; import com.puppycrawl.tools.checkstyle.Defn; import com.puppycrawl.tools.checkstyle.ModuleFactory; 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.LocalizedMessage; import com.puppycrawl.tools.checkstyle.api.Utils; import net.sourceforge.checkstyle4ada.api.KeywordListener; import net.sourceforge.checkstyle4ada.api.TokenTypes4Ada; import net.sourceforge.checkstyle4ada.grammars.GeneratedAdaLexer; import net.sourceforge.checkstyle4ada.grammars.GeneratedAdaRecognizer; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** * Responsible for walking an abstract syntax tree and notifying interested * checks at each each node. * * @author Oliver Burn * @author Daniel Zuberbuehler * @version 1.0 */ public final class TreeWalker4Ada extends AbstractFileSetCheck { /** * Overrides ANTLR error reporting so we completely control * checkstyle's output during parsing. This is important because * we try parsing with several grammers (with/without support for * <code>assert</code>). We must not write any error messages when * parsing fails because with the next grammar it might succeed * and the user will be confused. */ private static final class SilentAdaRecognizer extends GeneratedAdaRecognizer { /** * Creates a new <code>SilentJavaRecognizer</code> instance. * * @param aLexer the tokenstream the recognizer operates on. */ public SilentAdaRecognizer(TokenStream aLexer) { super(aLexer); } /** * Parser error-reporting function, does nothing. * @param aRex the exception to be reported */ public void reportError(RecognitionException aRex) { } /** * Parser error-reporting function, does nothing. * @param aMsg the error message */ public void reportError(String aMsg) { } /** * Parser warning-reporting function, does nothing. * @param aMsg the error message */ public void reportWarning(String aMsg) { } } /** default distance between tab stops */ private static final int DEFAULT_TAB_WIDTH = 4; /** maps from token name to checks */ private final Map mTokenToChecks = new HashMap(); /** all the registered checks */ private final Set mAllChecks = new HashSet(); /** the distance between tab stops */ private int mTabWidth = DEFAULT_TAB_WIDTH; /** cache file **/ private PropertyCacheFile mCache = new PropertyCacheFile(null, null); /** class loader to resolve classes with. **/ private ClassLoader mClassLoader; /** context of child components */ private Context mChildContext; /** a factory for creating submodules (i.e. the Checks) */ private ModuleFactory mModuleFactory; /** controls whether we should use recursive or iterative * algorithm for tree processing. */ private final boolean mRecursive; /** logger for debug purpose */ private static Log sLog = LogFactory.getLog("net.sourceforge.checkstyle4ada.TreeWalker4Ada"); //TODO: comment private static Vector keywordListeners = new Vector(); /** * Creates a new <code>TreeWalker</code> instance. */ public TreeWalker4Ada() { setFileExtensions(new String[] { "adb", "ads", "adc", "ada" }); // Tree walker can use two possible algorithms for // tree processing (iterative and recursive. // Recursive is default for now. String recursive = System.getProperty("checkstyle.use.recursive.algorithm", "true"); mRecursive = "true".equals(recursive); if (mRecursive) { sLog.debug("TreeWalker uses recursive algorithm"); } else { sLog.debug("TreeWalker uses iterative algorithm"); } } /** @param aTabWidth the distance between tab stops */ public void setTabWidth(int aTabWidth) { mTabWidth = aTabWidth; } /** @param aFileName the cache file */ public void setCacheFile(String aFileName) { final Configuration configuration = getConfiguration(); mCache = new PropertyCacheFile(configuration, aFileName); } /** @param aClassLoader class loader to resolve classes with. */ public void setClassLoader(ClassLoader aClassLoader) { mClassLoader = aClassLoader; } /** * Sets the module factory for creating child modules (Checks). * @param aModuleFactory the factory */ public void setModuleFactory(ModuleFactory aModuleFactory) { mModuleFactory = aModuleFactory; } /** @see com.puppycrawl.tools.checkstyle.api.Configurable */ public void finishLocalSetup() { DefaultContext checkContext = new DefaultContext(); checkContext.add("classLoader", mClassLoader); checkContext.add("messages", getMessageCollector()); checkContext.add("severity", getSeverity()); // TODO: hmmm.. this looks less than elegant // we have just parsed the string, // now we're recreating it only to parse it again a few moments later checkContext.add("tabWidth", String.valueOf(mTabWidth)); mChildContext = checkContext; } /** * Instantiates, configures and registers a Check that is specified * in the provided configuration. * @see com.puppycrawl.tools.checkstyle.api.AutomaticBean */ 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("TreeWalker4Ada is not allowed as a parent of " + name); } final Check c = (Check) module; c.contextualize(mChildContext); c.configure(aChildConf); c.init(); if (c instanceof KeywordListener) { keywordListeners.add(c); } registerCheck(c); } /** * Processes a specified file and reports all errors found. * @param aFile the file to process **/ private void process(File aFile) { // 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 { getMessageDispatcher().fireFileStarted(fileName); final String[] lines = Utils.getLines(fileName, getCharset()); FileContents contents = new FileContents(fileName, lines); //TODO: FileContents is Java specific, //so the comment start pattern is wrong for ada. //hopefully this method get's added to FileContents... //contents.setSingleLineCommentPat("--"); final DetailAST rootAST = TreeWalker4Ada.parse(contents); walk(rootAST, contents); } catch (FileNotFoundException fnfe) { Utils.getExceptionLogger().debug("FileNotFoundException occured.", fnfe); getMessageCollector().add( new LocalizedMessage(0, Defn.CHECKSTYLE_BUNDLE, "general.fileNotFound", null, this.getClass())); } catch (IOException ioe) { Utils.getExceptionLogger().debug("IOException occured.", ioe); getMessageCollector().add(new LocalizedMessage(0, Defn.CHECKSTYLE_BUNDLE, "general.exception", new String[] { ioe.getMessage() }, this.getClass())); } catch (RecognitionException re) { Utils.getExceptionLogger().debug("RecognitionException occured.", re); getMessageCollector().add(new LocalizedMessage(re.getLine(), re.getColumn(), Defn.CHECKSTYLE_BUNDLE, "general.exception", new String[] { re.getMessage() }, this.getClass())); } catch (TokenStreamRecognitionException tre) { Utils.getExceptionLogger().debug("TokenStreamRecognitionException occured.", tre); final RecognitionException re = tre.recog; if (re != null) { getMessageCollector().add(new LocalizedMessage(re.getLine(), re.getColumn(), Defn.CHECKSTYLE_BUNDLE, "general.exception", new String[] { re.getMessage() }, this.getClass())); } else { getMessageCollector().add(new LocalizedMessage(0, Defn.CHECKSTYLE_BUNDLE, "general.exception", new String[] { "TokenStreamRecognitionException occured." }, this.getClass())); } } catch (TokenStreamException te) { Utils.getExceptionLogger().debug("TokenStreamException occured.", te); getMessageCollector().add(new LocalizedMessage(0, Defn.CHECKSTYLE_BUNDLE, "general.exception", new String[] { te.getMessage() }, this.getClass())); } catch (Throwable err) { Utils.getExceptionLogger().debug("Throwable occured.", err); getMessageCollector().add(new LocalizedMessage(0, Defn.CHECKSTYLE_BUNDLE, "general.exception", new String[] { "" + err }, this.getClass())); } if (getMessageCollector().size() == 0) { mCache.checkedOk(fileName, timestamp); } else { fireErrors(fileName); } getMessageDispatcher().fireFileFinished(fileName); } /** * 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 { int[] tokens = new int[] {}; //safety initialization final Set checkTokens = aCheck.getTokenNames(); if (!checkTokens.isEmpty()) { tokens = aCheck.getRequiredTokens(); //register configured tokens final int acceptableTokens[] = aCheck.getAcceptableTokens(); Arrays.sort(acceptableTokens); final Iterator it = checkTokens.iterator(); while (it.hasNext()) { final String token = (String) it.next(); try { final int tokenId = TokenTypes4Ada.getTokenId(token); if (Arrays.binarySearch(acceptableTokens, tokenId) >= 0) { registerCheck(token, aCheck); } // TODO: else log warning } catch (IllegalArgumentException ex) { throw new CheckstyleException("illegal token \"" + token + "\" in check " + aCheck, ex); } } } else { tokens = aCheck.getDefaultTokens(); } for (int i = 0; i < tokens.length; i++) { registerCheck(tokens[i], 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(TokenTypes4Ada.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) { ArrayList visitors = (ArrayList) mTokenToChecks.get(aToken); if (visitors == null) { visitors = new ArrayList(); mTokenToChecks.put(aToken, visitors); } visitors.add(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) { getMessageCollector().reset(); notifyBegin(aAST, aContents); // empty files are not flagged by javac, 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) { final Iterator it = mAllChecks.iterator(); while (it.hasNext()) { final Check check = (Check) it.next(); check.setFileContents(aContents); check.beginTree(aRootAST); } } /** * Notify checks that finished walking a tree. * @param aRootAST the root of the tree */ private void notifyEnd(DetailAST aRootAST) { final Iterator it = mAllChecks.iterator(); while (it.hasNext()) { final Check check = (Check) it.next(); check.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 = (DetailAST) aAST.getFirstChild(); if (child != null) { processRec(child); } notifyLeave(aAST); final DetailAST sibling = (DetailAST) 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) { final ArrayList visitors = (ArrayList) mTokenToChecks.get(TokenTypes4Ada.getTokenName(aAST.getType())); if (visitors != null) { for (int i = 0; i < visitors.size(); i++) { final Check check = (Check) visitors.get(i); check.visitToken(aAST); } } } /** * Notify interested checks that leaving a node. * @param aAST the node to notify for */ private void notifyLeave(DetailAST aAST) { final ArrayList visitors = (ArrayList) mTokenToChecks.get(TokenTypes4Ada.getTokenName(aAST.getType())); if (visitors != null) { for (int i = 0; i < visitors.size(); i++) { final Check check = (Check) visitors.get(i); check.leaveToken(aAST); } } } /** * Static helper method to parses a Java source file. * @param aContents 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(FileContents aContents) throws RecognitionException, TokenStreamException { DetailAST rootAST = null; try { rootAST = parse(aContents, true); } catch (RecognitionException exception) { rootAST = parse(aContents, false); } return rootAST; } /** * Static helper method to parses a Java source file with a given * lexer class and parser class. * @param aContents contains the contents of the file * @param aSilentlyConsumeErrors flag to output errors to stdout or not * @param aTreatAssertAsKeyword flag to treat 'assert' as a keyowrd * @param aTreatEnumAsKeyword flag to treat 'enum' as a keyowrd * @throws TokenStreamException if lexing failed * @throws RecognitionException if parsing failed * @return the root of the AST */ private static DetailAST parse(FileContents aContents, boolean aSilentlyConsumeErrors) throws RecognitionException, TokenStreamException { final Reader sar = new StringArrayReader(aContents.getLines()); final GeneratedAdaLexer lexer = new GeneratedAdaLexer(sar); lexer.setFilename(aContents.getFilename()); lexer.setCommentListener(aContents); for (int i = 0; i < keywordListeners.size(); i++) { KeywordListener listener = (KeywordListener) keywordListeners.get(i); lexer.addKeywordListener(listener); } final GeneratedAdaRecognizer parser = aSilentlyConsumeErrors ? new SilentAdaRecognizer(lexer) : new GeneratedAdaRecognizer(lexer); parser.setFilename(aContents.getFilename()); parser.setASTNodeClass(DetailAST.class.getName()); parser.compilation_unit(); return (DetailAST) parser.getAST(); } /** @see com.puppycrawl.tools.checkstyle.api.FileSetCheck */ public void process(File[] aFiles) { final File[] adaFiles = filter(aFiles); for (int i = 0; i < adaFiles.length; i++) { process(adaFiles[i]); } } /** * @see com.puppycrawl.tools.checkstyle.api.FileSetCheck */ public void destroy() { for (Iterator it = mAllChecks.iterator(); it.hasNext();) { final Check c = (Check) it.next(); 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) { DetailAST curNode = aRoot; while (curNode != null) { notifyVisit(curNode); DetailAST toVisit = (DetailAST) curNode.getFirstChild(); while (curNode != null && toVisit == null) { notifyLeave(curNode); toVisit = (DetailAST) curNode.getNextSibling(); if (toVisit == null) { curNode = curNode.getParent(); } } curNode = toVisit; } } }