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 */
40package org.apache.tools.ant.antlr;
41
42import java.util.regex.*;
43import java.io.*;
44import java.util.Map;
45import org.apache.tools.ant.BuildException;
46import org.apache.tools.ant.DirectoryScanner;
47import org.apache.tools.ant.Project;
48import org.apache.tools.ant.Task;
49import org.apache.tools.ant.taskdefs.Execute;
50import org.apache.tools.ant.taskdefs.LogOutputStream;
51import org.apache.tools.ant.taskdefs.PumpStreamHandler;
52import org.apache.tools.ant.taskdefs.Redirector;
53import org.apache.tools.ant.types.Commandline;
54import org.apache.tools.ant.types.CommandlineJava;
55import org.apache.tools.ant.types.Path;
56import org.apache.tools.ant.util.JavaEnvUtils;
57import org.apache.tools.ant.util.LoaderUtils;
58import org.apache.tools.ant.util.TeeOutputStream;
59import org.apache.tools.ant.util.FileUtils;
60
61/**
62 *  Invokes the ANTLR3 Translator generator on a grammar file.
63 *
64 */
65public 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
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     */
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     */
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     */
148    public File getOutputdirectory() {
149        return outputDirectory;
150    }
151
152    /**
153     * The token files output directory.
154     */
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     */
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     */
172    public void setGlib(File superGrammarFile) {
173        this.superGrammar = superGrammarFile;
174    }
175
176    /**
177     * Sets a flag to enable ParseView debugging
178     */
179    public void setDebug(boolean enable) {
180        this.debug = enable;
181    }
182
183    /**
184     * Sets a flag to enable report statistics
185     */
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     */
193    public void setPrint(boolean enable) {
194        this.print = enable;
195    }
196
197    /**
198     * Sets a flag to enable profiling
199     */
200    public void setProfile(boolean enable) {
201        this.profile = enable;
202    }
203
204    /**
205     * Sets a flag to enable nfa generation
206     */
207    public void setNfa(boolean enable) {
208        this.nfa = enable;
209    }
210
211    /**
212     * Sets a flag to enable nfa generation
213     */
214    public void setDfa(boolean enable) {
215        this.dfa = enable;
216    }
217
218    /**
219     * Run the analysis multithreaded
220     * @param enable
221     */
222    public void setMultithreaded(boolean enable) {
223        multiThreaded = enable;
224    }
225
226    /**
227     * collapse incident edges into DFA states
228     * @param enable
229     */
230    public void setNocollapse(boolean enable) {
231        nocollapse = enable;
232    }
233
234    /**
235     * test lookahead against EBNF block exit branches
236     * @param enable
237     */
238    public void setNoprune(boolean enable) {
239        noprune = enable;
240    }
241
242    /**
243     * test lookahead against EBNF block exit branches
244     * @param enable
245     */
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     */
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     */
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     */
276    public void setDepend(boolean s) {
277        this.depend = s;
278    }
279
280    /**
281     * Sets a flag to emit diagnostic text
282     */
283    public void setDiagnostic(boolean enable) {
284        diagnostic = enable;
285    }
286
287    /**
288     * If true, enables all tracing.
289     * @deprecated
290     */
291    public void setTrace(boolean enable) {
292        trace = enable;
293    }
294
295    /**
296     * If true, enables parser tracing.
297     * @deprecated
298     */
299    public void setTraceParser(boolean enable) {
300        traceParser = enable;
301    }
302
303    /**
304     * If true, enables lexer tracing.
305     * @deprecated
306     */
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     */
318    public void setFork(boolean s) {
319        this.fork = s;
320    }
321
322    /**
323     * The working directory of the process
324     */
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     */
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     */
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
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     */
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     */
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
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     */
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
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
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
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 */
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