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.javacc;
020:
021: import java.io.File;
022: import java.io.IOException;
023: import java.util.Enumeration;
024: import java.util.Hashtable;
025:
026: import org.apache.tools.ant.BuildException;
027: import org.apache.tools.ant.Project;
028: import org.apache.tools.ant.Task;
029: import org.apache.tools.ant.taskdefs.Execute;
030: import org.apache.tools.ant.taskdefs.LogStreamHandler;
031: import org.apache.tools.ant.types.Commandline;
032: import org.apache.tools.ant.types.CommandlineJava;
033: import org.apache.tools.ant.types.Path;
034: import org.apache.tools.ant.util.JavaEnvUtils;
035:
036: /**
037: * Runs the JJTree compiler compiler.
038: *
039: */
040: public class JJTree extends Task {
041:
042: // keys to optional attributes
043: private static final String OUTPUT_FILE = "OUTPUT_FILE";
044: private static final String BUILD_NODE_FILES = "BUILD_NODE_FILES";
045: private static final String MULTI = "MULTI";
046: private static final String NODE_DEFAULT_VOID = "NODE_DEFAULT_VOID";
047: private static final String NODE_FACTORY = "NODE_FACTORY";
048: private static final String NODE_SCOPE_HOOK = "NODE_SCOPE_HOOK";
049: private static final String NODE_USES_PARSER = "NODE_USES_PARSER";
050: private static final String STATIC = "STATIC";
051: private static final String VISITOR = "VISITOR";
052:
053: private static final String NODE_PACKAGE = "NODE_PACKAGE";
054: private static final String VISITOR_EXCEPTION = "VISITOR_EXCEPTION";
055: private static final String NODE_PREFIX = "NODE_PREFIX";
056:
057: private final Hashtable optionalAttrs = new Hashtable();
058:
059: private String outputFile = null;
060:
061: private static final String DEFAULT_SUFFIX = ".jj";
062:
063: // required attributes
064: private File outputDirectory = null;
065: private File targetFile = null;
066: private File javaccHome = null;
067:
068: private CommandlineJava cmdl = new CommandlineJava();
069:
070: /**
071: * Sets the BUILD_NODE_FILES grammar option.
072: * @param buildNodeFiles a <code>boolean</code> value.
073: */
074: public void setBuildnodefiles(boolean buildNodeFiles) {
075: optionalAttrs.put(BUILD_NODE_FILES,
076: buildNodeFiles ? Boolean.TRUE : Boolean.FALSE);
077: }
078:
079: /**
080: * Sets the MULTI grammar option.
081: * @param multi a <code>boolean</code> value.
082: */
083: public void setMulti(boolean multi) {
084: optionalAttrs.put(MULTI, multi ? Boolean.TRUE : Boolean.FALSE);
085: }
086:
087: /**
088: * Sets the NODE_DEFAULT_VOID grammar option.
089: * @param nodeDefaultVoid a <code>boolean</code> value.
090: */
091: public void setNodedefaultvoid(boolean nodeDefaultVoid) {
092: optionalAttrs.put(NODE_DEFAULT_VOID,
093: nodeDefaultVoid ? Boolean.TRUE : Boolean.FALSE);
094: }
095:
096: /**
097: * Sets the NODE_FACTORY grammar option.
098: * @param nodeFactory a <code>boolean</code> value.
099: */
100: public void setNodefactory(boolean nodeFactory) {
101: optionalAttrs.put(NODE_FACTORY, nodeFactory ? Boolean.TRUE
102: : Boolean.FALSE);
103: }
104:
105: /**
106: * Sets the NODE_SCOPE_HOOK grammar option.
107: * @param nodeScopeHook a <code>boolean</code> value.
108: */
109: public void setNodescopehook(boolean nodeScopeHook) {
110: optionalAttrs.put(NODE_SCOPE_HOOK, nodeScopeHook ? Boolean.TRUE
111: : Boolean.FALSE);
112: }
113:
114: /**
115: * Sets the NODE_USES_PARSER grammar option.
116: * @param nodeUsesParser a <code>boolean</code> value.
117: */
118: public void setNodeusesparser(boolean nodeUsesParser) {
119: optionalAttrs.put(NODE_USES_PARSER,
120: nodeUsesParser ? Boolean.TRUE : Boolean.FALSE);
121: }
122:
123: /**
124: * Sets the STATIC grammar option.
125: * @param staticParser a <code>boolean</code> value.
126: */
127: public void setStatic(boolean staticParser) {
128: optionalAttrs.put(STATIC, staticParser ? Boolean.TRUE
129: : Boolean.FALSE);
130: }
131:
132: /**
133: * Sets the VISITOR grammar option.
134: * @param visitor a <code>boolean</code> value.
135: */
136: public void setVisitor(boolean visitor) {
137: optionalAttrs.put(VISITOR, visitor ? Boolean.TRUE
138: : Boolean.FALSE);
139: }
140:
141: /**
142: * Sets the NODE_PACKAGE grammar option.
143: * @param nodePackage the option to use.
144: */
145: public void setNodepackage(String nodePackage) {
146: optionalAttrs.put(NODE_PACKAGE, nodePackage);
147: }
148:
149: /**
150: * Sets the VISITOR_EXCEPTION grammar option.
151: * @param visitorException the option to use.
152: */
153: public void setVisitorException(String visitorException) {
154: optionalAttrs.put(VISITOR_EXCEPTION, visitorException);
155: }
156:
157: /**
158: * Sets the NODE_PREFIX grammar option.
159: * @param nodePrefix the option to use.
160: */
161: public void setNodeprefix(String nodePrefix) {
162: optionalAttrs.put(NODE_PREFIX, nodePrefix);
163: }
164:
165: /**
166: * The directory to write the generated JavaCC grammar and node files to.
167: * If not set, the files are written to the directory
168: * containing the grammar file.
169: * @param outputDirectory the output directory.
170: */
171: public void setOutputdirectory(File outputDirectory) {
172: this .outputDirectory = outputDirectory;
173: }
174:
175: /**
176: * The outputfile to write the generated JavaCC grammar file to.
177: * If not set, the file is written with the same name as
178: * the JJTree grammar file with a suffix .jj.
179: * @param outputFile the output file name.
180: */
181: public void setOutputfile(String outputFile) {
182: this .outputFile = outputFile;
183: }
184:
185: /**
186: * The jjtree grammar file to process.
187: * @param targetFile the grammar file.
188: */
189: public void setTarget(File targetFile) {
190: this .targetFile = targetFile;
191: }
192:
193: /**
194: * The directory containing the JavaCC distribution.
195: * @param javaccHome the directory containing JavaCC.
196: */
197: public void setJavacchome(File javaccHome) {
198: this .javaccHome = javaccHome;
199: }
200:
201: /**
202: * Constructor
203: */
204: public JJTree() {
205: cmdl.setVm(JavaEnvUtils.getJreExecutable("java"));
206: }
207:
208: /**
209: * Run the task.
210: * @throws BuildException on error.
211: */
212: public void execute() throws BuildException {
213:
214: // load command line with optional attributes
215: Enumeration iter = optionalAttrs.keys();
216: while (iter.hasMoreElements()) {
217: String name = (String) iter.nextElement();
218: Object value = optionalAttrs.get(name);
219: cmdl.createArgument().setValue(
220: "-" + name + ":" + value.toString());
221: }
222:
223: if (targetFile == null || !targetFile.isFile()) {
224: throw new BuildException("Invalid target: " + targetFile);
225: }
226:
227: File javaFile = null;
228:
229: // use the directory containing the target as the output directory
230: if (outputDirectory == null) {
231: // convert backslashes to slashes, otherwise jjtree will
232: // put this as comments and this seems to confuse javacc
233: cmdl.createArgument().setValue(
234: "-OUTPUT_DIRECTORY:" + getDefaultOutputDirectory());
235:
236: javaFile = new File(createOutputFileName(targetFile,
237: outputFile, null));
238: } else {
239: if (!outputDirectory.isDirectory()) {
240: throw new BuildException("'outputdirectory' "
241: + outputDirectory + " is not a directory.");
242: }
243:
244: // convert backslashes to slashes, otherwise jjtree will
245: // put this as comments and this seems to confuse javacc
246: cmdl.createArgument().setValue(
247: "-OUTPUT_DIRECTORY:"
248: + outputDirectory.getAbsolutePath()
249: .replace('\\', '/'));
250:
251: javaFile = new File(createOutputFileName(targetFile,
252: outputFile, outputDirectory.getPath()));
253: }
254:
255: if (javaFile.exists()
256: && targetFile.lastModified() < javaFile.lastModified()) {
257: log("Target is already built - skipping (" + targetFile
258: + ")", Project.MSG_VERBOSE);
259: return;
260: }
261:
262: if (outputFile != null) {
263: cmdl.createArgument().setValue(
264: "-" + OUTPUT_FILE + ":"
265: + outputFile.replace('\\', '/'));
266: }
267:
268: cmdl.createArgument().setValue(targetFile.getAbsolutePath());
269:
270: final Path classpath = cmdl.createClasspath(getProject());
271: final File javaccJar = JavaCC.getArchiveFile(javaccHome);
272: classpath.createPathElement().setPath(
273: javaccJar.getAbsolutePath());
274: classpath.addJavaRuntime();
275:
276: cmdl.setClassname(JavaCC.getMainClass(classpath,
277: JavaCC.TASKDEF_TYPE_JJTREE));
278:
279: final Commandline.Argument arg = cmdl.createVmArgument();
280: arg.setValue("-mx140M");
281: arg.setValue("-Dinstall.root=" + javaccHome.getAbsolutePath());
282:
283: final Execute process = new Execute(new LogStreamHandler(this ,
284: Project.MSG_INFO, Project.MSG_INFO), null);
285: log(cmdl.describeCommand(), Project.MSG_VERBOSE);
286: process.setCommandline(cmdl.getCommandline());
287:
288: try {
289: if (process.execute() != 0) {
290: throw new BuildException("JJTree failed.");
291: }
292: } catch (IOException e) {
293: throw new BuildException("Failed to launch JJTree", e);
294: }
295: }
296:
297: private String createOutputFileName(File destFile,
298: String optionalOutputFile, String outputDir) {
299: optionalOutputFile = validateOutputFile(optionalOutputFile,
300: outputDir);
301: String jjtreeFile = destFile.getAbsolutePath().replace('\\',
302: '/');
303:
304: if ((optionalOutputFile == null)
305: || optionalOutputFile.equals("")) {
306: int filePos = jjtreeFile.lastIndexOf("/");
307:
308: if (filePos >= 0) {
309: jjtreeFile = jjtreeFile.substring(filePos + 1);
310: }
311:
312: int suffixPos = jjtreeFile.lastIndexOf('.');
313:
314: if (suffixPos == -1) {
315: optionalOutputFile = jjtreeFile + DEFAULT_SUFFIX;
316: } else {
317: String currentSuffix = jjtreeFile.substring(suffixPos);
318:
319: if (currentSuffix.equals(DEFAULT_SUFFIX)) {
320: optionalOutputFile = jjtreeFile + DEFAULT_SUFFIX;
321: } else {
322: optionalOutputFile = jjtreeFile.substring(0,
323: suffixPos)
324: + DEFAULT_SUFFIX;
325: }
326: }
327: }
328:
329: if ((outputDir == null) || outputDir.equals("")) {
330: outputDir = getDefaultOutputDirectory();
331: }
332:
333: return (outputDir + "/" + optionalOutputFile)
334: .replace('\\', '/');
335: }
336:
337: /**
338: * When running JJTree from an Ant taskdesk the -OUTPUT_DIRECTORY must
339: * always be set. But when -OUTPUT_DIRECTORY is set, -OUTPUT_FILE is
340: * handled as if relative of this -OUTPUT_DIRECTORY. Thus when the
341: * -OUTPUT_FILE is absolute or contains a drive letter we have a problem.
342: *
343: * @param destFile
344: * @param outputDir
345: * @return
346: * @throws BuildException
347: */
348: private String validateOutputFile(String destFile, String outputDir)
349: throws BuildException {
350: if (destFile == null) {
351: return null;
352: }
353:
354: if ((outputDir == null)
355: && (destFile.startsWith("/") || destFile
356: .startsWith("\\"))) {
357: String relativeOutputFile = makeOutputFileRelative(destFile);
358: setOutputfile(relativeOutputFile);
359:
360: return relativeOutputFile;
361: }
362:
363: String root = getRoot(new File(destFile)).getAbsolutePath();
364:
365: if ((root.length() > 1)
366: && destFile.startsWith(root.substring(0,
367: root.length() - 1))) {
368: throw new BuildException(
369: "Drive letter in 'outputfile' not " + "supported: "
370: + destFile);
371: }
372:
373: return destFile;
374: }
375:
376: private String makeOutputFileRelative(String destFile) {
377: StringBuffer relativePath = new StringBuffer();
378: String defaultOutputDirectory = getDefaultOutputDirectory();
379: int nextPos = defaultOutputDirectory.indexOf('/');
380: int startPos = nextPos + 1;
381:
382: while (startPos > -1
383: && startPos < defaultOutputDirectory.length()) {
384: relativePath.append("/..");
385: nextPos = defaultOutputDirectory.indexOf('/', startPos);
386:
387: if (nextPos == -1) {
388: startPos = nextPos;
389: } else {
390: startPos = nextPos + 1;
391: }
392: }
393:
394: relativePath.append(destFile);
395:
396: return relativePath.toString();
397: }
398:
399: private String getDefaultOutputDirectory() {
400: return getProject().getBaseDir().getAbsolutePath().replace(
401: '\\', '/');
402: }
403:
404: /**
405: * Determine root directory for a given file.
406: *
407: * @param file
408: * @return file's root directory
409: */
410: private File getRoot(File file) {
411: File root = file.getAbsoluteFile();
412:
413: while (root.getParent() != null) {
414: root = root.getParentFile();
415: }
416:
417: return root;
418: }
419: }
|