| /* |
| * 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. |
| * |
| */ |
| |
| package org.apache.tools.ant.taskdefs.optional; |
| |
| import java.io.BufferedReader; |
| import java.io.ByteArrayOutputStream; |
| import java.io.File; |
| import java.io.FileReader; |
| import java.io.IOException; |
| |
| import org.apache.tools.ant.AntClassLoader; |
| import org.apache.tools.ant.BuildException; |
| import org.apache.tools.ant.Project; |
| import org.apache.tools.ant.Task; |
| import org.apache.tools.ant.taskdefs.Execute; |
| import org.apache.tools.ant.taskdefs.LogOutputStream; |
| import org.apache.tools.ant.taskdefs.PumpStreamHandler; |
| import org.apache.tools.ant.taskdefs.condition.Os; |
| import org.apache.tools.ant.types.Commandline; |
| import org.apache.tools.ant.types.CommandlineJava; |
| import org.apache.tools.ant.types.Path; |
| import org.apache.tools.ant.util.FileUtils; |
| import org.apache.tools.ant.util.JavaEnvUtils; |
| import org.apache.tools.ant.util.LoaderUtils; |
| import org.apache.tools.ant.util.TeeOutputStream; |
| |
| /** |
| * Invokes the ANTLR Translator generator on a grammar file. |
| * |
| */ |
| public class ANTLR extends Task { |
| |
| private CommandlineJava commandline = new CommandlineJava(); |
| |
| /** the file to process */ |
| private File targetFile; |
| |
| /** where to output the result */ |
| private File outputDirectory; |
| |
| /** an optional super grammar file */ |
| private File superGrammar; |
| |
| /** optional flag to enable html output */ |
| private boolean html; |
| |
| /** optional flag to print out a diagnostic file */ |
| private boolean diagnostic; |
| |
| /** optional flag to add trace methods */ |
| private boolean trace; |
| |
| /** optional flag to add trace methods to the parser only */ |
| private boolean traceParser; |
| |
| /** optional flag to add trace methods to the lexer only */ |
| private boolean traceLexer; |
| |
| /** optional flag to add trace methods to the tree walker only */ |
| private boolean traceTreeWalker; |
| |
| /** working directory */ |
| private File workingdir = null; |
| |
| /** captures ANTLR's output */ |
| private ByteArrayOutputStream bos = new ByteArrayOutputStream(); |
| |
| /** The debug attribute */ |
| private boolean debug; |
| |
| |
| /** Instance of a utility class to use for file operations. */ |
| private static final FileUtils FILE_UTILS = FileUtils.getFileUtils(); |
| |
| /** Constructor for ANTLR task. */ |
| public ANTLR() { |
| commandline.setVm(JavaEnvUtils.getJreExecutable("java")); |
| commandline.setClassname("antlr.Tool"); |
| } |
| |
| /** |
| * The grammar file to process. |
| * @param target the grammar file |
| */ |
| public void setTarget(File target) { |
| log("Setting target to: " + target.toString(), Project.MSG_VERBOSE); |
| this.targetFile = target; |
| } |
| |
| /** |
| * The directory to write the generated files to. |
| * @param outputDirectory the output directory |
| */ |
| public void setOutputdirectory(File outputDirectory) { |
| log("Setting output directory to: " + outputDirectory.toString(), Project.MSG_VERBOSE); |
| this.outputDirectory = outputDirectory; |
| } |
| |
| /** |
| * Sets an optional super grammar file. |
| * Use setGlib(File superGrammar) instead. |
| * @param superGrammar the super grammar filename |
| * @deprecated since ant 1.6 |
| */ |
| public void setGlib(String superGrammar) { |
| String sg = null; |
| if (Os.isFamily("dos")) { |
| sg = superGrammar.replace('\\', '/'); |
| } else { |
| sg = superGrammar; |
| } |
| setGlib(FILE_UTILS.resolveFile(getProject().getBaseDir(), sg)); |
| } |
| /** |
| * Sets an optional super grammar file |
| * @param superGrammar the super grammar file |
| * @since ant 1.6 |
| */ |
| public void setGlib(File superGrammar) { |
| this.superGrammar = superGrammar; |
| } |
| /** |
| * Sets a flag to enable ParseView debugging |
| * @param enable a <code>boolean</code> value |
| */ |
| public void setDebug(boolean enable) { |
| this.debug = enable; |
| } |
| |
| /** |
| * If true, emit html |
| * @param enable a <code>boolean</code> value |
| */ |
| public void setHtml(boolean enable) { |
| html = enable; |
| } |
| |
| /** |
| * Sets a flag to emit diagnostic text |
| * @param enable a <code>boolean</code> value |
| */ |
| public void setDiagnostic(boolean enable) { |
| diagnostic = enable; |
| } |
| |
| /** |
| * If true, enables all tracing. |
| * @param enable a <code>boolean</code> value |
| */ |
| public void setTrace(boolean enable) { |
| trace = enable; |
| } |
| |
| /** |
| * If true, enables parser tracing. |
| * @param enable a <code>boolean</code> value |
| */ |
| public void setTraceParser(boolean enable) { |
| traceParser = enable; |
| } |
| |
| /** |
| * If true, enables lexer tracing. |
| * @param enable a <code>boolean</code> value |
| */ |
| public void setTraceLexer(boolean enable) { |
| traceLexer = enable; |
| } |
| |
| /** |
| * Sets a flag to allow the user to enable tree walker tracing |
| * @param enable a <code>boolean</code> value |
| */ |
| public void setTraceTreeWalker(boolean enable) { |
| traceTreeWalker = enable; |
| } |
| |
| // we are forced to fork ANTLR since there is a call |
| // to System.exit() and there is nothing we can do |
| // right now to avoid this. :-( (SBa) |
| // I'm not removing this method to keep backward compatibility |
| /** |
| * @ant.attribute ignore="true" |
| * @param s a <code>boolean</code> value |
| */ |
| public void setFork(boolean s) { |
| //this.fork = s; |
| } |
| |
| /** |
| * The working directory of the process |
| * @param d the working directory |
| */ |
| public void setDir(File d) { |
| this.workingdir = d; |
| } |
| |
| /** |
| * Adds a classpath to be set |
| * because a directory might be given for Antlr debug. |
| * @return a path to be configured |
| */ |
| public Path createClasspath() { |
| return commandline.createClasspath(getProject()).createPath(); |
| } |
| |
| /** |
| * Adds a new JVM argument. |
| * @return create a new JVM argument so that any argument can be passed to the JVM. |
| * @see #setFork(boolean) |
| */ |
| public Commandline.Argument createJvmarg() { |
| return commandline.createVmArgument(); |
| } |
| |
| /** |
| * Adds the jars or directories containing Antlr |
| * this should make the forked JVM work without having to |
| * specify it directly. |
| * @throws BuildException on error |
| */ |
| public void init() throws BuildException { |
| addClasspathEntry("/antlr/ANTLRGrammarParseBehavior.class"); |
| } |
| |
| /** |
| * Search for the given resource and add the directory or archive |
| * that contains it to the classpath. |
| * |
| * <p>Doesn't work for archives in JDK 1.1 as the URL returned by |
| * getResource doesn't contain the name of the archive.</p> |
| * @param resource the resource name to search for |
| */ |
| protected void addClasspathEntry(String resource) { |
| /* |
| * pre Ant 1.6 this method used to call getClass().getResource |
| * while Ant 1.6 will call ClassLoader.getResource(). |
| * |
| * The difference is that Class.getResource expects a leading |
| * slash for "absolute" resources and will strip it before |
| * delegating to ClassLoader.getResource - so we now have to |
| * emulate Class's behavior. |
| */ |
| if (resource.startsWith("/")) { |
| resource = resource.substring(1); |
| } else { |
| resource = "org/apache/tools/ant/taskdefs/optional/" |
| + resource; |
| } |
| |
| File f = LoaderUtils.getResourceSource(getClass().getClassLoader(), |
| resource); |
| if (f != null) { |
| log("Found " + f.getAbsolutePath(), Project.MSG_DEBUG); |
| createClasspath().setLocation(f); |
| } else { |
| log("Couldn\'t find " + resource, Project.MSG_VERBOSE); |
| } |
| } |
| |
| /** |
| * Execute the task. |
| * @throws BuildException on error |
| */ |
| public void execute() throws BuildException { |
| validateAttributes(); |
| |
| //TODO: use ANTLR to parse the grammar file to do this. |
| File generatedFile = getGeneratedFile(); |
| boolean targetIsOutOfDate = |
| targetFile.lastModified() > generatedFile.lastModified(); |
| boolean superGrammarIsOutOfDate = superGrammar != null |
| && (superGrammar.lastModified() > generatedFile.lastModified()); |
| if (targetIsOutOfDate || superGrammarIsOutOfDate) { |
| if (targetIsOutOfDate) { |
| log("Compiling " + targetFile + " as it is newer than " |
| + generatedFile, Project.MSG_VERBOSE); |
| } else { |
| log("Compiling " + targetFile + " as " + superGrammar |
| + " is newer than " + generatedFile, Project.MSG_VERBOSE); |
| } |
| populateAttributes(); |
| commandline.createArgument().setValue(targetFile.toString()); |
| |
| log(commandline.describeCommand(), Project.MSG_VERBOSE); |
| int err = run(commandline.getCommandline()); |
| if (err != 0) { |
| throw new BuildException("ANTLR returned: " + err, getLocation()); |
| } |
| String output = bos.toString(); |
| if (output.contains("error:")) { |
| throw new BuildException("ANTLR signaled an error: " |
| + output, getLocation()); |
| } |
| } else { |
| log("Skipped grammar file. Generated file " + generatedFile |
| + " is newer.", Project.MSG_VERBOSE); |
| } |
| } |
| |
| /** |
| * A refactored method for populating all the command line arguments based |
| * on the user-specified attributes. |
| */ |
| private void populateAttributes() { |
| commandline.createArgument().setValue("-o"); |
| commandline.createArgument().setValue(outputDirectory.toString()); |
| if (superGrammar != null) { |
| commandline.createArgument().setValue("-glib"); |
| commandline.createArgument().setValue(superGrammar.toString()); |
| } |
| if (html) { |
| commandline.createArgument().setValue("-html"); |
| } |
| if (diagnostic) { |
| commandline.createArgument().setValue("-diagnostic"); |
| } |
| if (trace) { |
| commandline.createArgument().setValue("-trace"); |
| } |
| if (traceParser) { |
| commandline.createArgument().setValue("-traceParser"); |
| } |
| if (traceLexer) { |
| commandline.createArgument().setValue("-traceLexer"); |
| } |
| if (traceTreeWalker) { |
| if (is272()) { |
| commandline.createArgument().setValue("-traceTreeParser"); |
| } else { |
| commandline.createArgument().setValue("-traceTreeWalker"); |
| } |
| } |
| if (debug) { |
| commandline.createArgument().setValue("-debug"); |
| } |
| } |
| |
| private void validateAttributes() throws BuildException { |
| if (targetFile == null || !targetFile.isFile()) { |
| throw new BuildException("Invalid target: " + targetFile); |
| } |
| |
| // if no output directory is specified, used the target's directory |
| if (outputDirectory == null) { |
| setOutputdirectory(new File(targetFile.getParent())); |
| } |
| if (!outputDirectory.isDirectory()) { |
| throw new BuildException("Invalid output directory: " + outputDirectory); |
| } |
| } |
| |
| private File getGeneratedFile() throws BuildException { |
| String generatedFileName = null; |
| try { |
| BufferedReader in = new BufferedReader(new FileReader(targetFile)); |
| String line; |
| while ((line = in.readLine()) != null) { |
| int extendsIndex = line.indexOf(" extends "); |
| if (line.startsWith("class ") && extendsIndex > -1) { |
| generatedFileName = line.substring( |
| "class ".length(), extendsIndex).trim(); |
| break; |
| } |
| } |
| in.close(); |
| } catch (Exception e) { |
| throw new BuildException("Unable to determine generated class", e); |
| } |
| if (generatedFileName == null) { |
| throw new BuildException("Unable to determine generated class"); |
| } |
| return new File(outputDirectory, generatedFileName |
| + (html ? ".html" : ".java")); |
| } |
| |
| /** execute in a forked VM */ |
| private int run(String[] command) throws BuildException { |
| PumpStreamHandler psh = |
| new PumpStreamHandler(new LogOutputStream(this, Project.MSG_INFO), |
| new TeeOutputStream( |
| new LogOutputStream(this, |
| Project.MSG_WARN), |
| bos) |
| ); |
| Execute exe = new Execute(psh, null); |
| exe.setAntRun(getProject()); |
| if (workingdir != null) { |
| exe.setWorkingDirectory(workingdir); |
| } |
| exe.setCommandline(command); |
| try { |
| return exe.execute(); |
| } catch (IOException e) { |
| throw new BuildException(e, getLocation()); |
| } finally { |
| FileUtils.close(bos); |
| } |
| } |
| |
| /** |
| * Whether the antlr version is 2.7.2 (or higher). |
| * |
| * @return true if the version of Antlr present is 2.7.2 or later. |
| * @since Ant 1.6 |
| */ |
| protected boolean is272() { |
| AntClassLoader l = null; |
| try { |
| l = getProject().createClassLoader(commandline.getClasspath()); |
| l.loadClass("antlr.Version"); |
| return true; |
| } catch (ClassNotFoundException e) { |
| return false; |
| } finally { |
| if (l != null) { |
| l.cleanup(); |
| } |
| } |
| } |
| } |