001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     * 
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     * 
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    
018    package org.apache.commons.launcher;
019    
020    import java.io.File;
021    import java.io.FileOutputStream;
022    import java.io.IOException;
023    import java.net.URL;
024    import java.net.URLClassLoader;
025    import java.util.ArrayList;
026    import java.util.HashMap;
027    import java.util.Iterator;
028    import java.util.StringTokenizer;
029    import org.apache.commons.launcher.types.ArgumentSet;
030    import org.apache.commons.launcher.types.ConditionalArgument;
031    import org.apache.commons.launcher.types.ConditionalVariable;
032    import org.apache.commons.launcher.types.JVMArgumentSet;
033    import org.apache.commons.launcher.types.SysPropertySet;
034    import org.apache.tools.ant.BuildException;
035    import org.apache.tools.ant.Task;
036    import org.apache.tools.ant.types.Path;
037    import org.apache.tools.ant.types.Reference;
038    
039    /**
040     * A class that eliminates the need for a batch or shell script to launch a Java
041     * class. Some situations where elimination of a batch or shell script may be 
042     * desirable are:
043     * <ul>
044     * <li>You want to avoid having to determining where certain application paths
045     *  are e.g. your application's home directory, etc. Determining this
046     *  dynamically in a Windows batch scripts is very tricky on some versions of
047     *  Windows or when softlinks are used on Unix platforms.
048     * <li>You want to avoid having to handle native file and path separators or
049     *  native path quoting issues.
050     * <li>You need to enforce certain system properties e.g.
051     *  <code>java.endorsed.dirs</code> when running with JDK 1.4.
052     * <li>You want to allow users to pass in custom JVM arguments or system
053     *  properties without having to parse and reorder arguments in your script.
054     *  This can be tricky and/or messy in batch and shell scripts.
055     * <li>You want to bootstrap system properties from a configuration file instead
056     *  hard-coding them in your batch and shell scripts.
057     * <li>You want to provide localized error messages which is very tricky to do
058     *  in batch and shell scripts.
059     * </ul>
060     *
061     * @author Patrick Luby
062     */
063    public class LaunchTask extends Task {
064    
065        //----------------------------------------------------------- Static Fields
066    
067        /**
068         * The argument property name.
069         */
070        public final static String ARG_PROP_NAME = "launch.arg.";
071    
072        /**
073         * The name of this task.
074         */
075        public final static String TASK_NAME = "launch";
076    
077        /**
078         * Cached synchronous child processes for all instances of this class.
079         */
080        private static ArrayList childProcesses = new ArrayList();
081    
082        //------------------------------------------------------------------ Fields
083    
084        /**
085         * Cached appendOutput flag.
086         */
087        private boolean appendOutput = false;
088    
089        /**
090         * Cached synchronously executing child process.
091         */
092        private Process childProc = null;
093    
094        /**
095         * Cached classpath.
096         */
097        private Path classpath = null;
098    
099        /**
100         * Cached debug flag.
101         */
102        private boolean debug = false;
103    
104        /**
105         * Cached displayMinimizedWindow flag.
106         */
107        private boolean displayMinimizedWindow = false;
108    
109        /**
110         * Cached disposeMinimizedWindow flag.
111         */
112        private boolean disposeMinimizedWindow = true;
113    
114        /**
115         * Cached failOnError flag.
116         */
117        private boolean failOnError = false;
118    
119        /**
120         * Cached filter instance.
121         */
122        private LaunchFilter filter = null;
123    
124        /**
125         * Cached filterClassName.
126         */
127        private String filterClassName = null;
128    
129        /**
130         * Cached filterClasspath.
131         */
132        private Path filterClasspath = null;
133    
134        /**
135         * Cached main class name.
136         */
137        private String mainClassName = null;
138    
139        /**
140         * Cached minimizedWindowIcon.
141         */
142        private File minimizedWindowIcon = null;
143    
144        /**
145         * Cached minimizedWindowTitle.
146         */
147        private String minimizedWindowTitle = null;
148    
149        /**
150         * Cached output file.
151         */
152        private File outputFile = null;
153    
154        /**
155         * Cached print flag.
156         */
157        private boolean print = false;
158    
159        /**
160         * Cached redirect flag.
161         */
162        private boolean redirect = false;
163    
164        /**
165         * Cached requireTools flag.
166         */
167        private boolean requireTools = false;
168    
169        /**
170         * Cached arg elements
171         */
172        private ArgumentSet taskArgumentSet = new ArgumentSet();
173    
174        /**
175         * Cached jvmarg elements
176         */
177        private JVMArgumentSet taskJVMArgumentSet = new JVMArgumentSet();
178    
179        /**
180         * Cached sysproperty elements
181         */
182        private SysPropertySet taskSysPropertySet = new SysPropertySet();
183    
184        /**
185         * Cached useArgs flag.
186         */
187        private boolean useArgs = true;
188    
189        /**
190         * Cached useSystemIn flag.
191         */
192        private boolean useSystemIn = true;
193    
194        /**
195         * Cached waitForChild flag.
196         */
197        private boolean waitForChild = true;
198    
199        //---------------------------------------------------------- Static Methods
200    
201        /**
202         * Get the synchronous child processes for all instances of this class.
203         *
204         * @return the instances of this class.
205         */
206        public static Process[] getChildProcesses() {
207    
208            return (Process[])childProcesses.toArray(new Process[childProcesses.size()]);
209    
210        }
211    
212        //----------------------------------------------------------------- Methods
213    
214        /**
215         * Add a nested arg element. Note that Ant will not invoke the specified
216         * arg object's setter methods until after Ant invokes this method so
217         * processing of the specified arg object is handled in the
218         * {@link #execute()} method.
219         *
220         * @param arg the arg element
221         */
222        public void addArg(ConditionalArgument arg) {
223    
224            taskArgumentSet.addArg(arg);
225    
226        }
227    
228        /**
229         * Add a nested argset element.
230         *
231         * @param set the argset element
232         */
233        public void addArgset(ArgumentSet set) {
234    
235            taskArgumentSet.addArgset(set);
236    
237        }
238    
239        /**
240         * Add a nested jvmarg element. Note that Ant will not invoke the specified
241         * jvmarg object's setter methods until after Ant invokes this method so
242         * processing of the specified jvmarg object is handled in the
243         * {@link #execute()} method.
244         *
245         * @param jvmArg the jvmarg element
246         */
247        public void addJvmarg(ConditionalArgument jvmArg) {
248    
249            taskJVMArgumentSet.addJvmarg(jvmArg);
250    
251        }
252    
253        /**
254         * Add a nested jvmargset element.
255         *
256         * @param set the jvmargset element
257         */
258        public void addJvmargset(JVMArgumentSet set) {
259    
260            taskJVMArgumentSet.addJvmargset(set);
261    
262        }
263    
264        /**
265         * Add a nested sysproperty element. Note that Ant will not invoke the
266         * specified sysproperty object's setter methods until after Ant invokes
267         * this method so processing of the specified sysproperty object is handled
268         * in the {@link #execute()} method.
269         *
270         * @param var the sysproperty element
271         */
272        public void addSysproperty(ConditionalVariable var) {
273    
274            taskSysPropertySet.addSysproperty(var);
275    
276        }
277    
278        /**
279         * Add a nested syspropertyset element.
280         *
281         * @param set the syspropertyset element
282         */
283        public void addSyspropertyset(SysPropertySet set) {
284    
285            taskSysPropertySet.addSyspropertyset(set);
286    
287        }
288    
289        /**
290         * Create a nested classpath element.
291         *
292         * @return the Path object that contains all nested classpath elements
293         */
294        public Path createClasspath() {
295    
296            if (classpath == null)
297                classpath = new Path(project);
298            return classpath;
299    
300        }
301    
302        /**
303         * Create a nested filter classpath element.
304         *
305         * @return the Path object that contains all nested filter classpath
306         *  elements
307         */
308        public Path createFilterclasspath() {
309    
310            if (filterClasspath == null)
311                filterClasspath = new Path(project);
312            return filterClasspath;
313    
314        }
315    
316        /**
317         * Construct a Java command and execute it using the settings that Ant
318         * parsed from the Launcher's XML file. This method is called by the Ant
319         * classes.
320         *
321         * @throws BuildException if there is a configuration or other error
322         */
323        public void execute() throws BuildException {
324    
325            try {
326    
327                // Check that the Launcher class was used to start Ant as this
328                // task is not designed to use in a standalone Ant installation
329                if (!Launcher.isStarted())
330                    throw new BuildException(Launcher.getLocalizedString("no.run.standalone", this.getClass().getName()));
331    
332                // Don't do anything if the launching process has been stopped
333                if (Launcher.isStopped())
334                    throw new BuildException();
335    
336                if (mainClassName == null)
337                    throw new BuildException(Launcher.getLocalizedString("classname.null", this.getClass().getName()));
338    
339                // Copy all of the nested jvmarg elements into the jvmArgs object
340                ArrayList taskJVMArgs = taskJVMArgumentSet.getList();
341                ArrayList jvmArgs = new ArrayList(taskJVMArgs.size());
342                for (int i = 0; i < taskJVMArgs.size(); i++) {
343                    ConditionalArgument value = (ConditionalArgument)taskJVMArgs.get(i);
344                    // Test "if" and "unless" conditions
345                    if (testIfCondition(value.getIf()) && testUnlessCondition(value.getUnless())) {
346                        String[] list = value.getParts();
347                        for (int j = 0; j < list.length; j++)
348                            jvmArgs.add(list[j]);
349                    }
350                }
351    
352                // Copy all of the nested sysproperty elements into the sysProps
353                // object
354                ArrayList taskSysProps = taskSysPropertySet.getList();
355                HashMap sysProps = new HashMap(taskSysProps.size());
356                for (int i = 0; i < taskSysProps.size(); i++) {
357                    ConditionalVariable variable = (ConditionalVariable)taskSysProps.get(i);
358                    // Test "if" and "unless" conditions
359                    if (testIfCondition(variable.getIf()) && testUnlessCondition(variable.getUnless()))
360                        sysProps.put(variable.getKey(), variable.getValue());
361                }
362    
363                // Copy all of the nested arg elements into the appArgs object
364                ArrayList taskArgs = taskArgumentSet.getList();
365                ArrayList appArgs = new ArrayList(taskArgs.size());
366                for (int i = 0; i < taskArgs.size(); i++) {
367                    ConditionalArgument value = (ConditionalArgument)taskArgs.get(i);
368                    // Test "if" and "unless" conditions
369                    if (testIfCondition(value.getIf()) && testUnlessCondition(value.getUnless())) {
370                        String[] list = value.getParts();
371                        for (int j = 0; j < list.length; j++)
372                            appArgs.add(list[j]);
373                    }
374                }
375    
376                // Add the Launcher's command line arguments to the appArgs object
377                if (useArgs) {
378                    int currentArg = 0;
379                    String arg = null;
380                    while ((arg = project.getUserProperty(LaunchTask.ARG_PROP_NAME + Integer.toString(currentArg++))) != null)
381                        appArgs.add(arg);
382                }
383    
384                // Make working copies of some of the flags since they may get
385                // changed by a filter class
386                String filteredClasspath = null;
387                if (classpath != null)
388                    filteredClasspath = classpath.toString();
389                String filteredMainClassName = mainClassName;
390                boolean filteredRedirect = redirect;
391                File filteredOutputFile = outputFile;
392                boolean filteredAppendOutput = appendOutput;
393                boolean filteredDebug = debug;
394                boolean filteredDisplayMinimizedWindow = displayMinimizedWindow;
395                boolean filteredDisposeMinimizedWindow = disposeMinimizedWindow;
396                boolean filteredFailOnError = failOnError;
397                String filteredMinimizedWindowTitle = minimizedWindowTitle;
398                File filteredMinimizedWindowIcon = minimizedWindowIcon;
399                boolean filteredPrint = print;
400                boolean filteredRequireTools = requireTools;
401                boolean filteredUseSystemIn = useSystemIn;
402                boolean filteredWaitForChild = waitForChild;
403    
404                // If there is a filter in the filterclassname attribute, let it
405                // evaluate and edit the attributes and nested elements before we
406                // start evaluating them
407                if (filterClassName != null) {
408                     if (filter == null) {
409                         try {
410                             ClassLoader loader = this.getClass().getClassLoader();
411                             if (filterClasspath != null) {
412                                 // Construct a class loader to load the class
413                                 String[] fileList = filterClasspath.list();
414                                 URL[] urls = new URL[fileList.length];
415                                 for (int i = 0; i < fileList.length; i++)
416                                     urls[i] = new File(fileList[i]).toURL();
417                                 loader = new URLClassLoader(urls, loader);
418                             }
419                             Class filterClass = loader.loadClass(filterClassName);
420                             filter = (LaunchFilter)filterClass.newInstance();
421                             // Execute filter and save any changes
422                             LaunchCommand command = new LaunchCommand();
423                             command.setJvmargs(jvmArgs);
424                             command.setSysproperties(sysProps);
425                             command.setArgs(appArgs);
426                             command.setClasspath(filteredClasspath);
427                             command.setClassname(filteredMainClassName);
428                             command.setRedirectoutput(filteredRedirect);
429                             command.setOutput(filteredOutputFile);
430                             command.setAppendoutput(filteredAppendOutput);
431                             command.setDebug(filteredDebug);
432                             command.setDisplayminimizedwindow(filteredDisplayMinimizedWindow);
433                             command.setDisposeminimizedwindow(filteredDisposeMinimizedWindow);
434                             command.setFailonerror(filteredFailOnError);
435                             command.setMinimizedwindowtitle(filteredMinimizedWindowTitle);
436                             command.setMinimizedwindowicon(filteredMinimizedWindowIcon);
437                             command.setPrint(filteredPrint);
438                             command.setRequiretools(filteredRequireTools);
439                             command.setUsesystemin(filteredUseSystemIn);
440                             command.setWaitforchild(filteredWaitForChild);
441                             filter.filter(command);
442                             jvmArgs = command.getJvmargs();
443                             sysProps = command.getSysproperties();
444                             appArgs = command.getArgs();
445                             filteredClasspath = command.getClasspath();
446                             filteredMainClassName = command.getClassname();
447                             filteredRedirect = command.getRedirectoutput();
448                             filteredOutputFile = command.getOutput();
449                             filteredAppendOutput = command.getAppendoutput();
450                             filteredDebug = command.getDebug();
451                             filteredDisplayMinimizedWindow = command.getDisplayminimizedwindow();
452                             filteredDisposeMinimizedWindow = command.getDisposeminimizedwindow();
453                             filteredFailOnError = command.getFailonerror();
454                             filteredMinimizedWindowTitle = command.getMinimizedwindowtitle();
455                             filteredMinimizedWindowIcon = command.getMinimizedwindowicon();
456                             filteredPrint = command.getPrint();
457                             filteredRequireTools = command.getRequiretools();
458                             filteredUseSystemIn = command.getUsesystemin();
459                             filteredWaitForChild = command.getWaitforchild();
460                             // Check changes
461                             if (filteredMainClassName == null)
462                                 throw new BuildException(Launcher.getLocalizedString("classname.null", this.getClass().getName()));
463                             if (jvmArgs == null)
464                                 jvmArgs = new ArrayList();
465                             if (sysProps == null)
466                                 sysProps = new HashMap();
467                             if (appArgs == null)
468                                 appArgs = new ArrayList();
469                         } catch (BuildException be) {
470                             throw new BuildException(filterClassName + " " + Launcher.getLocalizedString("filter.exception", this.getClass().getName()), be);
471                         } catch (ClassCastException cce) {
472                             throw new BuildException(filterClassName + " " + Launcher.getLocalizedString("filter.not.filter", this.getClass().getName()));
473                         } catch (Exception e) {
474                             throw new BuildException(e);
475                         }
476                     }
477                }
478    
479                // Force child JVM into foreground if running using JDB
480                if (filteredDebug) {
481                    filteredWaitForChild = true;
482                    filteredUseSystemIn = true;
483                }
484    
485                // Prepend standard paths to classpath
486                StringBuffer fullClasspath = new StringBuffer(Launcher.getBootstrapFile().getPath());
487                if (filteredRequireTools) {
488                    fullClasspath.append(File.pathSeparator);
489                    fullClasspath.append(Launcher.getToolsClasspath());
490                }
491                if (filteredClasspath != null) {
492                    fullClasspath.append(File.pathSeparator);
493                    fullClasspath.append(filteredClasspath);
494                }
495    
496                // Set ChildMain.WAIT_FOR_CHILD_PROP_NAME property for child JVM
497                sysProps.remove(ChildMain.WAIT_FOR_CHILD_PROP_NAME);
498                if (filteredWaitForChild)
499                    sysProps.put(ChildMain.WAIT_FOR_CHILD_PROP_NAME, "");
500    
501                // Set minimized window properties for child JVM
502                sysProps.remove(ChildMain.DISPLAY_MINIMIZED_WINDOW_PROP_NAME);
503                sysProps.remove(ChildMain.MINIMIZED_WINDOW_TITLE_PROP_NAME);
504                sysProps.remove(ChildMain.MINIMIZED_WINDOW_ICON_PROP_NAME);
505                sysProps.remove(ChildMain.DISPOSE_MINIMIZED_WINDOW_PROP_NAME);
506                if (!filteredWaitForChild && filteredDisplayMinimizedWindow) {
507                    sysProps.put(ChildMain.DISPLAY_MINIMIZED_WINDOW_PROP_NAME, "");
508                    if (filteredMinimizedWindowTitle != null)
509                        sysProps.put(ChildMain.MINIMIZED_WINDOW_TITLE_PROP_NAME, filteredMinimizedWindowTitle);
510                    else
511                        sysProps.put(ChildMain.MINIMIZED_WINDOW_TITLE_PROP_NAME, getOwningTarget().getName());
512                    if (filteredMinimizedWindowIcon != null)
513                        sysProps.put(ChildMain.MINIMIZED_WINDOW_ICON_PROP_NAME, filteredMinimizedWindowIcon.getCanonicalPath());
514                    // Set ChildMain.DISPOSE_MINIMIZED_WINDOW_PROP_NAME property
515                    if (filteredDisposeMinimizedWindow)
516                        sysProps.put(ChildMain.DISPOSE_MINIMIZED_WINDOW_PROP_NAME, "");
517                }
518    
519                // Set ChildMain.OUTPUT_FILE_PROP_NAME property for child JVM
520                sysProps.remove(ChildMain.OUTPUT_FILE_PROP_NAME);
521                if (!filteredWaitForChild && filteredRedirect) {
522                    if (filteredOutputFile != null) {
523                        String outputFilePath = filteredOutputFile.getCanonicalPath();
524                        // Verify that we can write to the output file
525                        try {
526                            File parentFile = new File(filteredOutputFile.getParent());
527                            // To take care of non-existent log directories
528                            if ( !parentFile.exists() ) {
529                                //Trying to create non-existent parent directories
530                                parentFile.mkdirs();
531                                //If this fails createNewFile also fails
532                                //We can give more exact error message, if we choose
533                            }
534                            filteredOutputFile.createNewFile();
535                        } catch (IOException ioe) {
536                            throw new BuildException(outputFilePath + " " + Launcher.getLocalizedString("output.file.not.creatable", this.getClass().getName()));
537                        }
538                        if (!filteredOutputFile.canWrite())
539                            throw new BuildException(outputFilePath + " " + Launcher.getLocalizedString("output.file.not.writable", this.getClass().getName()));
540                        sysProps.put(ChildMain.OUTPUT_FILE_PROP_NAME, outputFilePath);
541                        if (filteredAppendOutput)
542                            sysProps.put(ChildMain.APPEND_OUTPUT_PROP_NAME, "");
543                        Launcher.getLog().println(Launcher.getLocalizedString("redirect.notice", this.getClass().getName()) + " " + outputFilePath);
544                    } else {
545                        throw new BuildException(Launcher.getLocalizedString("output.file.null", this.getClass().getName()));
546                    }
547                }
548    
549                // Create the heartbeatFile. This file is needed by the
550                // ParentListener class on Windows since the entire child JVM
551                // process will block on Windows machines using some versions of
552                // Unix shells such as MKS, etc.
553                File heartbeatFile = null;
554                FileOutputStream heartbeatOutputStream = null;
555                if (filteredWaitForChild) {
556                    File tmpDir = null;
557                    String tmpDirName = (String)sysProps.get("java.io.tmpdir");
558                    if (tmpDirName != null)
559                        tmpDir = new File(tmpDirName);
560                    heartbeatFile = File.createTempFile(ChildMain.HEARTBEAT_FILE_PROP_NAME + ".", "", tmpDir);
561                    // Open the heartbeat file for writing so that it the child JVM
562                    // will not be able to delete it while this process is running
563                    heartbeatOutputStream = new FileOutputStream(heartbeatFile);
564                    sysProps.put(ChildMain.HEARTBEAT_FILE_PROP_NAME, heartbeatFile.getCanonicalPath());
565                }
566    
567                // Assemble child command
568                String[] cmd = new String[5 + jvmArgs.size() + sysProps.size() + appArgs.size()];
569                int nextCmdArg = 0;
570                if (filteredDebug)
571                    cmd[nextCmdArg++] = Launcher.getJDBCommand();
572                else
573                    cmd[nextCmdArg++] = Launcher.getJavaCommand();
574                // Add jvmArgs to command
575                for (int i = 0; i < jvmArgs.size(); i++)
576                    cmd[nextCmdArg++] = (String)jvmArgs.get(i);
577                // Add properties to command
578                Iterator sysPropsKeys = sysProps.keySet().iterator();
579                while (sysPropsKeys.hasNext()) {
580                    String key = (String)sysPropsKeys.next();
581                    if (key == null)
582                        continue;
583                    String value = (String)sysProps.get(key);
584                    if (value == null)
585                        value = "";
586                    cmd[nextCmdArg++] = "-D" + key + "=" + value;
587                }
588                // Add classpath to command. Note that it is after the jvmArgs
589                // and system properties to prevent the user from sneaking in an
590                // alterate classpath through the jvmArgs.
591                cmd[nextCmdArg++] = "-classpath";
592                cmd[nextCmdArg++] = fullClasspath.toString();
593                // Add main class to command
594                int mainClassArg = nextCmdArg;
595                cmd[nextCmdArg++] = ChildMain.class.getName();
596                cmd[nextCmdArg++] = filteredMainClassName;
597                // Add args to command
598                for (int i = 0; i < appArgs.size(); i++)
599                {
600                    cmd[nextCmdArg++] = (String)appArgs.get(i);
601                }
602                // Print command
603                if (filteredPrint) {
604                    // Quote the command arguments
605                    String osname = System.getProperty("os.name").toLowerCase();
606                    StringBuffer buf = new StringBuffer(cmd.length * 100);
607                    String quote = null;
608                    String replaceQuote = null;
609                    if (osname.indexOf("windows") >= 0) {
610                        // Use double-quotes to quote on Windows
611                        quote = "\"";
612                        replaceQuote = quote + quote + quote;
613                    } else {
614                        // Use single-quotes to quote on Unix
615                        quote = "'";
616                        replaceQuote = quote + "\\" + quote + quote;
617                    }
618                    for (int i = 0; i < cmd.length; i++) {
619                        // Pull ChildMain out of command as we want to print the
620                        // real JVM command that can be executed by the user
621                        if (i == mainClassArg)
622                            continue;
623                        if (i > 0)
624                            buf.append(" ");
625                        buf.append(quote);
626                        StringTokenizer tokenizer = new StringTokenizer(cmd[i], quote, true);
627                        while (tokenizer.hasMoreTokens()) {
628                            String token = tokenizer.nextToken();
629                            if (quote.equals(token))
630                                buf.append(replaceQuote);
631                            else
632                                buf.append(token);
633                        }
634                        buf.append(quote);
635                    }
636                    // Print the quoted command
637                    System.err.println(Launcher.getLocalizedString("executing.child.command", this.getClass().getName()) + ":");
638                    System.err.println(buf.toString());
639                }
640    
641                // Create a child JVM
642                if (Launcher.isStopped())
643                    throw new BuildException();
644                Process proc = null;
645                synchronized (LaunchTask.childProcesses) {
646                    proc = Runtime.getRuntime().exec(cmd);
647                    // Add the synchronous child process
648                    if (filteredWaitForChild) {
649                        childProc = proc;
650                        LaunchTask.childProcesses.add(proc);
651                    }
652                }
653                if (filteredWaitForChild) {
654                    StreamConnector stdout =
655                        new StreamConnector(proc.getInputStream(), System.out);
656                    StreamConnector stderr =
657                        new StreamConnector(proc.getErrorStream(), System.err);
658                    stdout.start();
659                    stderr.start();
660                    if (filteredUseSystemIn) {
661                        StreamConnector stdin =
662                            new StreamConnector(System.in, proc.getOutputStream());
663                        stdin.start();
664                    }
665                    proc.waitFor();
666                    // Let threads flush any unflushed output
667                    stdout.join();
668                    stderr.join();
669                    if (heartbeatOutputStream != null)
670                        heartbeatOutputStream.close();
671                    if (heartbeatFile != null)
672                        heartbeatFile.delete();
673                    int exitValue = proc.exitValue();
674                    if (filteredFailOnError && exitValue != 0)
675                        throw new BuildException(Launcher.getLocalizedString("child.failed", this.getClass().getName()) + " " + exitValue);
676                }
677                // Need to check if the launching process has stopped because
678                // processes don't throw exceptions when they are terminated
679                if (Launcher.isStopped())
680                    throw new BuildException();
681    
682            } catch (BuildException be) {
683                throw be;
684            } catch (Exception e) {
685                if (Launcher.isStopped())
686                    throw new BuildException(Launcher.getLocalizedString("launch.task.stopped", this.getClass().getName()));
687                else 
688                    throw new BuildException(e);
689            }
690    
691        }
692    
693        /**
694         * Set the useArgs flag. Setting this flag to true will cause this
695         * task to append all of the command line arguments used to start the
696         * {@link Launcher#start(String[])} method to the arguments
697         * passed to the child JVM.
698         *
699         * @param useArgs the useArgs flag
700         */
701        public void setUseargs(boolean useArgs) {
702    
703            this.useArgs = useArgs;
704    
705        }
706    
707        /**
708         * Set the useSystemIn flag. Setting this flag to false will cause this 
709         * task to not read System.in. This will cause the child JVM to never
710         * receive any bytes when it reads System.in. Setting this flag to false
711         * is useful in some Unix environments where processes cannot be put in
712         * the background when they read System.in.
713         *
714         * @param useSystemIn the useSystemIn flag
715         */
716        public void setUsesystemin(boolean useSystemIn) {
717    
718            this.useSystemIn = useSystemIn;
719    
720        }
721    
722        /**
723         * Set the waitForChild flag. Setting this flag to true will cause this
724         * task to wait for the child JVM to finish executing before the task
725         * completes. Setting this flag to false will cause this task to complete
726         * immediately after it starts the execution of the child JVM. Setting it
727         * false emulates the "&" background operator in most Unix shells and is
728         * most of set to false when launching server or GUI applications.
729         *
730         * @param waitForChild the waitForChild flag
731         */
732        public void setWaitforchild(boolean waitForChild) {
733    
734            this.waitForChild = waitForChild;
735    
736        }
737    
738        /**
739         * Set the class name.
740         *
741         * @param mainClassName the class to execute <code>main(String[])</code>
742         */
743        public void setClassname(String mainClassName) {
744    
745            this.mainClassName = mainClassName;
746    
747        }
748    
749        /**
750         * Set the classpath.
751         *
752         * @param classpath the classpath
753         */
754        public void setClasspath(Path classpath) {
755    
756            createClasspath().append(classpath);
757    
758        }
759    
760        /**
761         * Adds a reference to a classpath defined elsewhere.
762         *
763         * @param ref reference to the classpath
764         */
765        public void setClasspathref(Reference ref) {
766    
767            createClasspath().setRefid(ref);
768    
769        }
770    
771        /**
772         * Set the debug flag. Setting this flag to true will cause this
773         * task to run the child JVM using the JDB debugger.
774         *
775         * @param debug the debug flag
776         */
777        public void setDebug(boolean debug) {
778    
779            this.debug = debug;
780    
781        }
782    
783        /**
784         * Set the displayMinimizedWindow flag. Note that this flag has no effect
785         * on non-Windows platforms. On Windows platform, setting this flag to true
786         * will cause a minimized window to be displayed in the Windows task bar
787         * while the child process is executing. This flag is usually set to true
788         * for server applications that also have their "waitForChild" attribute
789         * set to false via the {@link #setWaitforchild(boolean)} method.
790         *
791         * @param displayMinimizedWindow true if a minimized window should be
792         *  displayed in the Windows task bar while the child process is executing 
793         */
794        public void setDisplayminimizedwindow(boolean displayMinimizedWindow) {
795    
796            this.displayMinimizedWindow = displayMinimizedWindow;
797    
798        }
799    
800        /**
801         * Set the disposeMinimizedWindow flag. Note that this flag has no effect
802         * on non-Windows platforms. On Windows platform, setting this flag to true
803         * will cause any minimized window that is display by setting the
804         * "displayMinimizedWindow" attribute to true via the
805         * {@link #setDisplayminimizedwindow(boolean)} to be automatically
806         * disposed of when the child JVM's <code>main(String[])</code> returns.
807         * This flag is normally used for applications that don't explicitly call
808         * {@link System#exit(int)}. If an application does not explicitly call
809         * {@link System#exit(int)}, an minimized windows need to be disposed of
810         * for the child JVM to exit.
811         *
812         * @param disposeMinimizedWindow true if a minimized window in the Windows
813         *  taskbar should be automatically disposed of after the child JVM's
814         *  <code>main(String[])</code> returns
815         */
816        public void setDisposeminimizedwindow(boolean disposeMinimizedWindow) {
817    
818            this.disposeMinimizedWindow = disposeMinimizedWindow;
819    
820        }
821    
822        /**
823         * Set the failOnError flag.
824         *
825         * @param failOnError true if the launch process should stop if the child
826         *  JVM returns an exit value other than 0
827         */
828        public void setFailonerror(boolean failOnError) {
829    
830            this.failOnError = failOnError;
831    
832        }
833        /**
834         * Set the filter class name.
835         *
836         * @param filterClassName the class that implements the
837         *  {@link LaunchFilter} interface
838         */
839        public void setFilterclassname(String filterClassName) {
840    
841            this.filterClassName = filterClassName;
842    
843        }
844    
845        /**
846         * Set the filter class' classpath.
847         *
848         * @param filterClasspath the classpath for the filter class
849         */
850        public void setFilterclasspath(Path filterClasspath) {
851    
852            createFilterclasspath().append(filterClasspath);
853    
854        }
855    
856        /**
857         * Set the title for the minimized window that will be displayed in the
858         * Windows taskbar. Note that this property has no effect on non-Windows
859         * platforms.
860         *
861         * @param minimizedWindowTitle the title to set for any minimized window
862         *  that is displayed in the Windows taskbar
863         */
864        public void setMinimizedwindowtitle(String minimizedWindowTitle) {
865    
866            this.minimizedWindowTitle = minimizedWindowTitle;
867    
868        }
869    
870        /**
871         * Set the icon file for the minimized window that will be displayed in the
872         * Windows taskbar. Note that this property has no effect on non-Windows
873         * platforms.
874         *
875         * @param minimizedWindowIcon the icon file to use for any minimized window
876         *  that is displayed in the Windows taskbar
877         */
878        public void setMinimizedwindowicon(File minimizedWindowIcon) {
879    
880            this.minimizedWindowIcon = minimizedWindowIcon;
881    
882        }
883    
884        /**
885         * Set the file that the child JVM's System.out and System.err will be
886         * redirected to. Output will only be redirected if the redirect flag
887         * is set to true via the {@link #setRedirectoutput(boolean)} method.
888         *
889         * @param outputFile a File to redirect System.out and System.err to
890         */
891        public void setOutput(File outputFile) {
892    
893            this.outputFile = outputFile;
894    
895        }
896    
897        /**
898         * Set the print flag. Setting this flag to true will cause the full child
899         * JVM command to be printed to {@link System#out}.
900         *
901         * @param print the print flag
902         */
903        public void setPrint(boolean print) {
904    
905            this.print = print;
906    
907        }
908    
909        /**
910         * Set the appendOutput flag. Setting this flag to true will cause the child
911         * JVM to append System.out and System.err to the file specified by the
912         * {@link #setOutput(File)} method. Setting this flag to false will cause
913         * the child to overwrite the file.
914         *
915         * @param appendOutput true if output should be appended to the output file
916         */
917        public void setAppendoutput(boolean appendOutput) {
918    
919            this.appendOutput = appendOutput;
920    
921        }
922    
923        /**
924         * Set the redirect flag. Setting this flag to true will cause the child
925         * JVM's System.out and System.err to be redirected to file set using the
926         * {@link #setOutput(File)} method. Setting this flag to false will
927         * cause no redirection.
928         *
929         * @param redirect true if System.out and System.err should be redirected
930         */
931        public void setRedirectoutput(boolean redirect) {
932    
933            this.redirect = redirect;
934    
935        }
936    
937        /**
938         * Set the requireTools flag. Setting this flag to true will cause the
939         * JVM's tools.jar to be added to the child JVM's classpath. This
940         * sets an explicit requirement that the user use a JDK instead of a
941         * JRE. Setting this flag to false explicitly allows the user to use
942         * a JRE.
943         *
944         * @param requireTools true if a JDK is required and false if only a JRE
945         *  is required
946         */
947        public void setRequiretools(boolean requireTools) {
948    
949            this.requireTools = requireTools;
950    
951        }
952    
953        /**
954         * Determine if the "if" condition flag for a nested element meets all
955         * criteria for use.
956         *
957         * @param ifCondition the "if" condition flag for a nested element
958         * @return true if the nested element should be process and false if it
959         *  should be ignored
960         */
961        private boolean testIfCondition(String ifCondition) {
962    
963            if (ifCondition == null || "".equals(ifCondition))
964                return true;
965            return project.getProperty(ifCondition) != null;
966    
967        }
968    
969        /**
970         * Determine if the "unless" condition flag for a nested element meets all
971         * criteria for use.
972         *
973         * @param unlessCondition the "unless" condition flag for a nested element
974         * @return true if the nested element should be process and false if it
975         *  should be ignored
976         */
977        private boolean testUnlessCondition(String unlessCondition) {
978    
979            if (unlessCondition == null || "".equals(unlessCondition))
980                return true;
981            return project.getProperty(unlessCondition) == null;
982    
983        }
984    
985    }