org.codehaus.mojo.antlr.AbstractAntlrMojo Maven / Gradle / Ivy
The newest version!
package org.codehaus.mojo.antlr;
import static java.lang.ClassLoader.getSystemClassLoader;
import static java.util.Arrays.asList;
import static org.codehaus.plexus.util.StringUtils.isEmpty;
/*
* 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.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
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 org.apache.commons.exec.CommandLine;
import org.apache.commons.exec.DefaultExecutor;
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.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.mojo.antlr.proxy.Helper;
import org.codehaus.plexus.util.StringOutputStream;
import org.codehaus.plexus.util.StringUtils;
/**
* Base class with majority of Antlr functionalities.
*
* @author Vincent Siveton
* @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.
* See Command Line Options
*
* @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 sourceDirectory
* directory.
* See Command Line Options
*
* @parameter expression="${grammars}"
*/
protected String grammars;
/**
* Grammar list presents in the sourceDirectory directory.
* See Command Line Options
* Example:
*
*
* <grammarDefs>
* <grammar>
* <name>myGrammar.g</name>
* <glib>mySuperGrammar.g;myOtherSuperGrammar.g</glib>
* </grammar>
* </grammarDefs>
*
*
* @parameter expression="${grammarDefs}"
*/
protected org.codehaus.mojo.antlr.options.Grammar[] grammarDefs;
/**
* Launch the ParseView debugger upon parser invocation.
* See Command Line Options
*
* @parameter expression="${debug}" default-value="false"
*/
private boolean debug;
/**
* Generate a text file from your grammar with a lot of debugging info.
* See Command Line Options
*
* @parameter expression="${diagnostic}" default-value="false"
*/
private boolean diagnostic;
/**
* Have all rules call traceIn/traceOut.
* See Command Line Options
*
* @parameter expression="${trace}" default-value="false"
*/
private boolean trace;
/**
* Have parser rules call traceIn/traceOut.
* See Command Line Options
*
* @parameter expression="${traceParser}" default-value="false"
*/
private boolean traceParser;
/**
* Have lexer rules call traceIn/traceOut.
* See Command Line Options
*
* @parameter expression="${traceLexer}" default-value="false"
*/
private boolean traceLexer;
/**
* Have tree rules call traceIn/traceOut.
* See Command Line Options
*
* @parameter expression="${traceTreeParser}" default-value="false"
*/
private boolean traceTreeParser;
@Override
public File getSourceDirectory() {
return sourceDirectory;
}
@Override
public File getOutputDirectory() {
return outputDirectory;
}
/**
* @throws MojoExecutionException
*/
protected void executeAntlr() throws MojoExecutionException {
// Obviously, don't exit, or it will exit the entire build.
System.setProperty("ANTLR_DO_NOT_EXIT", "true");
// Otherwise the context loader is used to load internal classes, which is set to some realmclass nexus thing here,
// while other classes are loaded by the URL classloader we explicitly set here.
System.setProperty("ANTLR_USE_DIRECT_CLASS_LOADING", "true");
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"));
}
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 (RuntimeException e) {
if (e.getMessage().contains("ANTLR Panic")) {
// ANTLR-12
// Now basically every ANTLR version could set different message, how to handle in generic way?
// Probably only by creating an ANTLR 2.7.8 which throws a specific exception.
// On the other hand, ANTLR 2.* hasn't been updated in a decade, and the chance that it will
// ever be updated AND change that message is low.
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);
}
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().toURI().toURL() }, getSystemClassLoader())) {
classLoader.loadClass("antlr.Tool")
.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);
} catch (IOException e1) {
e1.printStackTrace();
}
}
/**
* 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 ((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("").append('\n');
msg.append(" VALUE ").append('\n');
msg.append("- OR - ").append('\n');
msg.append(" VALUE ").append('\n');
msg.append(" ").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 grammars and grammarDefs variables
*/
private org.codehaus.mojo.antlr.options.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() {
@Override
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])) {
org.codehaus.mojo.antlr.options.Grammar grammar = new org.codehaus.mojo.antlr.options.Grammar();
grammar.setName(dir[i]);
grammarList.add(grammar);
}
}
}
} else {
if (!grammarSet.contains(currentGrammar)) {
org.codehaus.mojo.antlr.options.Grammar grammar = new org.codehaus.mojo.antlr.options.Grammar();
grammar.setName(currentGrammar);
grammarList.add(grammar);
}
}
}
}
}
if (grammarDefs != null) {
grammarList.addAll(asList(grammarDefs));
}
return (org.codehaus.mojo.antlr.options.Grammar[]) grammarList.toArray(new org.codehaus.mojo.antlr.options.Grammar[0]);
}
public static class NoAntlrDependencyDefinedException extends MojoExecutionException {
public NoAntlrDependencyDefinedException(String s) {
super(s);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy