Home | History | Annotate | Download | only in antlr
      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 Jrgen 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 
    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