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
018package org.apache.commons.launcher;
019
020import java.io.File;
021import java.io.FileOutputStream;
022import java.io.IOException;
023import java.net.URL;
024import java.net.URLClassLoader;
025import java.util.ArrayList;
026import java.util.HashMap;
027import java.util.Iterator;
028import java.util.StringTokenizer;
029import org.apache.commons.launcher.types.ArgumentSet;
030import org.apache.commons.launcher.types.ConditionalArgument;
031import org.apache.commons.launcher.types.ConditionalVariable;
032import org.apache.commons.launcher.types.JVMArgumentSet;
033import org.apache.commons.launcher.types.SysPropertySet;
034import org.apache.tools.ant.BuildException;
035import org.apache.tools.ant.Task;
036import org.apache.tools.ant.types.Path;
037import 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 */
063public 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}