org.codehaus.mojo.javacc.AbstractJavaCCMojo Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ph-javacc-maven-plugin Show documentation
Show all versions of ph-javacc-maven-plugin Show documentation
Maven 3 Plugin for processing JavaCC grammar files.
package org.codehaus.mojo.javacc;
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashSet;
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 org.codehaus.plexus.util.FileUtils;
import org.codehaus.plexus.util.SelectorUtils;
import org.codehaus.plexus.util.StringUtils;
/**
* Provides common services for all mojos that compile JavaCC grammar files.
*
* @author [email protected]
* @author jesse
* @version $Id: AbstractJavaCCMojo.java 10774 2009-09-26 11:40:48Z bentmann $
*/
public abstract class AbstractJavaCCMojo extends AbstractMojo
{
/**
* The current Maven project.
*
* @parameter default-value="${project}"
* @readonly
* @required
*/
private MavenProject project;
/**
* The set of compile source roots whose contents are not generated as part of
* the build, i.e. those that usually reside somewhere below "${basedir}/src"
* in the project structure. Files in these source roots are owned by the user
* and must not be overwritten with generated files.
*/
private Collection nonGeneratedSourceRoots;
/**
* The Java version for which to generate source code. Default value is
* 1.5
for plugin version 2.6+ and 1.4
in older
* versions.
*
* @parameter property=jdkVersion
* @since 2.4
*/
private String jdkVersion;
/**
* The number of tokens to look ahead before making a decision at a choice
* point during parsing. The default value is 1
.
*
* @parameter property=lookAhead
*/
private Integer lookAhead;
/**
* This is the number of tokens considered in checking choices of the form "A
* | B | ..." for ambiguity. Default value is 2
.
*
* @parameter property=choiceAmbiguityCheck
*/
private Integer choiceAmbiguityCheck;
/**
* This is the number of tokens considered in checking all other kinds of
* choices (i.e., of the forms "(A)*", "(A)+", and "(A)?") for ambiguity.
* Default value is 1
.
*
* @parameter property=otherAmbiguityCheck
*/
private Integer otherAmbiguityCheck;
/**
* If true
, all methods and class variables are specified as
* static in the generated parser and token manager. This allows only one
* parser object to be present, but it improves the performance of the parser.
* Default value is true
.
*
* @parameter property=isStatic
*/
private Boolean isStatic;
/**
* This option is used to obtain debugging information from the generated
* parser. Setting this option to true
causes the parser to
* generate a trace of its actions. Default value is false
.
*
* @parameter property=debugParser
*/
private Boolean debugParser;
/**
* This is a boolean option whose default value is false
. Setting
* this option to true
causes the parser to generate all the
* tracing information it does when the option debugParser
is
* true
, and in addition, also causes it to generated a trace of
* actions performed during lookahead operation.
*
* @parameter property=debugLookAhead
*/
private Boolean debugLookAhead;
/**
* This option is used to obtain debugging information from the generated
* token manager. Default value is false
.
*
* @parameter property=debugTokenManager
*/
private Boolean debugTokenManager;
/**
* Setting it to false
causes errors due to parse errors to be
* reported in somewhat less detail. Default value is true
.
*
* @parameter property=errorReporting
*/
private Boolean errorReporting;
/**
* When set to true
, the generated parser uses an input stream
* object that processes Java Unicode escapes (\
u
* xxxx) before sending characters to the token manager. Default value
* is false
.
*
* @parameter property=javaUnicodeEscape
*/
private Boolean javaUnicodeEscape;
/**
* When set to true
, the generated parser uses uses an input
* stream object that reads Unicode files. By default, ASCII files are
* assumed. Default value is false
.
*
* @parameter property=unicodeInput
*/
private Boolean unicodeInput;
/**
* Setting this option to true
causes the generated token manager
* to ignore case in the token specifications and the input files. Default
* value is false
.
*
* @parameter property=ignoreCase
*/
private Boolean ignoreCase;
/**
* When set to true
, every call to the token manager's method
* getNextToken()
(see the description of the
* Java Compiler
* Compiler API) will cause a call to a user-defined method
* CommonTokenAction()
after the token has been scanned in by the
* token manager. Default value is false
.
*
* @parameter property=commonTokenAction
*/
private Boolean commonTokenAction;
/**
* The default action is to generate a token manager that works on the
* specified grammar tokens. If this option is set to true
, then
* the parser is generated to accept tokens from any token manager of type
* TokenManager
- this interface is generated into the generated
* parser directory. Default value is false
.
*
* @parameter property=userTokenManager
*/
private Boolean userTokenManager;
/**
* This flag controls whether the token manager will read characters from a
* character stream reader as defined by the options
* javaUnicodeEscape
and unicodeInput
or whether the
* token manager reads from a user-supplied implementation of
* CharStream
. Default value is false
.
*
* @parameter property=userCharStream
*/
private Boolean userCharStream;
/**
* A flag that controls whether the parser file (*Parser.java
)
* should be generated or not. If set to false
, only the token
* manager is generated. Default value is true
.
*
* @parameter property=buildParser
*/
private Boolean buildParser;
/**
* A flag that controls whether the token manager file (
* *TokenManager.java
) should be generated or not. Setting this
* to false
can speed up the generation process if only the
* parser part of the grammar changed. Default value is true
.
*
* @parameter property=buildTokenManager
*/
private Boolean buildTokenManager;
/**
* When set to true
, the generated token manager will include a
* field called parser
that references the instantiating parser
* instance. Default value is false
.
*
* @parameter property=tokenManagerUsesParser
*/
private Boolean tokenManagerUsesParser;
/**
* The name of the base class for the generated Token
class.
* Default value is java.lang.Object
.
*
* @parameter property=tokenExtends
* @since 2.5
*/
private String tokenExtends;
/**
* The name of a custom factory class used to create Token
* objects. This class must have a method with the signature
* public static Token newToken(int ofKind, String image)
. By
* default, tokens are created by calling Token.newToken()
.
*
* @parameter property=tokenFactory
* @since 2.5
*/
private String tokenFactory;
/**
* Enables/disables many syntactic and semantic checks on the grammar file
* during parser generation. Default value is true
.
*
* @parameter property=sanityCheck
*/
private Boolean sanityCheck;
/**
* This option setting controls lookahead ambiguity checking performed by
* JavaCC. Default value is false
.
*
* @parameter property=forceLaCheck
*/
private Boolean forceLaCheck;
/**
* Setting this option to true
causes the generated parser to
* lookahead for extra tokens ahead of time. Default value is
* false
.
*
* @parameter property=cacheTokens
*/
private Boolean cacheTokens;
/**
* A flag whether to keep line and column information along with a token.
* Default value is true
.
*
* @parameter property=keepLineColumn
*/
private Boolean keepLineColumn;
/**
* A flag whether the generated support classes of the parser should have
* public or package-private visibility. Default value is true
.
*
* @parameter property=supportClassVisibilityPublic
* @since 2.6
*/
private Boolean supportClassVisibilityPublic;
/**
* The file encoding to use for reading the grammar files.
*
* @parameter property=grammarEncoding
* default-value="${project.build.sourceEncoding}"
* @since 2.6
*/
private String grammarEncoding;
/**
* Gets the file encoding of the grammar files.
*
* @return The file encoding of the grammar files or null
if the
* user did not specify this mojo parameter.
*/
protected String getGrammarEncoding ()
{
return this.grammarEncoding;
}
/**
* Gets the Java version for which to generate source code.
*
* @return The Java version for which to generate source code, will be
* null
if the user did not specify this mojo parameter.
*/
protected String getJdkVersion ()
{
return this.jdkVersion;
}
/**
* Gets the flag whether to generate static parser.
*
* @return The flag whether to generate static parser, will be
* null
if the user did not specify this mojo parameter.
*/
protected Boolean getIsStatic ()
{
return this.isStatic;
}
/**
* Gets the absolute path to the directory where the grammar files are
* located.
*
* @return The absolute path to the directory where the grammar files are
* located, never null
.
*/
protected abstract File getSourceDirectory ();
/**
* Gets a set of Ant-like inclusion patterns used to select files from the
* source directory for processing.
*
* @return A set of Ant-like inclusion patterns used to select files from the
* source directory for processing, can be null
if all
* files should be included.
*/
protected abstract String [] getIncludes ();
/**
* Gets a set of Ant-like exclusion patterns used to unselect files from the
* source directory for processing.
*
* @return A set of Ant-like inclusion patterns used to unselect files from
* the source directory for processing, can be null
if no
* files should be excluded.
*/
protected abstract String [] getExcludes ();
/**
* Gets the absolute path to the directory where the generated Java files for
* the parser will be stored.
*
* @return The absolute path to the directory where the generated Java files
* for the parser will be stored, never null
.
*/
protected abstract File getOutputDirectory ();
/**
* Gets the granularity in milliseconds of the last modification date for
* testing whether a source needs recompilation.
*
* @return The granularity in milliseconds of the last modification date for
* testing whether a source needs recompilation.
*/
protected abstract int getStaleMillis ();
/**
* Gets all the output directories to register with the project for
* compilation.
*
* @return The compile source roots to register with the project, never
* null
.
*/
protected abstract File [] getCompileSourceRoots ();
/**
* Gets the package into which the generated parser files should be stored.
*
* @return The package into which the generated parser files should be stored,
* can be null
to use the package declaration from the
* grammar file.
*/
// TODO: Once the parameter "packageName" from the javacc mojo has been
// deleted, remove this method, too.
protected String getParserPackage ()
{
return null;
}
/**
* Execute the tool.
*
* @throws MojoExecutionException
* If the invocation of the tool failed.
* @throws MojoFailureException
* If the tool reported a non-zero exit code.
*/
public void execute () throws MojoExecutionException, MojoFailureException
{
final GrammarInfo [] grammarInfos = scanForGrammars ();
if (grammarInfos == null)
{
getLog ().info ("Skipping non-existing source directory: " + getSourceDirectory ());
return;
}
else
if (grammarInfos.length <= 0)
{
getLog ().info ("Skipping - all parsers are up to date");
}
else
{
determineNonGeneratedSourceRoots ();
if (StringUtils.isEmpty (grammarEncoding))
{
getLog ().warn ("File encoding for grammars has not been configured" +
", using platform default encoding, i.e. build is platform dependent!");
}
for (final GrammarInfo grammarInfo : grammarInfos)
{
processGrammar (grammarInfo);
}
getLog ().info ("Processed " + grammarInfos.length + " grammar" + (grammarInfos.length != 1 ? "s" : ""));
}
final Collection compileSourceRoots = new LinkedHashSet <> (Arrays.asList (getCompileSourceRoots ()));
for (final File file : compileSourceRoots)
{
addSourceRoot (file);
}
}
/**
* Passes the specified grammar file through the tool.
*
* @param grammarInfo
* The grammar info describing the grammar file to process, must not be
* null
.
* @throws MojoExecutionException
* If the invocation of the tool failed.
* @throws MojoFailureException
* If the tool reported a non-zero exit code.
*/
protected abstract void processGrammar (GrammarInfo grammarInfo) throws MojoExecutionException, MojoFailureException;
/**
* Scans the configured source directory for grammar files which need
* processing.
*
* @return An array of grammar infos describing the found grammar files or
* null
if the source directory does not exist.
* @throws MojoExecutionException
* If the source directory could not be scanned.
*/
private GrammarInfo [] scanForGrammars () throws MojoExecutionException
{
if (!getSourceDirectory ().isDirectory ())
{
return null;
}
GrammarInfo [] grammarInfos;
getLog ().debug ("Scanning for grammars: " + getSourceDirectory ());
try
{
final GrammarDirectoryScanner scanner = new GrammarDirectoryScanner ();
scanner.setSourceDirectory (getSourceDirectory ());
scanner.setIncludes (getIncludes ());
scanner.setExcludes (getExcludes ());
scanner.setOutputDirectory (getOutputDirectory ());
scanner.setParserPackage (getParserPackage ());
scanner.setStaleMillis (getStaleMillis ());
scanner.scan ();
grammarInfos = scanner.getIncludedGrammars ();
}
catch (final Exception e)
{
throw new MojoExecutionException ("Failed to scan for grammars: " + getSourceDirectory (), e);
}
getLog ().debug ("Found grammars: " + Arrays.asList (grammarInfos));
return grammarInfos;
}
/**
* Gets a temporary directory within the project's build directory.
*
* @return The path to the temporary directory, never null
.
*/
protected File getTempDirectory ()
{
return new File (this.project.getBuild ().getDirectory (), "javacc-" + System.currentTimeMillis ());
}
/**
* Deletes the specified temporary directory.
*
* @param tempDirectory
* The directory to delete, must not be null
.
*/
protected void deleteTempDirectory (final File tempDirectory)
{
try
{
FileUtils.deleteDirectory (tempDirectory);
}
catch (final IOException e)
{
getLog ().warn ("Failed to delete temporary directory: " + tempDirectory, e);
}
}
/**
* Scans the filesystem for output files and copies them to the specified
* compile source root. An output file is only copied to the compile source
* root if it doesn't already exist in another compile source root. This
* prevents duplicate class errors during compilation in case the user
* provided customized files in src/main/java
or similar.
*
* @param packageName
* The name of the destination package for the output files, must not
* be null
.
* @param sourceRoot
* The (absolute) path to the compile source root into which the output
* files should eventually be copied, must not be null
.
* @param tempDirectory
* The (absolute) path to the directory to scan for generated output
* files, must not be null
.
* @param updatePattern
* A glob pattern that matches the (simple) names of those files which
* should always be updated in case we are outputting directly into
* src/main/java
, may be null
. A leading "!"
* may be used to negate the pattern.
* @throws MojoExecutionException
* If the output files could not be copied.
*/
protected void copyGrammarOutput (final File sourceRoot,
final String packageName,
final File tempDirectory,
final String updatePattern) throws MojoExecutionException
{
try
{
final Collection tempFiles = FileUtils.getFiles (tempDirectory, "*.java", null);
for (final Iterator it = tempFiles.iterator (); it.hasNext ();)
{
final File tempFile = (File) it.next ();
String outputPath = "";
if (packageName.length () > 0)
{
outputPath = packageName.replace ('.', '/') + '/';
}
outputPath += tempFile.getName ();
final File outputFile = new File (sourceRoot, outputPath);
final File sourceFile = findSourceFile (outputPath);
boolean alwaysUpdate = false;
if (updatePattern != null && sourceFile != null)
{
if (updatePattern.startsWith ("!"))
{
alwaysUpdate = !SelectorUtils.match (updatePattern.substring (1), tempFile.getName ());
}
else
{
alwaysUpdate = SelectorUtils.match (updatePattern, tempFile.getName ());
}
}
if (sourceFile == null || (alwaysUpdate && sourceFile.equals (outputFile)))
{
getLog ().debug ("Copying generated file: " + outputPath);
try
{
FileUtils.copyFile (tempFile, outputFile);
}
catch (final IOException e)
{
throw new MojoExecutionException ("Failed to copy generated source file to output directory:" +
tempFile +
" -> " +
outputFile,
e);
}
}
else
{
getLog ().debug ("Skipping customized file: " + outputPath);
}
}
}
catch (final IOException e)
{
throw new MojoExecutionException ("Failed to copy generated source files", e);
}
}
/**
* Determines those compile source roots of the project that do not reside
* below the project's build directories. These compile source roots are
* assumed to contain hand-crafted sources that must not be overwritten with
* generated files. In most cases, this is simply
* "${project.build.sourceDirectory}".
*
* @throws MojoExecutionException
* If the compile source rotos could not be determined.
*/
private void determineNonGeneratedSourceRoots () throws MojoExecutionException
{
this.nonGeneratedSourceRoots = new LinkedHashSet <> ();
try
{
final String targetPrefix = new File (this.project.getBuild ().getDirectory ()).getCanonicalPath () +
File.separator;
final Collection sourceRoots = this.project.getCompileSourceRoots ();
for (final Iterator it = sourceRoots.iterator (); it.hasNext ();)
{
File sourceRoot = new File (it.next ().toString ());
if (!sourceRoot.isAbsolute ())
{
sourceRoot = new File (this.project.getBasedir (), sourceRoot.getPath ());
}
final String sourcePath = sourceRoot.getCanonicalPath ();
if (!sourcePath.startsWith (targetPrefix))
{
this.nonGeneratedSourceRoots.add (sourceRoot);
getLog ().debug ("Non-generated compile source root: " + sourceRoot);
}
else
{
getLog ().debug ("Generated compile source root: " + sourceRoot);
}
}
}
catch (final IOException e)
{
throw new MojoExecutionException ("Failed to determine non-generated source roots", e);
}
}
/**
* Determines whether the specified source file is already present in any of
* the compile source roots registered with the current Maven project.
*
* @param filename
* The source filename to check, relative to a source root, must not be
* null
.
* @return The (absolute) path to the existing source file if any,
* null
otherwise.
*/
private File findSourceFile (final String filename)
{
final Collection sourceRoots = this.nonGeneratedSourceRoots;
for (final File sourceRoot : sourceRoots)
{
final File sourceFile = new File (sourceRoot, filename);
if (sourceFile.exists ())
{
return sourceFile;
}
}
return null;
}
/**
* Determines whether the specified directory denotes a compile source root of
* the current project.
*
* @param directory
* The directory to check, must not be null
.
* @return true
if the specified directory is a compile source
* root of the project, false
otherwise.
*/
protected boolean isSourceRoot (final File directory)
{
return this.nonGeneratedSourceRoots.contains (directory);
}
/**
* Registers the specified directory as a compile source root for the current
* project.
*
* @param directory
* The absolute path to the source root, must not be null
.
*/
private void addSourceRoot (final File directory)
{
if (this.project != null)
{
getLog ().debug ("Adding compile source root: " + directory);
this.project.addCompileSourceRoot (directory.getAbsolutePath ());
}
}
/**
* Creates a new facade to invoke JavaCC. Most options for the invocation are
* derived from the current values of the corresponding mojo parameters. The
* caller is responsible to set the input file and output directory on the
* returned facade.
*
* @return The facade for the tool invocation, never null
.
*/
protected JavaCC newJavaCC ()
{
final JavaCC javacc = new JavaCC ();
javacc.setLog (getLog ());
javacc.setGrammarEncoding (this.grammarEncoding);
javacc.setJdkVersion (this.jdkVersion);
javacc.setStatic (this.isStatic);
javacc.setBuildParser (this.buildParser);
javacc.setBuildTokenManager (this.buildTokenManager);
javacc.setCacheTokens (this.cacheTokens);
javacc.setChoiceAmbiguityCheck (this.choiceAmbiguityCheck);
javacc.setCommonTokenAction (this.commonTokenAction);
javacc.setDebugLookAhead (this.debugLookAhead);
javacc.setDebugParser (this.debugParser);
javacc.setDebugTokenManager (this.debugTokenManager);
javacc.setErrorReporting (this.errorReporting);
javacc.setForceLaCheck (this.forceLaCheck);
javacc.setIgnoreCase (this.ignoreCase);
javacc.setJavaUnicodeEscape (this.javaUnicodeEscape);
javacc.setKeepLineColumn (this.keepLineColumn);
javacc.setLookAhead (this.lookAhead);
javacc.setOtherAmbiguityCheck (this.otherAmbiguityCheck);
javacc.setSanityCheck (this.sanityCheck);
javacc.setTokenManagerUsesParser (this.tokenManagerUsesParser);
javacc.setTokenExtends (this.tokenExtends);
javacc.setTokenFactory (this.tokenFactory);
javacc.setUnicodeInput (this.unicodeInput);
javacc.setUserCharStream (this.userCharStream);
javacc.setUserTokenManager (this.userTokenManager);
javacc.setSupportClassVisibilityPublic (this.supportClassVisibilityPublic);
return javacc;
}
}