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  *  2006-12-29: Modified to work for antlr3 by J�rgen Pfundt
17  *  2007-01-04: Some minor correction after checking code with findBugs tool
18  *  2007-02-10: Adapted the grammar type recognition to the changed naming
19  *              conventions for Tree Parser
20  *  2007-10-17: Options "trace", "traceLexer", "traceParser" and "glib" emit
21  *              warnings when being used.
22  *              Added recognition of "parser grammar T".
23  *              Added options "nocollapse", "noprune".
24  *              ANTLR option "depend" is being used to resolve build dependencies.
25  *  2007-11-15: Embedded Classpath statement had not been observed
26  *              with option depend="true" (Reported by Mats Behre)
27  *  2008-03-31: Support the option conversiontimeout. (Jim Idle)
28  *  2007-12-31: With option "depend=true" proceed even if first pass failed so
29  *              that ANTLR can spit out its errors
30  *  2008-08-09: Inspecting environment variable ANTLR_HOME to detect and add
31  *              antlr- and stringtemplate libraries to the classpath
32  *  2008-08-09: Removed routine checkGenerateFile. It got feeble with the
33  *              introduction of composed grammars, e.g. "import T.g" and after
34  *              a short struggle it started it's journey to /dev/null.
35  *              From now one it is always antlr itself via the depend option
36  *              which decides about dependecies
37  *  2008-08-19: Dependency check for composed grammars added.
38  *              Might need some further improvements.
39  */
40 package org.apache.tools.ant.antlr;
41 
42 import java.util.regex.*;
43 import java.io.*;
44 import java.util.Map;
45 import org.apache.tools.ant.BuildException;
46 import org.apache.tools.ant.DirectoryScanner;
47 import org.apache.tools.ant.Project;
48 import org.apache.tools.ant.Task;
49 import org.apache.tools.ant.taskdefs.Execute;
50 import org.apache.tools.ant.taskdefs.LogOutputStream;
51 import org.apache.tools.ant.taskdefs.PumpStreamHandler;
52 import org.apache.tools.ant.taskdefs.Redirector;
53 import org.apache.tools.ant.types.Commandline;
54 import org.apache.tools.ant.types.CommandlineJava;
55 import org.apache.tools.ant.types.Path;
56 import org.apache.tools.ant.util.JavaEnvUtils;
57 import org.apache.tools.ant.util.LoaderUtils;
58 import org.apache.tools.ant.util.TeeOutputStream;
59 import org.apache.tools.ant.util.FileUtils;
60 
61 /**
62  *  Invokes the ANTLR3 Translator generator on a grammar file.
63  *
64  */
65 public class ANTLR3 extends Task {
66 
67     private CommandlineJava commandline = new CommandlineJava();
68     /** the file to process */
69     private File target = null;
70     /** where to output the result */
71     private File outputDirectory = null;
72     /** location of token files */
73     private File libDirectory = null;
74     /** an optional super grammar file */
75     private File superGrammar;
76     /** depend */
77     private boolean depend = false;
78     /** fork */
79     private boolean fork;
80     /** name of output style for messages */
81     private String messageFormatName;
82     /** optional flag to print out a diagnostic file */
83     private boolean diagnostic;
84     /** optional flag to add methods */
85     private boolean trace;
86     /** optional flag to add trace methods to the parser only */
87     private boolean traceParser;
88     /** optional flag to add trace methods to the lexer only */
89     private boolean traceLexer;
90     /** working directory */
91     private File workingdir = null;
92     /** captures ANTLR's output */
93     private ByteArrayOutputStream bos = new ByteArrayOutputStream();
94     /** The debug attribute */
95     private boolean debug;
96     /** The report attribute */
97     private boolean report;
98     /** The print attribute */
99     private boolean print;
100     /** The profile attribute */
101     private boolean profile;
102     /** The nfa attribute */
103     private boolean nfa;
104     /** The dfa attribute */
105     private boolean dfa;
106     /** multi threaded analysis */
107     private boolean multiThreaded;
108     /** collapse incident edges into DFA states */
109     private boolean nocollapse;
110     /** test lookahead against EBNF block exit branches */
111     private boolean noprune;
112     /** put tags at start/stop of all templates in output */
113     private boolean dbgST;
114     /** print AST */
115     private boolean grammarTree;
116     /** Instance of a utility class to use for file operations. */
117     private FileUtils fileUtils;
118     /**
119      * Whether to override the default conversion timeout with -Xconversiontimeout nnnn
120      */
121     private String conversiontimeout;
122 
ANTLR3()123     public ANTLR3() {
124         commandline.setVm(JavaEnvUtils.getJreExecutable("java"));
125         commandline.setClassname("org.antlr.Tool");
126         fileUtils = FileUtils.getFileUtils();
127     }
128 
129     /**
130      * The grammar file to process.
131      */
setTarget(File targetFile)132     public void setTarget(File targetFile) {
133         log("Setting target to: " + targetFile.toString(), Project.MSG_VERBOSE);
134         this.target = targetFile;
135     }
136 
137     /**
138      * The directory to write the generated files to.
139      */
setOutputdirectory(File outputDirectoryFile)140     public void setOutputdirectory(File outputDirectoryFile) {
141         log("Setting output directory to: " + outputDirectoryFile.toString(), Project.MSG_VERBOSE);
142         this.outputDirectory = outputDirectoryFile;
143     }
144 
145     /**
146      * The directory to write the generated files to.
147      */
getOutputdirectory()148     public File getOutputdirectory() {
149         return outputDirectory;
150     }
151 
152     /**
153      * The token files output directory.
154      */
setLibdirectory(File libDirectoryFile)155     public void setLibdirectory(File libDirectoryFile) {
156         log("Setting lib directory to: " + libDirectoryFile.toString(), Project.MSG_VERBOSE);
157         this.libDirectory = libDirectoryFile;
158     }
159 
160     /**
161      * The output style for messages.
162      */
setMessageformat(String name)163     public void setMessageformat(String name) {
164         log("Setting message-format to: " + name, Project.MSG_VERBOSE);
165         this.messageFormatName = name;
166     }
167 
168     /**
169      * Sets an optional super grammar file
170      * @deprecated
171      */
setGlib(File superGrammarFile)172     public void setGlib(File superGrammarFile) {
173         this.superGrammar = superGrammarFile;
174     }
175 
176     /**
177      * Sets a flag to enable ParseView debugging
178      */
setDebug(boolean enable)179     public void setDebug(boolean enable) {
180         this.debug = enable;
181     }
182 
183     /**
184      * Sets a flag to enable report statistics
185      */
setReport(boolean enable)186     public void setReport(boolean enable) {
187         this.report = enable;
188     }
189 
190     /**
191      * Sets a flag to print out the grammar without actions
192      */
setPrint(boolean enable)193     public void setPrint(boolean enable) {
194         this.print = enable;
195     }
196 
197     /**
198      * Sets a flag to enable profiling
199      */
setProfile(boolean enable)200     public void setProfile(boolean enable) {
201         this.profile = enable;
202     }
203 
204     /**
205      * Sets a flag to enable nfa generation
206      */
setNfa(boolean enable)207     public void setNfa(boolean enable) {
208         this.nfa = enable;
209     }
210 
211     /**
212      * Sets a flag to enable nfa generation
213      */
setDfa(boolean enable)214     public void setDfa(boolean enable) {
215         this.dfa = enable;
216     }
217 
218     /**
219      * Run the analysis multithreaded
220      * @param enable
221      */
setMultithreaded(boolean enable)222     public void setMultithreaded(boolean enable) {
223         multiThreaded = enable;
224     }
225 
226     /**
227      * collapse incident edges into DFA states
228      * @param enable
229      */
setNocollapse(boolean enable)230     public void setNocollapse(boolean enable) {
231         nocollapse = enable;
232     }
233 
234     /**
235      * test lookahead against EBNF block exit branches
236      * @param enable
237      */
setNoprune(boolean enable)238     public void setNoprune(boolean enable) {
239         noprune = enable;
240     }
241 
242     /**
243      * test lookahead against EBNF block exit branches
244      * @param enable
245      */
setDbgST(boolean enable)246     public void setDbgST(boolean enable) {
247         dbgST = enable;
248     }
249 
250     /**
251      * override the default conversion timeout with -Xconversiontimeout nnnn
252      * @param conversiontimeoutString
253      */
setConversiontimeout(String conversiontimeoutString)254     public void setConversiontimeout(String conversiontimeoutString) {
255         log("Setting conversiontimeout to: " + conversiontimeoutString, Project.MSG_VERBOSE);
256         try {
257             int timeout = Integer.valueOf(conversiontimeoutString);
258             this.conversiontimeout = conversiontimeoutString;
259         } catch (NumberFormatException e) {
260             log("Option ConversionTimeOut ignored due to illegal value: '" + conversiontimeoutString + "'", Project.MSG_ERR);
261         }
262     }
263 
264     /**
265      * Set a flag to enable printing of the grammar tree
266      */
setGrammartree(boolean enable)267     public void setGrammartree(boolean enable) {
268         grammarTree = enable;
269     }
270 
271     /**
272      * Set a flag to enable dependency checking by ANTLR itself
273      * The depend option is always used implicitely inside the antlr3 task
274      * @deprecated
275      */
setDepend(boolean s)276     public void setDepend(boolean s) {
277         this.depend = s;
278     }
279 
280     /**
281      * Sets a flag to emit diagnostic text
282      */
setDiagnostic(boolean enable)283     public void setDiagnostic(boolean enable) {
284         diagnostic = enable;
285     }
286 
287     /**
288      * If true, enables all tracing.
289      * @deprecated
290      */
setTrace(boolean enable)291     public void setTrace(boolean enable) {
292         trace = enable;
293     }
294 
295     /**
296      * If true, enables parser tracing.
297      * @deprecated
298      */
setTraceParser(boolean enable)299     public void setTraceParser(boolean enable) {
300         traceParser = enable;
301     }
302 
303     /**
304      * If true, enables lexer tracing.
305      * @deprecated
306      */
setTraceLexer(boolean enable)307     public void setTraceLexer(boolean enable) {
308         traceLexer = enable;
309     }
310 
311     // we are forced to fork ANTLR since there is a call
312     // to System.exit() and there is nothing we can do
313     // right now to avoid this. :-( (SBa)
314     // I'm not removing this method to keep backward compatibility
315     /**
316      * @ant.attribute ignore="true"
317      */
setFork(boolean s)318     public void setFork(boolean s) {
319         this.fork = s;
320     }
321 
322     /**
323      * The working directory of the process
324      */
setDir(File d)325     public void setDir(File d) {
326         this.workingdir = d;
327     }
328 
329     /**
330      * Adds a classpath to be set
331      * because a directory might be given for Antlr debug.
332      */
createClasspath()333     public Path createClasspath() {
334         return commandline.createClasspath(getProject()).createPath();
335     }
336 
337     /**
338      * Adds a new JVM argument.
339      * @return  create a new JVM argument so that any argument can be passed to the JVM.
340      * @see #setFork(boolean)
341      */
createJvmarg()342     public Commandline.Argument createJvmarg() {
343         return commandline.createVmArgument();
344     }
345 
346     /**
347      * Adds the jars or directories containing Antlr and associates.
348      * This should make the forked JVM work without having to
349      * specify it directly.
350      */
351     @Override
init()352     public void init() throws BuildException {
353         /* Inquire environment variables */
354         Map<String, String> variables = System.getenv();
355         /* Get value for key "ANTLR_HOME" which should hopefully point to
356          * the directory where the current version of antlr3 is installed */
357         String antlrHome = variables.get("ANTLR_HOME");
358         if (antlrHome != null) {
359             /* Environment variable ANTLR_HOME has been defined.
360              * Now add all antlr and stringtemplate libraries to the
361              * classpath */
362             addAntlrJarsToClasspath(antlrHome + "/lib");
363         }
364         addClasspathEntry("/antlr/ANTLRGrammarParseBehavior.class", "AntLR2");
365         addClasspathEntry("/org/antlr/tool/ANTLRParser.class", "AntLR3");
366         addClasspathEntry("/org/antlr/stringtemplate/StringTemplate.class", "Stringtemplate");
367 
368 
369     }
370 
371     /**
372      * Search for the given resource and add the directory or archive
373      * that contains it to the classpath.
374      *
375      * <p>Doesn't work for archives in JDK 1.1 as the URL returned by
376      * getResource doesn't contain the name of the archive.</p>
377      */
addClasspathEntry(String resource, String msg)378     protected void addClasspathEntry(String resource, String msg) {
379         /*
380          * pre Ant 1.6 this method used to call getClass().getResource
381          * while Ant 1.6 will call ClassLoader.getResource().
382          *
383          * The difference is that Class.getResource expects a leading
384          * slash for "absolute" resources and will strip it before
385          * delegating to ClassLoader.getResource - so we now have to
386          * emulate Class's behavior.
387          */
388         if (resource.startsWith("/")) {
389             resource = resource.substring(1);
390         } else {
391             resource = "org/apache/tools/ant/taskdefs/optional/" + resource;
392         }
393 
394         File f = LoaderUtils.getResourceSource(getClass().getClassLoader(), resource);
395         if (f != null) {
396             log("Found via classpath: " + f.getAbsolutePath(), Project.MSG_VERBOSE);
397             createClasspath().setLocation(f);
398         } else {
399             log("Couldn\'t find resource " + resource + " for library " + msg + " in external classpath", Project.MSG_VERBOSE);
400         }
401     }
402 
403     /**
404      * If the environment variable ANTLR_HOME is defined and points
405      * to the installation directory of antlr3 then look for all antlr-*.jar and
406      * stringtemplate-*.jar files in the lib directory and add them
407      * to the classpath.
408      * This feature should make working with eclipse or netbeans projects a
409      * little bit easier. As wildcards are being used for the version part
410      * of the jar-archives it makes the task independent of
411      * new releases. Just let ANTLR_HOME point to the new installation
412      * directory.
413      */
addAntlrJarsToClasspath(String antlrLibDir)414     private void addAntlrJarsToClasspath(String antlrLibDir) {
415         String[] includes = {"antlr-*.jar", "stringtemplate-*.jar"};
416 
417         DirectoryScanner ds = new DirectoryScanner();
418         ds.setIncludes(includes);
419         ds.setBasedir(new File(antlrLibDir));
420         ds.setCaseSensitive(true);
421         ds.scan();
422 
423         String separator = System.getProperty("file.separator");
424         String[] files = ds.getIncludedFiles();
425         for (String file : files) {
426             File f = new File(antlrLibDir + separator + file);
427             log("Found via ANTLR_HOME: " + f.getAbsolutePath(), Project.MSG_VERBOSE);
428             createClasspath().setLocation(f);
429         }
430     }
431 
432     @Override
execute()433     public void execute() throws BuildException {
434 
435         validateAttributes();
436 
437         // Use ANTLR itself to resolve dependencies and decide whether
438         // to invoke ANTLR for compilation
439         if (dependencyCheck()) {
440             populateAttributes();
441             commandline.createArgument().setValue(target.toString());
442 
443             log(commandline.describeCommand(), Project.MSG_VERBOSE);
444             int err = 0;
445             try {
446                 err = run(commandline.getCommandline(), new LogOutputStream(this, Project.MSG_INFO), new LogOutputStream(this, Project.MSG_WARN));
447             } catch (IOException e) {
448                 throw new BuildException(e, getLocation());
449             } finally {
450                 try {
451                     bos.close();
452                 } catch (IOException e) {
453                     // ignore
454                 }
455             }
456 
457             if (err != 0) {
458                 throw new BuildException("ANTLR returned: " + err, getLocation());
459             } else {
460                 Pattern p = Pattern.compile("error\\([0-9]+\\):");
461                 Matcher m = p.matcher(bos.toString());
462                 if (m.find()) {
463                     throw new BuildException("ANTLR signaled an error.", getLocation());
464                 }
465             }
466         } else {
467             try {
468                 log("All dependencies of grammar file \'" + target.getCanonicalPath() + "\' are up to date.", Project.MSG_VERBOSE);
469             } catch (IOException ex) {
470                 log("All dependencies of grammar file \'" + target.toString() + "\' are up to date.", Project.MSG_VERBOSE);
471             }
472         }
473     }
474 
475     /**
476      * A refactored method for populating all the command line arguments based
477      * on the user-specified attributes.
478      */
populateAttributes()479     private void populateAttributes() {
480 
481         commandline.createArgument().setValue("-o");
482         commandline.createArgument().setValue(outputDirectory.toString());
483 
484         commandline.createArgument().setValue("-lib");
485         commandline.createArgument().setValue(libDirectory.toString());
486 
487         if (superGrammar != null) {
488             log("Option 'glib' is not supported by ANTLR v3. Option ignored!", Project.MSG_WARN);
489         }
490 
491         if (diagnostic) {
492             commandline.createArgument().setValue("-diagnostic");
493         }
494         if (depend) {
495             log("Option 'depend' is implicitely always used by ANTLR v3. Option can safely be omitted!", Project.MSG_WARN);
496         }
497         if (trace) {
498             log("Option 'trace' is not supported by ANTLR v3. Option ignored!", Project.MSG_WARN);
499         }
500         if (traceParser) {
501             log("Option 'traceParser' is not supported by ANTLR v3. Option ignored!", Project.MSG_WARN);
502         }
503         if (traceLexer) {
504             log("Option 'traceLexer' is not supported by ANTLR v3. Option ignored!", Project.MSG_WARN);
505         }
506         if (debug) {
507             commandline.createArgument().setValue("-debug");
508         }
509         if (report) {
510             commandline.createArgument().setValue("-report");
511         }
512         if (print) {
513             commandline.createArgument().setValue("-print");
514         }
515         if (profile) {
516             commandline.createArgument().setValue("-profile");
517         }
518         if (messageFormatName != null) {
519             commandline.createArgument().setValue("-message-format");
520             commandline.createArgument().setValue(messageFormatName);
521         }
522         if (nfa) {
523             commandline.createArgument().setValue("-nfa");
524         }
525         if (dfa) {
526             commandline.createArgument().setValue("-dfa");
527         }
528         if (multiThreaded) {
529             commandline.createArgument().setValue("-Xmultithreaded");
530         }
531         if (nocollapse) {
532             commandline.createArgument().setValue("-Xnocollapse");
533         }
534         if (noprune) {
535             commandline.createArgument().setValue("-Xnoprune");
536         }
537         if (dbgST) {
538             commandline.createArgument().setValue("-XdbgST");
539         }
540         if (conversiontimeout != null) {
541             commandline.createArgument().setValue("-Xconversiontimeout");
542             commandline.createArgument().setValue(conversiontimeout);
543         }
544         if (grammarTree) {
545             commandline.createArgument().setValue("-Xgrtree");
546         }
547     }
548 
validateAttributes()549     private void validateAttributes() throws BuildException {
550 
551         if (target == null) {
552             throw new BuildException("No target grammar, lexer grammar or tree parser specified!");
553         } else if (!target.isFile()) {
554             throw new BuildException("Target: " + target + " is not a file!");
555         }
556 
557         // if no output directory is specified, use the target's directory
558         if (outputDirectory == null) {
559             setOutputdirectory(new File(target.getParent()));
560         }
561 
562         if (!outputDirectory.isDirectory()) {
563             throw new BuildException("Invalid output directory: " + outputDirectory);
564         }
565 
566         if (workingdir != null && !workingdir.isDirectory()) {
567             throw new BuildException("Invalid working directory: " + workingdir);
568         }
569 
570         // if no libDirectory is specified, use the target's directory
571         if (libDirectory == null) {
572             setLibdirectory(new File(target.getParent()));
573         }
574 
575         if (!libDirectory.isDirectory()) {
576             throw new BuildException("Invalid lib directory: " + libDirectory);
577         }
578     }
579 
dependencyCheck()580     private boolean dependencyCheck() throws BuildException {
581         // using "antlr -o <OutputDirectory> -lib <LibDirectory> -depend <T>"
582         // to get the list of dependencies
583         CommandlineJava cmdline;
584         try {
585             cmdline = (CommandlineJava) commandline.clone();
586         } catch (java.lang.CloneNotSupportedException e) {
587             throw new BuildException("Clone of commandline failed: " + e);
588         }
589 
590         cmdline.createArgument().setValue("-depend");
591         cmdline.createArgument().setValue("-o");
592         cmdline.createArgument().setValue(outputDirectory.toString());
593         cmdline.createArgument().setValue("-lib");
594         cmdline.createArgument().setValue(libDirectory.toString());
595         cmdline.createArgument().setValue(target.toString());
596 
597         log(cmdline.describeCommand(), Project.MSG_VERBOSE);
598 
599         // redirect output generated by ANTLR to temporary file
600         Redirector r = new Redirector(this);
601         File f;
602         try {
603             f = File.createTempFile("depend", null, getOutputdirectory());
604             f.deleteOnExit();
605             log("Write dependencies for '" + target.toString() + "' to file '" + f.getCanonicalPath() + "'", Project.MSG_VERBOSE);
606             r.setOutput(f);
607             r.setAlwaysLog(false);
608             r.createStreams();
609         } catch (IOException e) {
610             throw new BuildException("Redirection of output failed: " + e);
611         }
612 
613         // execute antlr -depend ...
614         int err = 0;
615         try {
616             err = run(cmdline.getCommandline(), r.getOutputStream(), null);
617         } catch (IOException e) {
618             try {
619                 r.complete();
620                 log("Redirection of output terminated.", Project.MSG_VERBOSE);
621             } catch (IOException ex) {
622                 log("Termination of output redirection failed: " + ex, Project.MSG_ERR);
623             }
624             throw new BuildException(e, getLocation());
625         } finally {
626             try {
627                 bos.close();
628             } catch (IOException e) {
629                 // ignore
630             }
631         }
632 
633         try {
634             r.complete();
635             log("Redirection of output terminated.", Project.MSG_VERBOSE);
636         } catch (IOException e) {
637             log("Termination of output redirection failed: " + e, Project.MSG_ERR);
638         }
639 
640         if (err != 0) {
641             if (f.exists()) {
642                 f.delete();
643             }
644             if (cmdline.getClasspath() == null) {
645                 log("Antlr libraries not found in external classpath or embedded classpath statement ", Project.MSG_ERR);
646             }
647             log("Dependency check failed. ANTLR returned: " + err, Project.MSG_ERR);
648             return true;
649         } else {
650             Pattern p = Pattern.compile("error\\([0-9]+\\):");
651             Matcher m = p.matcher(bos.toString());
652             if (m.find()) {
653                 if (f.exists()) {
654                     f.delete();
655                 }
656                 // On error always recompile
657                 return true;
658             }
659         }
660 
661         boolean compile = false;
662 
663         // open temporary file
664         BufferedReader in = null;
665         try {
666             in = new BufferedReader(new FileReader(f));
667         } catch (IOException e) {
668             try {
669                 if (in != null) {
670                     in.close();
671                 }
672             } catch (IOException ex) {
673                 throw new BuildException("Could not close file\'" + f.toString() + "\'.");
674             }
675             if (f.exists()) {
676                 f.delete();
677             }
678             try {
679                 throw new BuildException("Could not open \'" + f.getCanonicalPath() + "\' for reading.");
680             } catch (IOException ex) {
681                 throw new BuildException("Could not open \'" + f.toString() + "\' for reading.");
682             }
683         }
684 
685         // evaluate dependencies in temporary file
686         String s;
687 
688         try {
689             while ((s = in.readLine()) != null) {
690                 String a;
691                 String b;
692                 // As the separator string in lines emitted by the depend option
693                 // is either " : " or ": " trim is invoked for the first file name of a line
694                 int to = s.indexOf(": ");
695                 if (to >= 0) {
696                     a = s.substring(0, to).trim();
697                     File lhs = new File(a);
698                     if (!lhs.isFile()) {
699                         log("File '" + a + "' is not a regular file", Project.MSG_VERBOSE);
700                         String name = lhs.getName();
701                         String[] parts = splitRightHandSide(name, "\\u002E");
702                         if (parts.length <= 1) {
703                             a += ".java";
704                             lhs = new File(a);
705                             if (lhs.isFile()) {
706                                 log("File '" + a + "' is a regular file last modified at " + lhs.lastModified(), Project.MSG_VERBOSE);
707                             }
708                         }
709                     }
710 
711                     b = s.substring(to + ": ".length());
712                     String[] names = splitRightHandSide(b, ", ?");
713                     File aFile = new File(a);
714                     for (String name : names) {
715                         File bFile = new File(name);
716                         log("File '" + a + "' depends on file '" + name + "'", Project.MSG_VERBOSE);
717                         log("File '" + a + "' modified at " + aFile.lastModified(), Project.MSG_VERBOSE);
718                         log("File '" + name + "' modified at " + bFile.lastModified(), Project.MSG_VERBOSE);
719                         if (fileUtils.isUpToDate(aFile, bFile)) {
720                             log("Compiling " + target + " as '" + name + "' is newer than '" + a + "'", Project.MSG_VERBOSE);
721                             // Feeling not quite comfortable with touching the file
722                             fileUtils.setFileLastModified(aFile, -1);
723                             log("Touching file '" + a + "'", Project.MSG_VERBOSE);
724                             compile = true;
725                             break;
726                         }
727                     }
728                     if (compile) {
729                         break;
730                     }
731                 }
732             }
733             in.close();
734         } catch (IOException e) {
735             if (f.exists()) {
736                 f.delete();
737             }
738             throw new BuildException("Error reading file '" + f.toString() + "'");
739         }
740 
741         if (f.exists()) {
742             f.delete();
743         }
744 
745         return compile;
746     }
747 
splitRightHandSide(String fileNames, String pattern)748     private String[] splitRightHandSide(String fileNames, String pattern) {
749         String[] names = fileNames.split(pattern);
750         for (String name : names) {
751             log("Split right hand side '" + name + "'", Project.MSG_VERBOSE);
752         }
753         return names;
754     }
755 
756     /** execute in a forked VM */
run(String[] command, OutputStream out, OutputStream err)757     private int run(String[] command, OutputStream out, OutputStream err) throws IOException {
758         PumpStreamHandler psh;
759         if (err == null) {
760             psh = new PumpStreamHandler(out, bos);
761         } else {
762             psh = new PumpStreamHandler(out, new TeeOutputStream(err, bos));
763         }
764 
765         Execute exe = new Execute(psh, null);
766 
767         exe.setAntRun(getProject());
768         if (workingdir != null) {
769             exe.setWorkingDirectory(workingdir);
770         }
771 
772         exe.setCommandline(command);
773 
774         return exe.execute();
775     }
776 }
777