package org.codehaus.mojo.antlr;
/*
* 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.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.regex.Pattern;
import java.lang.reflect.InvocationTargetException;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.MavenProjectHelper;
import org.apache.commons.exec.DefaultExecutor;
import org.apache.commons.exec.CommandLine;
import org.codehaus.mojo.antlr.options.Grammar;
import org.codehaus.mojo.antlr.proxy.Helper;
import org.codehaus.mojo.antlr.metadata.MetadataExtracter;
import org.codehaus.mojo.antlr.metadata.XRef;
import org.codehaus.mojo.antlr.plan.GenerationPlan;
import org.codehaus.mojo.antlr.plan.GenerationPlanBuilder;
import org.codehaus.plexus.util.StringOutputStream;
import org.codehaus.plexus.util.StringUtils;
/**
* Base class with majority of Antlr functionalities.
*
* @author <a href="mailto:vincent.siveton@gmail.com">Vincent Siveton</a>
* @version $Id$
*/
public abstract class AbstractAntlrMojo
extends AbstractMojo
implements Environment
{
// ----------------------------------------------------------------------
// Mojo parameters
// ----------------------------------------------------------------------
/**
* Specifies the Antlr directory containing grammar files.
*
* @parameter default-value="${basedir}/src/main/antlr"
*/
protected File sourceDirectory;
/**
* The Maven Project Object
*
* @parameter expression="${project}"
* @readonly
*/
protected MavenProject project;
/**
* The maven project's helper.
*
* @component role="org.apache.maven.project.MavenProjectHelper"
* @readonly
*/
private MavenProjectHelper projectHelper;
// ----------------------------------------------------------------------
// Antlr parameters
// See http://www.antlr2.org/doc/options.html#Command%20Line%20Options
// ----------------------------------------------------------------------
/**
* Specifies the destination directory where Antlr should generate files. <br/>
* See <a href="http://www.antlr2.org/doc/options.html#Command%20Line%20Options">Command Line Options</a>
*
* @parameter default-value="${project.build.directory}/generated-sources/antlr"
*/
protected File outputDirectory;
/**
* Comma separated grammar file names or grammar pattern file names present in the <code>sourceDirectory</code>
* directory. <br/>
* See <a href="http://www.antlr2.org/doc/options.html#Command%20Line%20Options">Command Line Options</a>
*
* @parameter expression="${grammars}"
*/
protected String grammars;
/**
* Grammar list presents in the <code>sourceDirectory</code> directory. <br/>
* See <a href="http://www.antlr2.org/doc/options.html#Command%20Line%20Options">Command Line Options</a> <br/>
* Example:
*
* <pre>
* <grammarDefs><br/>
* <grammar><br/>
* <name>myGrammar.g</name><br/>
* <glib>mySuperGrammar.g;myOtherSuperGrammar.g</glib><br/>
* </grammar><br/>
* </grammarDefs>
* </pre>
*
* @parameter expression="${grammarDefs}"
*/
protected Grammar[] grammarDefs;
/**
* Launch the ParseView debugger upon parser invocation. <br/>
* See <a href="http://www.antlr2.org/doc/options.html#Command%20Line%20Options">Command Line Options</a>
*
* @parameter expression="${debug}" default-value="false"
*/
private boolean debug;
/**
* Generate a text file from your grammar with a lot of debugging info. <br/>
* See <a href="http://www.antlr2.org/doc/options.html#Command%20Line%20Options">Command Line Options</a>
*
* @parameter expression="${diagnostic}" default-value="false"
*/
private boolean diagnostic;
/**
* Have all rules call traceIn/traceOut. <br/>
* See <a href="http://www.antlr2.org/doc/options.html#Command%20Line%20Options">Command Line Options</a>
*
* @parameter expression="${trace}" default-value="false"
*/
private boolean trace;
/**
* Have parser rules call traceIn/traceOut. <br/>
* See <a href="http://www.antlr2.org/doc/options.html#Command%20Line%20Options">Command Line Options</a>
*
* @parameter expression="${traceParser}" default-value="false"
*/
private boolean traceParser;
/**
* Have lexer rules call traceIn/traceOut. <br/>
* See <a href="http://www.antlr2.org/doc/options.html#Command%20Line%20Options">Command Line Options</a>
*
* @parameter expression="${traceLexer}" default-value="false"
*/
private boolean traceLexer;
/**
* Have tree rules call traceIn/traceOut. <br/>
* See <a href="http://www.antlr2.org/doc/options.html#Command%20Line%20Options">Command Line Options</a>
*
* @parameter expression="${traceTreeParser}" default-value="false"
*/
private boolean traceTreeParser;
public File getSourceDirectory()
{
return sourceDirectory;
}
public File getOutputDirectory()
{
return outputDirectory;
}
/**
* @throws MojoExecutionException
*/
protected void executeAntlr()
throws MojoExecutionException
{
validateParameters();
Artifact antlrArtifact = locateAntlrArtifact();
MetadataExtracter metadataExtracter = new MetadataExtracter( this, new Helper( antlrArtifact ) );
XRef metadata = metadataExtracter.processMetadata( getGrammars() );
Iterator generationPlans = new GenerationPlanBuilder( this ).buildGenerationPlans( metadata ).iterator();
while ( generationPlans.hasNext() )
{
final GenerationPlan plan = (GenerationPlan) generationPlans.next();
if ( !plan.isOutOfDate() )
{
getLog().info( "grammar [" + plan.getId() + "] was up-to-date; skipping" );
continue;
}
getLog().info( "performing grammar generation [" + plan.getId() + "]" );
performGeneration( plan, antlrArtifact );
}
if ( project != null )
{
projectHelper.addResource( project, outputDirectory.getAbsolutePath(),
Collections.singletonList( "**/**.txt" ), new ArrayList() );
project.addCompileSourceRoot( outputDirectory.getAbsolutePath() );
}
}
protected final Artifact locateAntlrArtifact()
throws NoAntlrDependencyDefinedException
{
Artifact antlrArtifact = null;
if ( project.getCompileArtifacts() != null )
{
Iterator projectArtifacts = project.getCompileArtifacts().iterator();
while ( projectArtifacts.hasNext() )
{
final Artifact artifact = (Artifact) projectArtifacts.next();
if ( "antlr".equals( artifact.getGroupId() )
&& ( "antlr".equals( artifact.getArtifactId() ) || "antlr-all".equals( artifact.getArtifactId() ) ) )
{
antlrArtifact = artifact;
break;
}
}
}
if ( antlrArtifact == null )
{
throw new NoAntlrDependencyDefinedException( "project did not define antlr:antlr depenency" );
}
// TODO : enforce specific version range; e.g. [2.7,3.0) ???
return antlrArtifact;
}
protected void performGeneration( GenerationPlan plan, Artifact antlrArtifact )
throws MojoExecutionException
{
if ( !plan.getGenerationDirectory().getParentFile().exists() )
{
plan.getGenerationDirectory().getParentFile().mkdirs();
}
// ----------------------------------------------------------------------
// Wrap arguments
// Note: grammar file should be last
// ----------------------------------------------------------------------
List arguments = new LinkedList();
addArgIf( arguments, debug, "-debug" );
addArgIf( arguments, diagnostic, "-diagnostic" );
addArgIf( arguments, trace, "-trace" );
addArgIf( arguments, traceParser, "-traceParser" );
addArgIf( arguments, traceLexer, "-traceLexer" );
addArgIf( arguments, traceTreeParser, "-traceTreeParser" );
addArgs( arguments );
arguments.add( "-o" );
arguments.add( plan.getGenerationDirectory().getPath() );
if ( plan.getCollectedSuperGrammarIds().size() > 0 )
{
arguments.add( "-glib" );
StringBuffer buffer = new StringBuffer();
Iterator ids = plan.getCollectedSuperGrammarIds().iterator();
while ( ids.hasNext() )
{
buffer.append( new File( sourceDirectory, (String) ids.next() ) );
if ( ids.hasNext() )
{
buffer.append( ';' );
}
}
arguments.add( buffer.toString() );
}
arguments.add( plan.getSource().getPath() );
String[] args = (String[]) arguments.toArray( new String[arguments.size()] );
if ( plan.getImportVocabTokenTypesDirectory() != null
&& !plan.getImportVocabTokenTypesDirectory().equals( plan.getGenerationDirectory() ) )
{
// we need to spawn a new process to properly set up PWD
CommandLine commandLine = new CommandLine( "java" );
commandLine.addArgument( "-classpath", false );
commandLine.addArgument( generateClasspathForProcessSpawning( antlrArtifact ), true );
commandLine.addArgument( "antlr.Tool", false );
commandLine.addArguments( args, true );
DefaultExecutor executor = new DefaultExecutor();
executor.setWorkingDirectory( plan.getImportVocabTokenTypesDirectory() );
try
{
executor.execute( commandLine );
}
catch ( IOException e )
{
getLog().warn( "Error spawning process to execute antlr tool : " + e.getMessage() );
}
return;
}
// ----------------------------------------------------------------------
// Call Antlr
// ----------------------------------------------------------------------
if ( getLog().isDebugEnabled() )
{
getLog().debug( "antlr args=\n" + StringUtils.join( args, "\n" ) );
}
boolean failedSetManager = false;
SecurityManager oldSm = null;
try
{
oldSm = System.getSecurityManager();
System.setSecurityManager( NoExitSecurityManager.INSTANCE );
}
catch ( SecurityException ex )
{
// ANTLR-12
oldSm = null;
failedSetManager = true;
// ignore, in embedded environment the security manager can already be set.
// in such a case assume the exit call is handled properly..
getLog().warn( "Cannot set custom SecurityManager. "
+ "Antlr's call to System.exit() can cause application shutdown "
+ "if not handled by the current SecurityManager." );
}
String originalUserDir = null;
if ( plan.getImportVocabTokenTypesDirectory() != null )
{
originalUserDir = System.getProperty( "user.dir" );
System.setProperty( "user.dir", plan.getImportVocabTokenTypesDirectory().getPath() );
}
PrintStream oldErr = System.err;
OutputStream errOS = new StringOutputStream();
PrintStream err = new PrintStream( errOS );
System.setErr( err );
try
{
executeAntlrInIsolatedClassLoader( (String[]) arguments.toArray( new String[0] ), antlrArtifact );
}
catch ( SecurityException e )
{
if ( e.getMessage().equals( "exitVM-0" )
|| e.getClass().getName().equals( "org.netbeans.core.execution.ExitSecurityException" ) ) // netbeans
// IDE Sec
// Manager.
{
// ANTLR-12
// now basically every secutiry manager could set different message, how to handle in generic way?
// probably only by external execution
// / in case of NetBeans SecurityManager, it's not possible to distinguish exit codes, rather swallow
// than fail.
getLog().debug( e );
}
else
{
throw new MojoExecutionException( "Antlr execution failed: " + e.getMessage() + "\n Error output:\n"
+ errOS, e );
}
}
finally
{
if ( originalUserDir != null )
{
System.setProperty( "user.dir", originalUserDir );
}
if ( !failedSetManager )
{
System.setSecurityManager( oldSm );
}
System.setErr( oldErr );
System.err.println( errOS.toString() );
}
}
private String generateClasspathForProcessSpawning( Artifact antlrArtifact )
{
// todo : is maven by itself enough for the generation???
return antlrArtifact.getFile().getPath();
}
private void executeAntlrInIsolatedClassLoader( String[] args, Artifact antlrArtifact )
throws MojoExecutionException
{
try
{
URLClassLoader classLoader =
new URLClassLoader( new URL[] { antlrArtifact.getFile().toURL() }, ClassLoader.getSystemClassLoader() );
Class toolClass = classLoader.loadClass( "antlr.Tool" );
toolClass.getMethod( "main", new Class[] { String[].class } ).invoke( null, new Object[] { args } );
}
catch ( MalformedURLException e )
{
throw new MojoExecutionException( "Unable to resolve antlr:antlr artifact url", e );
}
catch ( ClassNotFoundException e )
{
throw new MojoExecutionException( "could not locate antlr.Tool class" );
}
catch ( NoSuchMethodException e )
{
throw new MojoExecutionException( "error locating antlt.Tool#main", e );
}
catch ( InvocationTargetException e )
{
throw new MojoExecutionException( "error perforing antlt.Tool#main", e.getTargetException() );
}
catch ( IllegalAccessException e )
{
throw new MojoExecutionException( "error perforing antlt.Tool#main", e );
}
}
/**
* Add arguments to be included in Antlr call
*
* @param arguments
*/
protected abstract void addArgs( List arguments );
/**
* Convenience method to add an argument
*
* @param arguments
* @param b
* @param value
*/
protected static void addArgIf( List arguments, boolean b, String value )
{
if ( b )
{
arguments.add( value );
}
}
/**
* @param grammar
* @param outputDir
* @return generated file
* @throws IOException
*/
private File getGeneratedFile( String grammar, File outputDir )
throws IOException
{
String generatedFileName = null;
String packageName = "";
BufferedReader in = new BufferedReader( new FileReader( grammar ) );
String line;
while ( ( line = in.readLine() ) != null )
{
line = line.trim();
int extendsIndex = line.indexOf( " extends " );
if ( line.startsWith( "class " ) && extendsIndex > -1 )
{
generatedFileName = line.substring( 6, extendsIndex ).trim();
break;
}
else if ( line.startsWith( "package" ) )
{
packageName = line.substring( 8 ).trim();
}
}
in.close();
if ( generatedFileName == null )
{
throw new IOException( "Unable to generate the output file name: is the grammar '" + grammar + "' valide?" );
}
File genFile = null;
if ( "".equals( packageName ) )
{
genFile = new File( outputDir, generatedFileName + ".java" );
}
else
{
String packagePath = packageName.replace( '.', File.separatorChar );
packagePath = packagePath.replace( ';', File.separatorChar );
genFile = new File( new File( outputDir, packagePath ), generatedFileName + ".java" );
}
return genFile;
}
/**
* grammars or grammarDefs parameters is required
*
* @throws MojoExecutionException
*/
private void validateParameters()
throws MojoExecutionException
{
if ( ( StringUtils.isEmpty( grammars ) ) && ( ( grammarDefs == null ) || ( grammarDefs.length == 0 ) ) )
{
StringBuffer msg = new StringBuffer();
msg.append( "Antlr plugin parameters are invalid/missing." ).append( '\n' );
msg.append( "Inside the definition for plugin 'antlr-maven-plugin' specify the following:" ).append( '\n' );
msg.append( '\n' );
msg.append( "<configuration>" ).append( '\n' );
msg.append( " <grammars>VALUE</grammars>" ).append( '\n' );
msg.append( "- OR - " ).append( '\n' );
msg.append( " <grammarDefs>VALUE</grammarDefs>" ).append( '\n' );
msg.append( "</configuration>" ).append( '\n' );
throw new MojoExecutionException( msg.toString() );
}
}
/**
* Get the list of all grammars to be compiled. The grammars variable can be a list of file or patterns. For
* instance, one can use *.g instead of a full list of grammar names. Be aware that sometime the grammar order is
* important, and that patterns won't keep this order, but we can still combine both elements( ordered names first,
* then the patterns). File name won't be added twice in the list of files.
*
* @return an array of grammar from <code>grammars</code> and <code>grammarDefs</code> variables
*/
private Grammar[] getGrammars()
{
List grammarList = new ArrayList();
Set grammarSet = new HashSet();
if ( StringUtils.isNotEmpty( grammars ) )
{
StringTokenizer st = new StringTokenizer( grammars, ", " );
while ( st.hasMoreTokens() )
{
String currentGrammar = st.nextToken().trim();
if ( StringUtils.isNotEmpty( currentGrammar ) )
{
// Check if some pattern has been used
if ( ( currentGrammar.indexOf( '*' ) != -1 ) || ( currentGrammar.indexOf( '?' ) != -1 ) )
{
// We first have to 'protect' the '.', and transform patterns
// to regexp, substituting '*' to '.*' and '?' to '.'
final String transformedGrammar =
currentGrammar.replaceAll( "\\.", "\\\\." ).replaceAll( "\\*", ".*" ).replaceAll( "\\?",
"." );
// Filter the source directory
String[] dir = sourceDirectory.list( new FilenameFilter()
{
public boolean accept( File dir, String s )
{
return Pattern.matches( transformedGrammar, s );
}
} );
if ( ( dir != null ) && ( dir.length != 0 ) )
{
for ( int i = 0; i < dir.length; i++ )
{
// Just add fles which are not in the set
// of files already seen.
if ( !grammarSet.contains( dir[i] ) )
{
Grammar grammar = new Grammar();
grammar.setName( dir[i] );
grammarList.add( grammar );
}
}
}
}
else
{
if ( !grammarSet.contains( currentGrammar ) )
{
Grammar grammar = new Grammar();
grammar.setName( currentGrammar );
grammarList.add( grammar );
}
}
}
}
}
if ( grammarDefs != null )
{
grammarList.addAll( Arrays.asList( grammarDefs ) );
}
return (Grammar[]) grammarList.toArray( new Grammar[0] );
}
public static class NoAntlrDependencyDefinedException
extends MojoExecutionException
{
public NoAntlrDependencyDefinedException( String s )
{
super( s );
}
}
}