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 javacc-maven-plugin Show documentation
Show all versions of 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;
/**
* Provides common services for all mojos that compile JavaCC grammar files.
*
* @author [email protected]
* @author jesse
* @version $Id: AbstractJavaCCMojo.java 8156 2008-11-26 18:20:19Z 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.4
.
*
* @parameter expression="${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 expression="${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 expression="${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 expression="${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 expression="${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 expression="${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 expression="${debugLookAhead}"
*/
private Boolean debugLookAhead;
/**
* This option is used to obtain debugging information from the generated token manager. Default value is
* false
.
*
* @parameter expression="${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 expression="${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 expression="${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 expression="${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 expression="${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 expression="${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 expression="${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 expression="${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 expression="${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 expression="${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 expression="${tokenManagerUsesParser}"
*/
private Boolean tokenManagerUsesParser;
/**
* The name of the base class for the generated Token
class. Default value is
* java.lang.Object
.
*
* @parameter expression="${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 expression="${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 expression="${sanityCheck}"
*/
private Boolean sanityCheck;
/**
* This option setting controls lookahead ambiguity checking performed by JavaCC. Default value is
* false
.
*
* @parameter expression="${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 expression="${cacheTokens}"
*/
private Boolean cacheTokens;
/**
* A flag whether to keep line and column information along with a token. Default value is true
.
*
* @parameter expression="${keepLineColumn}"
*/
private Boolean keepLineColumn;
/**
* 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
{
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();
for ( int i = 0; i < grammarInfos.length; i++ )
{
processGrammar( grammarInfos[i] );
}
getLog().info( "Processed " + grammarInfos.length + " grammar" + ( grammarInfos.length != 1 ? "s" : "" ) );
}
Collection compileSourceRoots = new LinkedHashSet( Arrays.asList( getCompileSourceRoots() ) );
for ( Iterator it = compileSourceRoots.iterator(); it.hasNext(); )
{
addSourceRoot( (File) it.next() );
}
}
/**
* 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
{
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 ( 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( File tempDirectory )
{
try
{
FileUtils.deleteDirectory( tempDirectory );
}
catch ( 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( File sourceRoot, String packageName, File tempDirectory, String updatePattern )
throws MojoExecutionException
{
try
{
Collection tempFiles = FileUtils.getFiles( tempDirectory, "*.java", null );
for ( Iterator it = tempFiles.iterator(); it.hasNext(); )
{
File tempFile = (File) it.next();
String outputPath = "";
if ( packageName.length() > 0 )
{
outputPath = packageName.replace( '.', '/' ) + '/';
}
outputPath += tempFile.getName();
File outputFile = new File( sourceRoot, outputPath );
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 ( 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 ( 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
{
String targetPrefix =
new File( this.project.getBuild().getDirectory() ).getCanonicalPath() + File.separator;
Collection sourceRoots = this.project.getCompileSourceRoots();
for ( Iterator it = sourceRoots.iterator(); it.hasNext(); )
{
File sourceRoot = new File( it.next().toString() );
if ( !sourceRoot.isAbsolute() )
{
sourceRoot = new File( this.project.getBasedir(), sourceRoot.getPath() );
}
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 ( 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( String filename )
{
Collection sourceRoots = this.nonGeneratedSourceRoots;
for ( Iterator it = sourceRoots.iterator(); it.hasNext(); )
{
File sourceRoot = (File) it.next();
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( 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( 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()
{
JavaCC javacc = new JavaCC();
javacc.setLog( getLog() );
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 );
return javacc;
}
}