All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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