1. /*
  2. * Copyright 2000-2004 The Apache Software Foundation
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. *
  16. */
  17. package org.apache.tools.ant.taskdefs.optional.javacc;
  18. import java.io.File;
  19. import java.io.IOException;
  20. import java.util.Enumeration;
  21. import java.util.Hashtable;
  22. import org.apache.tools.ant.BuildException;
  23. import org.apache.tools.ant.Project;
  24. import org.apache.tools.ant.Task;
  25. import org.apache.tools.ant.taskdefs.Execute;
  26. import org.apache.tools.ant.taskdefs.LogStreamHandler;
  27. import org.apache.tools.ant.types.Commandline;
  28. import org.apache.tools.ant.types.CommandlineJava;
  29. import org.apache.tools.ant.types.Path;
  30. import org.apache.tools.ant.util.JavaEnvUtils;
  31. /**
  32. * Runs the JJTree compiler compiler.
  33. *
  34. */
  35. public class JJTree extends Task {
  36. // keys to optional attributes
  37. private static final String OUTPUT_FILE = "OUTPUT_FILE";
  38. private static final String BUILD_NODE_FILES = "BUILD_NODE_FILES";
  39. private static final String MULTI = "MULTI";
  40. private static final String NODE_DEFAULT_VOID = "NODE_DEFAULT_VOID";
  41. private static final String NODE_FACTORY = "NODE_FACTORY";
  42. private static final String NODE_SCOPE_HOOK = "NODE_SCOPE_HOOK";
  43. private static final String NODE_USES_PARSER = "NODE_USES_PARSER";
  44. private static final String STATIC = "STATIC";
  45. private static final String VISITOR = "VISITOR";
  46. private static final String NODE_PACKAGE = "NODE_PACKAGE";
  47. private static final String VISITOR_EXCEPTION = "VISITOR_EXCEPTION";
  48. private static final String NODE_PREFIX = "NODE_PREFIX";
  49. private final Hashtable optionalAttrs = new Hashtable();
  50. private String outputFile = null;
  51. private static final String DEFAULT_SUFFIX = ".jj";
  52. // required attributes
  53. private File outputDirectory = null;
  54. private File target = null;
  55. private File javaccHome = null;
  56. private CommandlineJava cmdl = new CommandlineJava();
  57. /**
  58. * Sets the BUILD_NODE_FILES grammar option.
  59. */
  60. public void setBuildnodefiles(boolean buildNodeFiles) {
  61. optionalAttrs.put(BUILD_NODE_FILES, new Boolean(buildNodeFiles));
  62. }
  63. /**
  64. * Sets the MULTI grammar option.
  65. */
  66. public void setMulti(boolean multi) {
  67. optionalAttrs.put(MULTI, new Boolean(multi));
  68. }
  69. /**
  70. * Sets the NODE_DEFAULT_VOID grammar option.
  71. */
  72. public void setNodedefaultvoid(boolean nodeDefaultVoid) {
  73. optionalAttrs.put(NODE_DEFAULT_VOID, new Boolean(nodeDefaultVoid));
  74. }
  75. /**
  76. * Sets the NODE_FACTORY grammar option.
  77. */
  78. public void setNodefactory(boolean nodeFactory) {
  79. optionalAttrs.put(NODE_FACTORY, new Boolean(nodeFactory));
  80. }
  81. /**
  82. * Sets the NODE_SCOPE_HOOK grammar option.
  83. */
  84. public void setNodescopehook(boolean nodeScopeHook) {
  85. optionalAttrs.put(NODE_SCOPE_HOOK, new Boolean(nodeScopeHook));
  86. }
  87. /**
  88. * Sets the NODE_USES_PARSER grammar option.
  89. */
  90. public void setNodeusesparser(boolean nodeUsesParser) {
  91. optionalAttrs.put(NODE_USES_PARSER, new Boolean(nodeUsesParser));
  92. }
  93. /**
  94. * Sets the STATIC grammar option.
  95. */
  96. public void setStatic(boolean staticParser) {
  97. optionalAttrs.put(STATIC, new Boolean(staticParser));
  98. }
  99. /**
  100. * Sets the VISITOR grammar option.
  101. */
  102. public void setVisitor(boolean visitor) {
  103. optionalAttrs.put(VISITOR, new Boolean(visitor));
  104. }
  105. /**
  106. * Sets the NODE_PACKAGE grammar option.
  107. */
  108. public void setNodepackage(String nodePackage) {
  109. optionalAttrs.put(NODE_PACKAGE, new String(nodePackage));
  110. }
  111. /**
  112. * Sets the VISITOR_EXCEPTION grammar option.
  113. */
  114. public void setVisitorException(String visitorException) {
  115. optionalAttrs.put(VISITOR_EXCEPTION, new String(visitorException));
  116. }
  117. /**
  118. * Sets the NODE_PREFIX grammar option.
  119. */
  120. public void setNodeprefix(String nodePrefix) {
  121. optionalAttrs.put(NODE_PREFIX, new String(nodePrefix));
  122. }
  123. /**
  124. * The directory to write the generated JavaCC grammar and node files to.
  125. * If not set, the files are written to the directory
  126. * containing the grammar file.
  127. */
  128. public void setOutputdirectory(File outputDirectory) {
  129. this.outputDirectory = outputDirectory;
  130. }
  131. /**
  132. * The outputfile to write the generated JavaCC grammar file to.
  133. * If not set, the file is written with the same name as
  134. * the JJTree grammar file with a suffix .jj.
  135. */
  136. public void setOutputfile(String outputFile) {
  137. this.outputFile = outputFile;
  138. }
  139. /**
  140. * The jjtree grammar file to process.
  141. */
  142. public void setTarget(File target) {
  143. this.target = target;
  144. }
  145. /**
  146. * The directory containing the JavaCC distribution.
  147. */
  148. public void setJavacchome(File javaccHome) {
  149. this.javaccHome = javaccHome;
  150. }
  151. public JJTree() {
  152. cmdl.setVm(JavaEnvUtils.getJreExecutable("java"));
  153. }
  154. public void execute() throws BuildException {
  155. // load command line with optional attributes
  156. Enumeration iter = optionalAttrs.keys();
  157. while (iter.hasMoreElements()) {
  158. String name = (String) iter.nextElement();
  159. Object value = optionalAttrs.get(name);
  160. cmdl.createArgument().setValue("-" + name + ":" + value.toString());
  161. }
  162. if (target == null || !target.isFile()) {
  163. throw new BuildException("Invalid target: " + target);
  164. }
  165. File javaFile = null;
  166. // use the directory containing the target as the output directory
  167. if (outputDirectory == null) {
  168. // convert backslashes to slashes, otherwise jjtree will
  169. // put this as comments and this seems to confuse javacc
  170. cmdl.createArgument().setValue("-OUTPUT_DIRECTORY:"
  171. + getDefaultOutputDirectory());
  172. javaFile = new File(createOutputFileName(target, outputFile,
  173. null));
  174. } else {
  175. if (!outputDirectory.isDirectory()) {
  176. throw new BuildException("'outputdirectory' " + outputDirectory
  177. + " is not a directory.");
  178. }
  179. // convert backslashes to slashes, otherwise jjtree will
  180. // put this as comments and this seems to confuse javacc
  181. cmdl.createArgument().setValue("-OUTPUT_DIRECTORY:"
  182. + outputDirectory.getAbsolutePath()
  183. .replace('\\', '/'));
  184. javaFile = new File(createOutputFileName(target, outputFile,
  185. outputDirectory
  186. .getPath()));
  187. }
  188. if (javaFile.exists()
  189. && target.lastModified() < javaFile.lastModified()) {
  190. log("Target is already built - skipping (" + target + ")",
  191. Project.MSG_VERBOSE);
  192. return;
  193. }
  194. if (outputFile != null) {
  195. cmdl.createArgument().setValue("-" + OUTPUT_FILE + ":"
  196. + outputFile.replace('\\', '/'));
  197. }
  198. cmdl.createArgument().setValue(target.getAbsolutePath());
  199. cmdl.setClassname(JavaCC.getMainClass(javaccHome,
  200. JavaCC.TASKDEF_TYPE_JJTREE));
  201. final Path classpath = cmdl.createClasspath(getProject());
  202. final File javaccJar = JavaCC.getArchiveFile(javaccHome);
  203. classpath.createPathElement().setPath(javaccJar.getAbsolutePath());
  204. classpath.addJavaRuntime();
  205. final Commandline.Argument arg = cmdl.createVmArgument();
  206. arg.setValue("-mx140M");
  207. arg.setValue("-Dinstall.root=" + javaccHome.getAbsolutePath());
  208. final Execute process =
  209. new Execute(new LogStreamHandler(this,
  210. Project.MSG_INFO,
  211. Project.MSG_INFO),
  212. null);
  213. log(cmdl.describeCommand(), Project.MSG_VERBOSE);
  214. process.setCommandline(cmdl.getCommandline());
  215. try {
  216. if (process.execute() != 0) {
  217. throw new BuildException("JJTree failed.");
  218. }
  219. } catch (IOException e) {
  220. throw new BuildException("Failed to launch JJTree", e);
  221. }
  222. }
  223. private String createOutputFileName(File target, String optionalOutputFile,
  224. String outputDirectory) {
  225. optionalOutputFile = validateOutputFile(optionalOutputFile,
  226. outputDirectory);
  227. String jjtreeFile = target.getAbsolutePath().replace('\\', '/');
  228. if ((optionalOutputFile == null) || optionalOutputFile.equals("")) {
  229. int filePos = jjtreeFile.lastIndexOf("/");
  230. if (filePos >= 0) {
  231. jjtreeFile = jjtreeFile.substring(filePos + 1);
  232. }
  233. int suffixPos = jjtreeFile.lastIndexOf('.');
  234. if (suffixPos == -1) {
  235. optionalOutputFile = jjtreeFile + DEFAULT_SUFFIX;
  236. } else {
  237. String currentSuffix = jjtreeFile.substring(suffixPos);
  238. if (currentSuffix.equals(DEFAULT_SUFFIX)) {
  239. optionalOutputFile = jjtreeFile + DEFAULT_SUFFIX;
  240. } else {
  241. optionalOutputFile = jjtreeFile.substring(0, suffixPos)
  242. + DEFAULT_SUFFIX;
  243. }
  244. }
  245. }
  246. if ((outputDirectory == null) || outputDirectory.equals("")) {
  247. outputDirectory = getDefaultOutputDirectory();
  248. }
  249. return (outputDirectory + "/" + optionalOutputFile).replace('\\', '/');
  250. }
  251. /*
  252. * Not used anymore
  253. private boolean isAbsolute(String fileName) {
  254. return (fileName.startsWith("/") || (new File(fileName).isAbsolute()));
  255. }
  256. */
  257. /**
  258. * When running JJTree from an Ant taskdesk the -OUTPUT_DIRECTORY must
  259. * always be set. But when -OUTPUT_DIRECTORY is set, -OUTPUT_FILE is
  260. * handled as if relative of this -OUTPUT_DIRECTORY. Thus when the
  261. * -OUTPUT_FILE is absolute or contains a drive letter we have a problem.
  262. *
  263. * @param outputFile
  264. * @param outputDirectory
  265. * @return
  266. * @throws BuildException
  267. */
  268. private String validateOutputFile(String outputFile,
  269. String outputDirectory)
  270. throws BuildException {
  271. if (outputFile == null) {
  272. return null;
  273. }
  274. if ((outputDirectory == null)
  275. && (outputFile.startsWith("/") || outputFile.startsWith("\\"))) {
  276. String relativeOutputFile = makeOutputFileRelative(outputFile);
  277. setOutputfile(relativeOutputFile);
  278. return relativeOutputFile;
  279. }
  280. String root = getRoot(new File(outputFile)).getAbsolutePath();
  281. if ((root.length() > 1)
  282. && outputFile.startsWith(root.substring(0, root.length() - 1))) {
  283. throw new BuildException("Drive letter in 'outputfile' not "
  284. + "supported: " + outputFile);
  285. }
  286. return outputFile;
  287. }
  288. private String makeOutputFileRelative(String outputFile) {
  289. StringBuffer relativePath = new StringBuffer();
  290. String defaultOutputDirectory = getDefaultOutputDirectory();
  291. int nextPos = defaultOutputDirectory.indexOf('/');
  292. int startPos = nextPos + 1;
  293. while (startPos > -1 && startPos < defaultOutputDirectory.length()) {
  294. relativePath.append("/..");
  295. nextPos = defaultOutputDirectory.indexOf('/', startPos);
  296. if (nextPos == -1) {
  297. startPos = nextPos;
  298. } else {
  299. startPos = nextPos + 1;
  300. }
  301. }
  302. relativePath.append(outputFile);
  303. return relativePath.toString();
  304. }
  305. private String getDefaultOutputDirectory() {
  306. return getProject().getBaseDir().getAbsolutePath().replace('\\', '/');
  307. }
  308. /**
  309. * Determine root directory for a given file.
  310. *
  311. * @param file
  312. * @return file's root directory
  313. */
  314. private File getRoot(File file) {
  315. File root = file.getAbsoluteFile();
  316. while (root.getParent() != null) {
  317. root = root.getParentFile();
  318. }
  319. return root;
  320. }
  321. }