001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: */
018:
019: package org.apache.tools.ant.taskdefs.optional;
020:
021: import java.io.BufferedReader;
022: import java.io.ByteArrayOutputStream;
023: import java.io.File;
024: import java.io.FileReader;
025: import java.io.IOException;
026: import org.apache.tools.ant.AntClassLoader;
027: import org.apache.tools.ant.BuildException;
028: import org.apache.tools.ant.Project;
029: import org.apache.tools.ant.Task;
030: import org.apache.tools.ant.taskdefs.Execute;
031: import org.apache.tools.ant.taskdefs.LogOutputStream;
032: import org.apache.tools.ant.taskdefs.PumpStreamHandler;
033: import org.apache.tools.ant.taskdefs.condition.Os;
034: import org.apache.tools.ant.types.Commandline;
035: import org.apache.tools.ant.types.CommandlineJava;
036: import org.apache.tools.ant.types.Path;
037: import org.apache.tools.ant.util.JavaEnvUtils;
038: import org.apache.tools.ant.util.LoaderUtils;
039: import org.apache.tools.ant.util.TeeOutputStream;
040: import org.apache.tools.ant.util.FileUtils;
041:
042: /**
043: * Invokes the ANTLR Translator generator on a grammar file.
044: *
045: */
046: public class ANTLR extends Task {
047:
048: private CommandlineJava commandline = new CommandlineJava();
049:
050: /** the file to process */
051: private File targetFile;
052:
053: /** where to output the result */
054: private File outputDirectory;
055:
056: /** an optional super grammar file */
057: private File super Grammar;
058:
059: /** optional flag to enable html output */
060: private boolean html;
061:
062: /** optional flag to print out a diagnostic file */
063: private boolean diagnostic;
064:
065: /** optional flag to add trace methods */
066: private boolean trace;
067:
068: /** optional flag to add trace methods to the parser only */
069: private boolean traceParser;
070:
071: /** optional flag to add trace methods to the lexer only */
072: private boolean traceLexer;
073:
074: /** optional flag to add trace methods to the tree walker only */
075: private boolean traceTreeWalker;
076:
077: /** working directory */
078: private File workingdir = null;
079:
080: /** captures ANTLR's output */
081: private ByteArrayOutputStream bos = new ByteArrayOutputStream();
082:
083: /** The debug attribute */
084: private boolean debug;
085:
086: /** Instance of a utility class to use for file operations. */
087: private static final FileUtils FILE_UTILS = FileUtils
088: .getFileUtils();
089:
090: /** Constructor for ANTLR task. */
091: public ANTLR() {
092: commandline.setVm(JavaEnvUtils.getJreExecutable("java"));
093: commandline.setClassname("antlr.Tool");
094: }
095:
096: /**
097: * The grammar file to process.
098: * @param target the gramer file
099: */
100: public void setTarget(File target) {
101: log("Setting target to: " + target.toString(),
102: Project.MSG_VERBOSE);
103: this .targetFile = target;
104: }
105:
106: /**
107: * The directory to write the generated files to.
108: * @param outputDirectory the output directory
109: */
110: public void setOutputdirectory(File outputDirectory) {
111: log("Setting output directory to: "
112: + outputDirectory.toString(), Project.MSG_VERBOSE);
113: this .outputDirectory = outputDirectory;
114: }
115:
116: /**
117: * Sets an optional super grammar file.
118: * Use setGlib(File superGrammar) instead.
119: * @param superGrammar the super grammar filename
120: * @deprecated since ant 1.6
121: */
122: public void setGlib(String super Grammar) {
123: String sg = null;
124: if (Os.isFamily("dos")) {
125: sg = super Grammar.replace('\\', '/');
126: } else {
127: sg = super Grammar;
128: }
129: setGlib(FILE_UTILS.resolveFile(getProject().getBaseDir(), sg));
130: }
131:
132: /**
133: * Sets an optional super grammar file
134: * @param superGrammar the super grammar file
135: * @since ant 1.6
136: */
137: public void setGlib(File super Grammar) {
138: this .super Grammar = super Grammar;
139: }
140:
141: /**
142: * Sets a flag to enable ParseView debugging
143: * @param enable a <code>boolean</code> value
144: */
145: public void setDebug(boolean enable) {
146: this .debug = enable;
147: }
148:
149: /**
150: * If true, emit html
151: * @param enable a <code>boolean</code> value
152: */
153: public void setHtml(boolean enable) {
154: html = enable;
155: }
156:
157: /**
158: * Sets a flag to emit diagnostic text
159: * @param enable a <code>boolean</code> value
160: */
161: public void setDiagnostic(boolean enable) {
162: diagnostic = enable;
163: }
164:
165: /**
166: * If true, enables all tracing.
167: * @param enable a <code>boolean</code> value
168: */
169: public void setTrace(boolean enable) {
170: trace = enable;
171: }
172:
173: /**
174: * If true, enables parser tracing.
175: * @param enable a <code>boolean</code> value
176: */
177: public void setTraceParser(boolean enable) {
178: traceParser = enable;
179: }
180:
181: /**
182: * If true, enables lexer tracing.
183: * @param enable a <code>boolean</code> value
184: */
185: public void setTraceLexer(boolean enable) {
186: traceLexer = enable;
187: }
188:
189: /**
190: * Sets a flag to allow the user to enable tree walker tracing
191: * @param enable a <code>boolean</code> value
192: */
193: public void setTraceTreeWalker(boolean enable) {
194: traceTreeWalker = enable;
195: }
196:
197: // we are forced to fork ANTLR since there is a call
198: // to System.exit() and there is nothing we can do
199: // right now to avoid this. :-( (SBa)
200: // I'm not removing this method to keep backward compatibility
201: /**
202: * @ant.attribute ignore="true"
203: * @param s a <code>boolean</code> value
204: */
205: public void setFork(boolean s) {
206: //this.fork = s;
207: }
208:
209: /**
210: * The working directory of the process
211: * @param d the working directory
212: */
213: public void setDir(File d) {
214: this .workingdir = d;
215: }
216:
217: /**
218: * Adds a classpath to be set
219: * because a directory might be given for Antlr debug.
220: * @return a path to be configured
221: */
222: public Path createClasspath() {
223: return commandline.createClasspath(getProject()).createPath();
224: }
225:
226: /**
227: * Adds a new JVM argument.
228: * @return create a new JVM argument so that any argument can be passed to the JVM.
229: * @see #setFork(boolean)
230: */
231: public Commandline.Argument createJvmarg() {
232: return commandline.createVmArgument();
233: }
234:
235: /**
236: * Adds the jars or directories containing Antlr
237: * this should make the forked JVM work without having to
238: * specify it directly.
239: * @throws BuildException on error
240: */
241: public void init() throws BuildException {
242: addClasspathEntry("/antlr/ANTLRGrammarParseBehavior.class");
243: }
244:
245: /**
246: * Search for the given resource and add the directory or archive
247: * that contains it to the classpath.
248: *
249: * <p>Doesn't work for archives in JDK 1.1 as the URL returned by
250: * getResource doesn't contain the name of the archive.</p>
251: * @param resource the resource name to search for
252: */
253: protected void addClasspathEntry(String resource) {
254: /*
255: * pre Ant 1.6 this method used to call getClass().getResource
256: * while Ant 1.6 will call ClassLoader.getResource().
257: *
258: * The difference is that Class.getResource expects a leading
259: * slash for "absolute" resources and will strip it before
260: * delegating to ClassLoader.getResource - so we now have to
261: * emulate Class's behavior.
262: */
263: if (resource.startsWith("/")) {
264: resource = resource.substring(1);
265: } else {
266: resource = "org/apache/tools/ant/taskdefs/optional/"
267: + resource;
268: }
269:
270: File f = LoaderUtils.getResourceSource(getClass()
271: .getClassLoader(), resource);
272: if (f != null) {
273: log("Found " + f.getAbsolutePath(), Project.MSG_DEBUG);
274: createClasspath().setLocation(f);
275: } else {
276: log("Couldn\'t find " + resource, Project.MSG_VERBOSE);
277: }
278: }
279:
280: /**
281: * Execute the task.
282: * @throws BuildException on error
283: */
284: public void execute() throws BuildException {
285: validateAttributes();
286:
287: //TODO: use ANTLR to parse the grammar file to do this.
288: File generatedFile = getGeneratedFile();
289: boolean targetIsOutOfDate = targetFile.lastModified() > generatedFile
290: .lastModified();
291: boolean super GrammarIsOutOfDate = super Grammar != null
292: && (super Grammar.lastModified() > generatedFile
293: .lastModified());
294: if (targetIsOutOfDate || super GrammarIsOutOfDate) {
295: if (targetIsOutOfDate) {
296: log("Compiling " + targetFile + " as it is newer than "
297: + generatedFile, Project.MSG_VERBOSE);
298: } else if (super GrammarIsOutOfDate) {
299: log("Compiling " + targetFile + " as " + super Grammar
300: + " is newer than " + generatedFile,
301: Project.MSG_VERBOSE);
302: }
303: populateAttributes();
304: commandline.createArgument()
305: .setValue(targetFile.toString());
306:
307: log(commandline.describeCommand(), Project.MSG_VERBOSE);
308: int err = run(commandline.getCommandline());
309: if (err != 0) {
310: throw new BuildException("ANTLR returned: " + err,
311: getLocation());
312: } else {
313: String output = bos.toString();
314: if (output.indexOf("error:") > -1) {
315: throw new BuildException(
316: "ANTLR signaled an error: " + output,
317: getLocation());
318: }
319: }
320: } else {
321: log("Skipped grammar file. Generated file " + generatedFile
322: + " is newer.", Project.MSG_VERBOSE);
323: }
324: }
325:
326: /**
327: * A refactored method for populating all the command line arguments based
328: * on the user-specified attributes.
329: */
330: private void populateAttributes() {
331: commandline.createArgument().setValue("-o");
332: commandline.createArgument().setValue(
333: outputDirectory.toString());
334: if (super Grammar != null) {
335: commandline.createArgument().setValue("-glib");
336: commandline.createArgument().setValue(
337: super Grammar.toString());
338: }
339: if (html) {
340: commandline.createArgument().setValue("-html");
341: }
342: if (diagnostic) {
343: commandline.createArgument().setValue("-diagnostic");
344: }
345: if (trace) {
346: commandline.createArgument().setValue("-trace");
347: }
348: if (traceParser) {
349: commandline.createArgument().setValue("-traceParser");
350: }
351: if (traceLexer) {
352: commandline.createArgument().setValue("-traceLexer");
353: }
354: if (traceTreeWalker) {
355: if (is272()) {
356: commandline.createArgument().setValue(
357: "-traceTreeParser");
358: } else {
359: commandline.createArgument().setValue(
360: "-traceTreeWalker");
361: }
362: }
363: if (debug) {
364: commandline.createArgument().setValue("-debug");
365: }
366: }
367:
368: private void validateAttributes() throws BuildException {
369: if (targetFile == null || !targetFile.isFile()) {
370: throw new BuildException("Invalid target: " + targetFile);
371: }
372:
373: // if no output directory is specified, used the target's directory
374: if (outputDirectory == null) {
375: setOutputdirectory(new File(targetFile.getParent()));
376: }
377: if (!outputDirectory.isDirectory()) {
378: throw new BuildException("Invalid output directory: "
379: + outputDirectory);
380: }
381: }
382:
383: private File getGeneratedFile() throws BuildException {
384: String generatedFileName = null;
385: try {
386: BufferedReader in = new BufferedReader(new FileReader(
387: targetFile));
388: String line;
389: while ((line = in.readLine()) != null) {
390: int extendsIndex = line.indexOf(" extends ");
391: if (line.startsWith("class ") && extendsIndex > -1) {
392: generatedFileName = line.substring(6, extendsIndex)
393: .trim();
394: break;
395: }
396: }
397: in.close();
398: } catch (Exception e) {
399: throw new BuildException(
400: "Unable to determine generated class", e);
401: }
402: if (generatedFileName == null) {
403: throw new BuildException(
404: "Unable to determine generated class");
405: }
406: return new File(outputDirectory, generatedFileName
407: + (html ? ".html" : ".java"));
408: }
409:
410: /** execute in a forked VM */
411: private int run(String[] command) throws BuildException {
412: PumpStreamHandler psh = new PumpStreamHandler(
413: new LogOutputStream(this , Project.MSG_INFO),
414: new TeeOutputStream(new LogOutputStream(this ,
415: Project.MSG_WARN), bos));
416: Execute exe = new Execute(psh, null);
417: exe.setAntRun(getProject());
418: if (workingdir != null) {
419: exe.setWorkingDirectory(workingdir);
420: }
421: exe.setCommandline(command);
422: try {
423: return exe.execute();
424: } catch (IOException e) {
425: throw new BuildException(e, getLocation());
426: } finally {
427: FileUtils.close(bos);
428: }
429: }
430:
431: /**
432: * Whether the antlr version is 2.7.2 (or higher).
433: *
434: * @return true if the version of Antlr present is 2.7.2 or later.
435: * @since Ant 1.6
436: */
437: protected boolean is272() {
438: AntClassLoader l = null;
439: try {
440: l = getProject().createClassLoader(
441: commandline.getClasspath());
442: l.loadClass("antlr.Version");
443: return true;
444: } catch (ClassNotFoundException e) {
445: return false;
446: } finally {
447: if (l != null) {
448: l.cleanup();
449: }
450: }
451: }
452: }
|