| /** |
| [The "BSD licence"] |
| |
| ANTLR - Copyright (c) 2005-2008 Terence Parr |
| Maven Plugin - Copyright (c) 2009 Jim Idle |
| |
| All rights reserved. |
| |
| Redistribution and use in source and binary forms, with or without |
| modification, are permitted provided that the following conditions |
| are met: |
| 1. Redistributions of source code must retain the above copyright |
| notice, this list of conditions and the following disclaimer. |
| 2. Redistributions in binary form must reproduce the above copyright |
| notice, this list of conditions and the following disclaimer in the |
| documentation and/or other materials provided with the distribution. |
| 3. The name of the author may not be used to endorse or promote products |
| derived from this software without specific prior written permission. |
| |
| THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR |
| IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
| OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. |
| IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, |
| INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT |
| NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
| THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| /* ======================================================================== |
| * This is the definitive ANTLR3 Mojo set. All other sets are belong to us. |
| */ |
| package org.antlr.mojo.antlr3; |
| |
| import org.apache.maven.plugin.AbstractMojo; |
| import org.apache.maven.plugin.MojoExecutionException; |
| import org.apache.maven.plugin.MojoFailureException; |
| import org.apache.maven.project.MavenProject; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.util.Collections; |
| import java.util.HashSet; |
| import java.util.Set; |
| import org.antlr.Tool; |
| import org.antlr.runtime.RecognitionException; |
| import org.apache.maven.plugin.logging.Log; |
| import org.codehaus.plexus.compiler.util.scan.InclusionScanException; |
| import org.codehaus.plexus.compiler.util.scan.SimpleSourceInclusionScanner; |
| import org.codehaus.plexus.compiler.util.scan.SourceInclusionScanner; |
| import org.codehaus.plexus.compiler.util.scan.mapping.SourceMapping; |
| import org.codehaus.plexus.compiler.util.scan.mapping.SuffixMapping; |
| |
| /** |
| * Goal that picks up all the ANTLR grammars in a project and moves those that |
| * are required for generation of the compilable sources into the location |
| * that we use to compile them, such as target/generated-sources/antlr3 ... |
| * |
| * @goal antlr |
| * |
| * @phase process-sources |
| * @requiresDependencyResolution compile |
| * @requiresProject true |
| * |
| * @author <a href="mailto:jimi@temporal-wave.com">Jim Idle</a> |
| */ |
| public class Antlr3Mojo |
| extends AbstractMojo { |
| |
| // First, let's deal with the options that the ANTLR tool itself |
| // can be configured by. |
| // |
| /** |
| * If set to true, then after the tool has processed an input grammar file |
| * it will report various statistics about the parser, such as information |
| * on cyclic DFAs, which rules may use backtracking, and so on. |
| * |
| * @parameter default-value="false" |
| */ |
| protected boolean report; |
| /** |
| * If set to true, then the ANTLR tool will print a version of the input |
| * grammar which is devoid of any actions that may be present in the input file. |
| * |
| * @parameter default-value="false" |
| */ |
| protected boolean printGrammar; |
| /** |
| * If set to true, then the code generated by the ANTLR code generator will |
| * be set to debug mode. This means that when run, the code will 'hang' and |
| * wait for a debug connection on a TCP port (49100 by default). |
| * |
| * @parameter default-value="false" |
| */ |
| protected boolean debug; |
| /** |
| * If set to true, then then the generated parser will compute and report on |
| * profile information at runtime. |
| * |
| * @parameter default-value="false" |
| */ |
| protected boolean profile; |
| /** |
| * If set to true then the ANTLR tool will generate a description of the nfa |
| * for each rule in <a href="http://www.graphviz.org">Dot format</a> |
| * |
| * @parameter default-value="false" |
| */ |
| protected boolean nfa; |
| /** |
| * If set to true then the ANTLR tool will generate a description of the DFA |
| * for each decision in the grammar in <a href="http://www.graphviz.org">Dot format</a> |
| * |
| * @parameter default-value="false" |
| */ |
| protected boolean dfa; |
| /** |
| * If set to true, the generated parser code will log rule entry and exit points |
| * to stdout as an aid to debugging. |
| * |
| * @parameter default-value="false" |
| */ |
| protected boolean trace; |
| /** |
| * If this parameter is set, it indicates that any warning or error messages returned |
| * by ANLTR, should be formatted in the specified way. Currently, ANTLR supports the |
| * built-in formats of antlr, gnu and vs2005. |
| * |
| * @parameter default-value="antlr" |
| */ |
| protected String messageFormat; |
| /** |
| * If this parameter is set to true, then ANTLR will report all sorts of things |
| * about what it is doing such as the names of files and the version of ANTLR and so on. |
| * |
| * @parameter default-value="true" |
| */ |
| protected boolean verbose; |
| |
| /** |
| * The number of alts, beyond which ANTLR will not generate a switch statement |
| * for the DFA. |
| * |
| * @parameter default-value="300" |
| */ |
| private int maxSwitchCaseLabels; |
| |
| /** |
| * The number of alts, below which ANTLR will not choose to generate a switch |
| * statement over an if statement. |
| */ |
| private int minSwitchAlts; |
| |
| /* -------------------------------------------------------------------- |
| * The following are Maven specific parameters, rather than specificlly |
| * options that the ANTLR tool can use. |
| */ |
| /** |
| * Provides an explicit list of all the grammars that should |
| * be included in the generate phase of the plugin. Note that the plugin |
| * is smart enough to realize that imported grammars should be included but |
| * not acted upon directly by the ANTLR Tool. |
| * |
| * Unless otherwise specified, the include list scans for and includes all |
| * files that end in ".g" in any directory beneath src/main/antlr3. Note that |
| * this version of the plugin looks for the directory antlr3 and not the directory |
| * antlr, so as to avoid clashes and confusion for projects that use both v2 and v3 grammars |
| * such as ANTLR itself. |
| * |
| * @parameter |
| */ |
| protected Set includes = new HashSet(); |
| /** |
| * Provides an explicit list of any grammars that should be excluded from |
| * the generate phase of the plugin. Files listed here will not be sent for |
| * processing by the ANTLR tool. |
| * |
| * @parameter |
| */ |
| protected Set excludes = new HashSet(); |
| /** |
| * @parameter expression="${project}" |
| * @required |
| * @readonly |
| */ |
| protected MavenProject project; |
| /** |
| * Specifies the Antlr directory containing grammar files. For |
| * antlr version 3.x we default this to a directory in the tree |
| * called antlr3 because the antlr directory is occupied by version |
| * 2.x grammars. |
| * |
| * @parameter default-value="${basedir}/src/main/antlr3" |
| * @required |
| */ |
| private File sourceDirectory; |
| /** |
| * Location for generated Java files. For antlr version 3.x we default |
| * this to a directory in the tree called antlr3 because the antlr |
| * directory is occupied by version 2.x grammars. |
| * |
| * @parameter default-value="${project.build.directory}/generated-sources/antlr3" |
| * @required |
| */ |
| private File outputDirectory; |
| /** |
| * Location for imported token files, e.g. <code>.tokens</code> and imported grammars. |
| * Note that ANTLR will not try to process grammars that it finds to be imported |
| * into other grammars (in the same processing session). |
| * |
| * @parameter default-value="${basedir}/src/main/antlr3/imports" |
| */ |
| private File libDirectory; |
| |
| public File getSourceDirectory() { |
| return sourceDirectory; |
| } |
| |
| public File getOutputDirectory() { |
| return outputDirectory; |
| } |
| |
| public File getLibDirectory() { |
| return libDirectory; |
| } |
| |
| void addSourceRoot(File outputDir) { |
| project.addCompileSourceRoot(outputDir.getPath()); |
| } |
| /** |
| * An instance of the ANTLR tool build |
| */ |
| protected Tool tool; |
| |
| /** |
| * The main entry point for this Mojo, it is responsible for converting |
| * ANTLR 3.x grammars into the target language specified by the grammar. |
| * |
| * @throws org.apache.maven.plugin.MojoExecutionException When something is discovered such as a missing source |
| * @throws org.apache.maven.plugin.MojoFailureException When something really bad happens such as not being able to create the ANTLR Tool |
| */ |
| public void execute() |
| throws MojoExecutionException, MojoFailureException { |
| |
| Log log = getLog(); |
| |
| // Check to see if the user asked for debug information, then dump all the |
| // parameters we have picked up if they did. |
| // |
| if (log.isDebugEnabled()) { |
| |
| // Excludes |
| // |
| for (String e : (Set<String>) excludes) { |
| |
| log.debug("ANTLR: Exclude: " + e); |
| } |
| |
| // Includes |
| // |
| for (String e : (Set<String>) includes) { |
| |
| log.debug("ANTLR: Include: " + e); |
| } |
| |
| // Output location |
| // |
| log.debug("ANTLR: Output: " + outputDirectory); |
| |
| // Library directory |
| // |
| log.debug("ANTLR: Library: " + libDirectory); |
| |
| // Flags |
| // |
| log.debug("ANTLR: report : " + report); |
| log.debug("ANTLR: printGrammar : " + printGrammar); |
| log.debug("ANTLR: debug : " + debug); |
| log.debug("ANTLR: profile : " + profile); |
| log.debug("ANTLR: nfa : " + nfa); |
| log.debug("ANTLR: dfa : " + dfa); |
| log.debug("ANTLR: trace : " + trace); |
| log.debug("ANTLR: messageFormat : " + messageFormat); |
| log.debug("ANTLR: maxSwitchCaseLabels : " + maxSwitchCaseLabels); |
| log.debug("ANTLR: minSwitchAlts : " + minSwitchAlts); |
| log.debug("ANTLR: verbose : " + verbose); |
| } |
| |
| // Ensure that the output directory path is all in tact so that |
| // ANTLR can just write into it. |
| // |
| File outputDir = getOutputDirectory(); |
| |
| if (!outputDir.exists()) { |
| outputDir.mkdirs(); |
| } |
| |
| // First thing we need is an instance of the ANTLR 3.1 build tool |
| // |
| try { |
| // ANTLR Tool buld interface |
| // |
| tool = new Tool(); |
| } catch (Exception e) { |
| log.error("The attempt to create the ANTLR build tool failed, see exception report for details"); |
| |
| throw new MojoFailureException("Jim failed you!"); |
| } |
| |
| // Next we need to set the options given to us in the pom into the |
| // tool instance we have created. |
| // |
| tool.setDebug(debug); |
| tool.setGenerate_DFA_dot(dfa); |
| tool.setGenerate_NFA_dot(nfa); |
| tool.setProfile(profile); |
| tool.setReport(report); |
| tool.setPrintGrammar(printGrammar); |
| tool.setTrace(trace); |
| tool.setVerbose(verbose); |
| tool.setMessageFormat(messageFormat); |
| tool.setMaxSwitchCaseLabels(maxSwitchCaseLabels); |
| tool.setMinSwitchAlts(minSwitchAlts); |
| |
| // Where do we want ANTLR to produce its output? (Base directory) |
| // |
| if (log.isDebugEnabled()) |
| { |
| log.debug("Output directory base will be " + outputDirectory.getAbsolutePath()); |
| } |
| tool.setOutputDirectory(outputDirectory.getAbsolutePath()); |
| |
| // Tell ANTLR that we always want the output files to be produced in the output directory |
| // using the same relative path as the input file was to the input directory. |
| // |
| tool.setForceRelativeOutput(true); |
| |
| // Where do we want ANTLR to look for .tokens and import grammars? |
| // |
| tool.setLibDirectory(libDirectory.getAbsolutePath()); |
| |
| if (!sourceDirectory.exists()) { |
| if (log.isInfoEnabled()) { |
| log.info("No ANTLR grammars to compile in " + sourceDirectory.getAbsolutePath()); |
| } |
| return; |
| } else { |
| if (log.isInfoEnabled()) { |
| log.info("ANTLR: Processing source directory " + sourceDirectory.getAbsolutePath()); |
| } |
| } |
| |
| // Set working directory for ANTLR to be the base source directory |
| // |
| tool.setInputDirectory(sourceDirectory.getAbsolutePath()); |
| |
| try { |
| |
| // Now pick up all the files and process them with the Tool |
| // |
| processGrammarFiles(sourceDirectory, outputDirectory); |
| |
| } catch (InclusionScanException ie) { |
| |
| log.error(ie); |
| throw new MojoExecutionException("Fatal error occured while evaluating the names of the grammar files to analyze"); |
| |
| } catch (Exception e) { |
| |
| getLog().error(e); |
| throw new MojoExecutionException(e.getMessage()); |
| } |
| |
| |
| |
| tool.process(); |
| |
| // If any of the grammar files caused errors but did nto throw exceptions |
| // then we should have accumulated errors in the counts |
| // |
| if (tool.getNumErrors() > 0) { |
| throw new MojoExecutionException("ANTLR caught " + tool.getNumErrors() + " build errors."); |
| } |
| |
| // All looks good, so we need to tel Maven about the sources that |
| // we just created. |
| // |
| if (project != null) { |
| // Tell Maven that there are some new source files underneath |
| // the output directory. |
| // |
| addSourceRoot(this.getOutputDirectory()); |
| } |
| |
| } |
| |
| |
| /** |
| * |
| * @param sourceDirectory |
| * @param outputDirectory |
| * @throws antlr.TokenStreamException |
| * @throws antlr.RecognitionException |
| * @throws java.io.IOException |
| * @throws org.codehaus.plexus.compiler.util.scan.InclusionScanException |
| */ |
| private void processGrammarFiles(File sourceDirectory, File outputDirectory) |
| throws RecognitionException, IOException, InclusionScanException { |
| // Which files under the source set should we be looking for as grammar files |
| // |
| SourceMapping mapping = new SuffixMapping("g", Collections.EMPTY_SET); |
| |
| // What are the sets of includes (defaulted or otherwise). |
| // |
| Set includes = getIncludesPatterns(); |
| |
| // Now, to the excludes, we need to add the imports directory |
| // as this is autoscanned for importd grammars and so is auto-excluded from the |
| // set of gramamr fiels we shuold be analyzing. |
| // |
| excludes.add("imports/**"); |
| |
| SourceInclusionScanner scan = new SimpleSourceInclusionScanner(includes, excludes); |
| |
| scan.addSourceMapping(mapping); |
| Set grammarFiles = scan.getIncludedSources(sourceDirectory, null); |
| |
| if (grammarFiles.isEmpty()) { |
| if (getLog().isInfoEnabled()) { |
| getLog().info("No grammars to process"); |
| } |
| } else { |
| |
| // Tell the ANTLR tool that we want sorted build mode |
| // |
| tool.setMake(true); |
| |
| // Iterate each grammar file we were given and add it into the tool's list of |
| // grammars to process. |
| // |
| for (File grammar : (Set<File>) grammarFiles) { |
| |
| if (getLog().isDebugEnabled()) { |
| getLog().debug("Grammar file '" + grammar.getPath() + "' detected."); |
| } |
| |
| |
| String relPath = findSourceSubdir(sourceDirectory, grammar.getPath()) + grammar.getName(); |
| |
| if (getLog().isDebugEnabled()) { |
| getLog().debug(" ... relative path is: " + relPath); |
| } |
| tool.addGrammarFile(relPath); |
| |
| } |
| |
| } |
| |
| |
| } |
| |
| public Set getIncludesPatterns() { |
| if (includes == null || includes.isEmpty()) { |
| return Collections.singleton("**/*.g"); |
| } |
| return includes; |
| } |
| |
| /** |
| * Given the source directory File object and the full PATH to a |
| * grammar, produce the path to the named grammar file in relative |
| * terms to the sourceDirectory. This will then allow ANTLR to |
| * produce output relative to the base of the output directory and |
| * reflect the input organization of the grammar files. |
| * |
| * @param sourceDirectory The source directory File object |
| * @param grammarFileName The full path to the input grammar file |
| * @return The path to the grammar file relative to the source directory |
| */ |
| private String findSourceSubdir(File sourceDirectory, String grammarFileName) { |
| String srcPath = sourceDirectory.getPath() + File.separator; |
| |
| if (!grammarFileName.startsWith(srcPath)) { |
| throw new IllegalArgumentException("expected " + grammarFileName + " to be prefixed with " + sourceDirectory); |
| } |
| |
| File unprefixedGrammarFileName = new File(grammarFileName.substring(srcPath.length())); |
| |
| return unprefixedGrammarFileName.getParent() + File.separator; |
| } |
| } |