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;
  18. import java.io.BufferedReader;
  19. import java.io.ByteArrayOutputStream;
  20. import java.io.File;
  21. import java.io.FileReader;
  22. import java.io.IOException;
  23. import org.apache.tools.ant.AntClassLoader;
  24. import org.apache.tools.ant.BuildException;
  25. import org.apache.tools.ant.Project;
  26. import org.apache.tools.ant.Task;
  27. import org.apache.tools.ant.taskdefs.Execute;
  28. import org.apache.tools.ant.taskdefs.LogOutputStream;
  29. import org.apache.tools.ant.taskdefs.PumpStreamHandler;
  30. import org.apache.tools.ant.taskdefs.condition.Os;
  31. import org.apache.tools.ant.types.Commandline;
  32. import org.apache.tools.ant.types.CommandlineJava;
  33. import org.apache.tools.ant.types.Path;
  34. import org.apache.tools.ant.util.JavaEnvUtils;
  35. import org.apache.tools.ant.util.LoaderUtils;
  36. import org.apache.tools.ant.util.TeeOutputStream;
  37. import org.apache.tools.ant.util.FileUtils;
  38. /**
  39. * Invokes the ANTLR Translator generator on a grammar file.
  40. *
  41. */
  42. public class ANTLR extends Task {
  43. private CommandlineJava commandline = new CommandlineJava();
  44. /** the file to process */
  45. private File target;
  46. /** where to output the result */
  47. private File outputDirectory;
  48. /** an optional super grammar file */
  49. private File superGrammar;
  50. /** optional flag to enable html output */
  51. private boolean html;
  52. /** optional flag to print out a diagnostic file */
  53. private boolean diagnostic;
  54. /** optional flag to add trace methods */
  55. private boolean trace;
  56. /** optional flag to add trace methods to the parser only */
  57. private boolean traceParser;
  58. /** optional flag to add trace methods to the lexer only */
  59. private boolean traceLexer;
  60. /** optional flag to add trace methods to the tree walker only */
  61. private boolean traceTreeWalker;
  62. /** working directory */
  63. private File workingdir = null;
  64. /** captures ANTLR's output */
  65. private ByteArrayOutputStream bos = new ByteArrayOutputStream();
  66. /** The debug attribute */
  67. private boolean debug;
  68. /** Instance of a utility class to use for file operations. */
  69. private FileUtils fileUtils;
  70. public ANTLR() {
  71. commandline.setVm(JavaEnvUtils.getJreExecutable("java"));
  72. commandline.setClassname("antlr.Tool");
  73. fileUtils = FileUtils.newFileUtils();
  74. }
  75. /**
  76. * The grammar file to process.
  77. */
  78. public void setTarget(File target) {
  79. log("Setting target to: " + target.toString(), Project.MSG_VERBOSE);
  80. this.target = target;
  81. }
  82. /**
  83. * The directory to write the generated files to.
  84. */
  85. public void setOutputdirectory(File outputDirectory) {
  86. log("Setting output directory to: " + outputDirectory.toString(), Project.MSG_VERBOSE);
  87. this.outputDirectory = outputDirectory;
  88. }
  89. /**
  90. * Sets an optional super grammar file.
  91. * Use setGlib(File superGrammar) instead.
  92. * @deprecated since ant 1.6
  93. */
  94. public void setGlib(String superGrammar) {
  95. String sg = null;
  96. if (Os.isFamily("dos")) {
  97. sg = superGrammar.replace('\\', '/');
  98. } else {
  99. sg = superGrammar;
  100. }
  101. setGlib(fileUtils.resolveFile(getProject().getBaseDir(), sg));
  102. }
  103. /**
  104. * Sets an optional super grammar file
  105. * @since ant 1.6
  106. */
  107. public void setGlib(File superGrammar) {
  108. this.superGrammar = superGrammar;
  109. }
  110. /**
  111. * Sets a flag to enable ParseView debugging
  112. */
  113. public void setDebug(boolean enable) {
  114. this.debug = enable;
  115. }
  116. /**
  117. * If true, emit html
  118. */
  119. public void setHtml(boolean enable) {
  120. html = enable;
  121. }
  122. /**
  123. * Sets a flag to emit diagnostic text
  124. */
  125. public void setDiagnostic(boolean enable) {
  126. diagnostic = enable;
  127. }
  128. /**
  129. * If true, enables all tracing.
  130. */
  131. public void setTrace(boolean enable) {
  132. trace = enable;
  133. }
  134. /**
  135. * If true, enables parser tracing.
  136. */
  137. public void setTraceParser(boolean enable) {
  138. traceParser = enable;
  139. }
  140. /**
  141. * If true, enables lexer tracing.
  142. */
  143. public void setTraceLexer(boolean enable) {
  144. traceLexer = enable;
  145. }
  146. /**
  147. * Sets a flag to allow the user to enable tree walker tracing
  148. */
  149. public void setTraceTreeWalker(boolean enable) {
  150. traceTreeWalker = enable;
  151. }
  152. // we are forced to fork ANTLR since there is a call
  153. // to System.exit() and there is nothing we can do
  154. // right now to avoid this. :-( (SBa)
  155. // I'm not removing this method to keep backward compatibility
  156. /**
  157. * @ant.attribute ignore="true"
  158. */
  159. public void setFork(boolean s) {
  160. //this.fork = s;
  161. }
  162. /**
  163. * The working directory of the process
  164. */
  165. public void setDir(File d) {
  166. this.workingdir = d;
  167. }
  168. /**
  169. * Adds a classpath to be set
  170. * because a directory might be given for Antlr debug.
  171. */
  172. public Path createClasspath() {
  173. return commandline.createClasspath(getProject()).createPath();
  174. }
  175. /**
  176. * Adds a new JVM argument.
  177. * @return create a new JVM argument so that any argument can be passed to the JVM.
  178. * @see #setFork(boolean)
  179. */
  180. public Commandline.Argument createJvmarg() {
  181. return commandline.createVmArgument();
  182. }
  183. /**
  184. * Adds the jars or directories containing Antlr
  185. * this should make the forked JVM work without having to
  186. * specify it directly.
  187. */
  188. public void init() throws BuildException {
  189. addClasspathEntry("/antlr/ANTLRGrammarParseBehavior.class");
  190. }
  191. /**
  192. * Search for the given resource and add the directory or archive
  193. * that contains it to the classpath.
  194. *
  195. * <p>Doesn't work for archives in JDK 1.1 as the URL returned by
  196. * getResource doesn't contain the name of the archive.</p>
  197. */
  198. protected void addClasspathEntry(String resource) {
  199. /*
  200. * pre Ant 1.6 this method used to call getClass().getResource
  201. * while Ant 1.6 will call ClassLoader.getResource().
  202. *
  203. * The difference is that Class.getResource expects a leading
  204. * slash for "absolute" resources and will strip it before
  205. * delegating to ClassLoader.getResource - so we now have to
  206. * emulate Class's behavior.
  207. */
  208. if (resource.startsWith("/")) {
  209. resource = resource.substring(1);
  210. } else {
  211. resource = "org/apache/tools/ant/taskdefs/optional/"
  212. + resource;
  213. }
  214. File f = LoaderUtils.getResourceSource(getClass().getClassLoader(),
  215. resource);
  216. if (f != null) {
  217. log("Found " + f.getAbsolutePath(), Project.MSG_DEBUG);
  218. createClasspath().setLocation(f);
  219. } else {
  220. log("Couldn\'t find " + resource, Project.MSG_VERBOSE);
  221. }
  222. }
  223. public void execute() throws BuildException {
  224. validateAttributes();
  225. //TODO: use ANTLR to parse the grammar file to do this.
  226. File generatedFile = getGeneratedFile();
  227. boolean targetIsOutOfDate =
  228. target.lastModified() > generatedFile.lastModified();
  229. boolean superGrammarIsOutOfDate = superGrammar != null
  230. && (superGrammar.lastModified() > generatedFile.lastModified());
  231. if (targetIsOutOfDate || superGrammarIsOutOfDate) {
  232. if (targetIsOutOfDate) {
  233. log("Compiling " + target + " as it is newer than "
  234. + generatedFile, Project.MSG_VERBOSE);
  235. } else if (superGrammarIsOutOfDate) {
  236. log("Compiling " + target + " as " + superGrammar
  237. + " is newer than " + generatedFile, Project.MSG_VERBOSE);
  238. }
  239. populateAttributes();
  240. commandline.createArgument().setValue(target.toString());
  241. log(commandline.describeCommand(), Project.MSG_VERBOSE);
  242. int err = run(commandline.getCommandline());
  243. if (err != 0) {
  244. throw new BuildException("ANTLR returned: " + err, getLocation());
  245. } else {
  246. String output = bos.toString();
  247. if (output.indexOf("error:") > -1) {
  248. throw new BuildException("ANTLR signaled an error: "
  249. + output, getLocation());
  250. }
  251. }
  252. } else {
  253. log("Skipped grammar file. Generated file " + generatedFile
  254. + " is newer.", Project.MSG_VERBOSE);
  255. }
  256. }
  257. /**
  258. * A refactored method for populating all the command line arguments based
  259. * on the user-specified attributes.
  260. */
  261. private void populateAttributes() {
  262. commandline.createArgument().setValue("-o");
  263. commandline.createArgument().setValue(outputDirectory.toString());
  264. if (superGrammar != null) {
  265. commandline.createArgument().setValue("-glib");
  266. commandline.createArgument().setValue(superGrammar.toString());
  267. }
  268. if (html) {
  269. commandline.createArgument().setValue("-html");
  270. }
  271. if (diagnostic) {
  272. commandline.createArgument().setValue("-diagnostic");
  273. }
  274. if (trace) {
  275. commandline.createArgument().setValue("-trace");
  276. }
  277. if (traceParser) {
  278. commandline.createArgument().setValue("-traceParser");
  279. }
  280. if (traceLexer) {
  281. commandline.createArgument().setValue("-traceLexer");
  282. }
  283. if (traceTreeWalker) {
  284. if (is272()) {
  285. commandline.createArgument().setValue("-traceTreeParser");
  286. } else {
  287. commandline.createArgument().setValue("-traceTreeWalker");
  288. }
  289. }
  290. if (debug) {
  291. commandline.createArgument().setValue("-debug");
  292. }
  293. }
  294. private void validateAttributes() throws BuildException {
  295. if (target == null || !target.isFile()) {
  296. throw new BuildException("Invalid target: " + target);
  297. }
  298. // if no output directory is specified, used the target's directory
  299. if (outputDirectory == null) {
  300. setOutputdirectory(new File(target.getParent()));
  301. }
  302. if (!outputDirectory.isDirectory()) {
  303. throw new BuildException("Invalid output directory: " + outputDirectory);
  304. }
  305. }
  306. private File getGeneratedFile() throws BuildException {
  307. String generatedFileName = null;
  308. try {
  309. BufferedReader in = new BufferedReader(new FileReader(target));
  310. String line;
  311. while ((line = in.readLine()) != null) {
  312. int extendsIndex = line.indexOf(" extends ");
  313. if (line.startsWith("class ") && extendsIndex > -1) {
  314. generatedFileName = line.substring(6, extendsIndex).trim();
  315. break;
  316. }
  317. }
  318. in.close();
  319. } catch (Exception e) {
  320. throw new BuildException("Unable to determine generated class", e);
  321. }
  322. if (generatedFileName == null) {
  323. throw new BuildException("Unable to determine generated class");
  324. }
  325. return new File(outputDirectory, generatedFileName + ".java");
  326. }
  327. /** execute in a forked VM */
  328. private int run(String[] command) throws BuildException {
  329. PumpStreamHandler psh =
  330. new PumpStreamHandler(new LogOutputStream(this, Project.MSG_INFO),
  331. new TeeOutputStream(
  332. new LogOutputStream(this,
  333. Project.MSG_WARN),
  334. bos)
  335. );
  336. Execute exe = new Execute(psh, null);
  337. exe.setAntRun(getProject());
  338. if (workingdir != null) {
  339. exe.setWorkingDirectory(workingdir);
  340. }
  341. exe.setCommandline(command);
  342. try {
  343. return exe.execute();
  344. } catch (IOException e) {
  345. throw new BuildException(e, getLocation());
  346. } finally {
  347. try {
  348. bos.close();
  349. } catch (IOException e) {
  350. // ignore
  351. }
  352. }
  353. }
  354. /**
  355. * Whether the antlr version is 2.7.2 (or higher).
  356. *
  357. * @return true if the version of Antlr present is 2.7.2 or later.
  358. * @since Ant 1.6
  359. */
  360. protected boolean is272() {
  361. try {
  362. AntClassLoader l = new AntClassLoader(getProject(),
  363. commandline.getClasspath());
  364. l.loadClass("antlr.Version");
  365. return true;
  366. } catch (ClassNotFoundException e) {
  367. return false;
  368. } // end of try-catch
  369. }
  370. }