[SCM] UNNAMED PROJECT branch, master, updated. 7e42d6e3c0f53a26e963d73a09650173b78f6cc0

tony mancill tmancill at debian.org
Mon Apr 15 00:50:46 UTC 2013


The following commit has been merged in the master branch:
commit e455adfd78f1c0543c46b375dd15f202788ab72d
Author: tony mancill <tmancill at debian.org>
Date:   Sun Apr 14 17:40:11 2013 -0700

    merge upstream version 1.1 into master

diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 0000000..f820d4b
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,203 @@
+/*
+ *                                 Apache License
+ *                           Version 2.0, January 2004
+ *                        http://www.apache.org/licenses/
+ *
+ *   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+ *
+ *   1. Definitions.
+ *
+ *      "License" shall mean the terms and conditions for use, reproduction,
+ *      and distribution as defined by Sections 1 through 9 of this document.
+ *
+ *      "Licensor" shall mean the copyright owner or entity authorized by
+ *      the copyright owner that is granting the License.
+ *
+ *      "Legal Entity" shall mean the union of the acting entity and all
+ *      other entities that control, are controlled by, or are under common
+ *      control with that entity. For the purposes of this definition,
+ *      "control" means (i) the power, direct or indirect, to cause the
+ *      direction or management of such entity, whether by contract or
+ *      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ *      outstanding shares, or (iii) beneficial ownership of such entity.
+ *
+ *      "You" (or "Your") shall mean an individual or Legal Entity
+ *      exercising permissions granted by this License.
+ *
+ *      "Source" form shall mean the preferred form for making modifications,
+ *      including but not limited to software source code, documentation
+ *      source, and configuration files.
+ *
+ *      "Object" form shall mean any form resulting from mechanical
+ *      transformation or translation of a Source form, including but
+ *      not limited to compiled object code, generated documentation,
+ *      and conversions to other media types.
+ *
+ *      "Work" shall mean the work of authorship, whether in Source or
+ *      Object form, made available under the License, as indicated by a
+ *      copyright notice that is included in or attached to the work
+ *      (an example is provided in the Appendix below).
+ *
+ *      "Derivative Works" shall mean any work, whether in Source or Object
+ *      form, that is based on (or derived from) the Work and for which the
+ *      editorial revisions, annotations, elaborations, or other modifications
+ *      represent, as a whole, an original work of authorship. For the purposes
+ *      of this License, Derivative Works shall not include works that remain
+ *      separable from, or merely link (or bind by name) to the interfaces of,
+ *      the Work and Derivative Works thereof.
+ *
+ *      "Contribution" shall mean any work of authorship, including
+ *      the original version of the Work and any modifications or additions
+ *      to that Work or Derivative Works thereof, that is intentionally
+ *      submitted to Licensor for inclusion in the Work by the copyright owner
+ *      or by an individual or Legal Entity authorized to submit on behalf of
+ *      the copyright owner. For the purposes of this definition, "submitted"
+ *      means any form of electronic, verbal, or written communication sent
+ *      to the Licensor or its representatives, including but not limited to
+ *      communication on electronic mailing lists, source code control systems,
+ *      and issue tracking systems that are managed by, or on behalf of, the
+ *      Licensor for the purpose of discussing and improving the Work, but
+ *      excluding communication that is conspicuously marked or otherwise
+ *      designated in writing by the copyright owner as "Not a Contribution."
+ *
+ *      "Contributor" shall mean Licensor and any individual or Legal Entity
+ *      on behalf of whom a Contribution has been received by Licensor and
+ *      subsequently incorporated within the Work.
+ *
+ *   2. Grant of Copyright License. Subject to the terms and conditions of
+ *      this License, each Contributor hereby grants to You a perpetual,
+ *      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ *      copyright license to reproduce, prepare Derivative Works of,
+ *      publicly display, publicly perform, sublicense, and distribute the
+ *      Work and such Derivative Works in Source or Object form.
+ *
+ *   3. Grant of Patent License. Subject to the terms and conditions of
+ *      this License, each Contributor hereby grants to You a perpetual,
+ *      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ *      (except as stated in this section) patent license to make, have made,
+ *      use, offer to sell, sell, import, and otherwise transfer the Work,
+ *      where such license applies only to those patent claims licensable
+ *      by such Contributor that are necessarily infringed by their
+ *      Contribution(s) alone or by combination of their Contribution(s)
+ *      with the Work to which such Contribution(s) was submitted. If You
+ *      institute patent litigation against any entity (including a
+ *      cross-claim or counterclaim in a lawsuit) alleging that the Work
+ *      or a Contribution incorporated within the Work constitutes direct
+ *      or contributory patent infringement, then any patent licenses
+ *      granted to You under this License for that Work shall terminate
+ *      as of the date such litigation is filed.
+ *
+ *   4. Redistribution. You may reproduce and distribute copies of the
+ *      Work or Derivative Works thereof in any medium, with or without
+ *      modifications, and in Source or Object form, provided that You
+ *      meet the following conditions:
+ *
+ *      (a) You must give any other recipients of the Work or
+ *          Derivative Works a copy of this License; and
+ *
+ *      (b) You must cause any modified files to carry prominent notices
+ *          stating that You changed the files; and
+ *
+ *      (c) You must retain, in the Source form of any Derivative Works
+ *          that You distribute, all copyright, patent, trademark, and
+ *          attribution notices from the Source form of the Work,
+ *          excluding those notices that do not pertain to any part of
+ *          the Derivative Works; and
+ *
+ *      (d) If the Work includes a "NOTICE" text file as part of its
+ *          distribution, then any Derivative Works that You distribute must
+ *          include a readable copy of the attribution notices contained
+ *          within such NOTICE file, excluding those notices that do not
+ *          pertain to any part of the Derivative Works, in at least one
+ *          of the following places: within a NOTICE text file distributed
+ *          as part of the Derivative Works; within the Source form or
+ *          documentation, if provided along with the Derivative Works; or,
+ *          within a display generated by the Derivative Works, if and
+ *          wherever such third-party notices normally appear. The contents
+ *          of the NOTICE file are for informational purposes only and
+ *          do not modify the License. You may add Your own attribution
+ *          notices within Derivative Works that You distribute, alongside
+ *          or as an addendum to the NOTICE text from the Work, provided
+ *          that such additional attribution notices cannot be construed
+ *          as modifying the License.
+ *
+ *      You may add Your own copyright statement to Your modifications and
+ *      may provide additional or different license terms and conditions
+ *      for use, reproduction, or distribution of Your modifications, or
+ *      for any such Derivative Works as a whole, provided Your use,
+ *      reproduction, and distribution of the Work otherwise complies with
+ *      the conditions stated in this License.
+ *
+ *   5. Submission of Contributions. Unless You explicitly state otherwise,
+ *      any Contribution intentionally submitted for inclusion in the Work
+ *      by You to the Licensor shall be under the terms and conditions of
+ *      this License, without any additional terms or conditions.
+ *      Notwithstanding the above, nothing herein shall supersede or modify
+ *      the terms of any separate license agreement you may have executed
+ *      with Licensor regarding such Contributions.
+ *
+ *   6. Trademarks. This License does not grant permission to use the trade
+ *      names, trademarks, service marks, or product names of the Licensor,
+ *      except as required for reasonable and customary use in describing the
+ *      origin of the Work and reproducing the content of the NOTICE file.
+ *
+ *   7. Disclaimer of Warranty. Unless required by applicable law or
+ *      agreed to in writing, Licensor provides the Work (and each
+ *      Contributor provides its Contributions) on an "AS IS" BASIS,
+ *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ *      implied, including, without limitation, any warranties or conditions
+ *      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ *      PARTICULAR PURPOSE. You are solely responsible for determining the
+ *      appropriateness of using or redistributing the Work and assume any
+ *      risks associated with Your exercise of permissions under this License.
+ *
+ *   8. Limitation of Liability. In no event and under no legal theory,
+ *      whether in tort (including negligence), contract, or otherwise,
+ *      unless required by applicable law (such as deliberate and grossly
+ *      negligent acts) or agreed to in writing, shall any Contributor be
+ *      liable to You for damages, including any direct, indirect, special,
+ *      incidental, or consequential damages of any character arising as a
+ *      result of this License or out of the use or inability to use the
+ *      Work (including but not limited to damages for loss of goodwill,
+ *      work stoppage, computer failure or malfunction, or any and all
+ *      other commercial damages or losses), even if such Contributor
+ *      has been advised of the possibility of such damages.
+ *
+ *   9. Accepting Warranty or Additional Liability. While redistributing
+ *      the Work or Derivative Works thereof, You may choose to offer,
+ *      and charge a fee for, acceptance of support, warranty, indemnity,
+ *      or other liability obligations and/or rights consistent with this
+ *      License. However, in accepting such obligations, You may act only
+ *      on Your own behalf and on Your sole responsibility, not on behalf
+ *      of any other Contributor, and only if You agree to indemnify,
+ *      defend, and hold each Contributor harmless for any liability
+ *      incurred by, or claims asserted against, such Contributor by reason
+ *      of your accepting any such warranty or additional liability.
+ *
+ *   END OF TERMS AND CONDITIONS
+ *
+ *   APPENDIX: How to apply the Apache License to your work.
+ *
+ *      To apply the Apache License to your work, attach the following
+ *      boilerplate notice, with the fields enclosed by brackets "[]"
+ *      replaced with your own identifying information. (Don't include
+ *      the brackets!)  The text should be enclosed in the appropriate
+ *      comment syntax for the file format. We also recommend that a
+ *      file or class name and description of purpose be included on the
+ *      same "printed page" as the copyright notice for easier
+ *      identification within third-party archives.
+ *
+ *   Copyright [yyyy] [name of copyright owner]
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ */
diff --git a/NOTICE.txt b/NOTICE.txt
new file mode 100644
index 0000000..5857d84
--- /dev/null
+++ b/NOTICE.txt
@@ -0,0 +1,5 @@
+Apache Commons Exec
+Copyright 2005-2010 The Apache Software Foundation
+
+This product includes software developed by
+The Apache Software Foundation (http://www.apache.org/).
diff --git a/STATUS b/STATUS
new file mode 100644
index 0000000..0f273de
--- /dev/null
+++ b/STATUS
@@ -0,0 +1,50 @@
+Proposal for Exec Package
+28th July 2005
+
+Rationale
+------------------------------------
+Executing external processes from Java is a well-known problem area. It is inheriently platform dependent and requires
+the developer to know and test for platform specific behaviors, for example using cmd.exe on Windows or limited buffer
+sizes causing deadlocks. The JRE support for this is very limited, albeit better with the new Java SE 1.5
+ProcessBuilder class.
+
+Reliably executing external processes can also require knowledge of the environment variables before or after the
+command is executed. In J2SE 1.1-1.4 there is not support for this, since the method, System.getenv(), for retriving
+environment variables is deprecated (in later releases the deprecation was removed).
+
+The are currently several different libraries that for their own purposes has implemented frameworks around
+Runtime.exec() to handle the various issue outlined above. The proposed project should aim at coordinating and
+learning from these initatives to create and maintain a simple, reusable and well-tested package. Since some of the
+more problematic platforms are not readily available, it is my hope that the broad Apache community can be a
+great help.
+
+Scope of the package
+------------------------------------
+The package shall create and maintain a process execution package written in the Java language to be distributed
+under the ASF license. The Java code might also be complemented with scripts (e.g. Perl scripts) to fully enable
+execution on some operating systems. The package should aim for supporting a wide range of operating systems while
+still having a consistent API for all platforms.
+
+Identify the initial source for the package
+------------------------------------
+Several implementations exists and should be researched before finalizing the design:
+ * Ant 1.X contains probably the most mature code within the exec task. This code has been stripped of the
+   Ant specifics and cleaned up by Niklas Gustavsson and can be donated under the ASF license.
+ * Ideas from http://ant.apache.org/ant2/actionlist.html#exec
+ * plexus-utils has a similar but slimmer BSD-licensed implementation than Ant that can be reused
+
+Identify the base name for the package
+------------------------------------
+org.apache.commons.exec
+
+INITIAL COMMITTERS
+------------------------------------
+Brett Porter
+Stefan Bodewig
+Trygve Laugstol
+
+INITIAL CONTRIBUTORS
+------------------------------------
+Niklas Gustavsson
+Kev Jackson
+
diff --git a/build.xml b/build.xml
new file mode 100644
index 0000000..6bb0942
--- /dev/null
+++ b/build.xml
@@ -0,0 +1,153 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements.  See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership.  The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License.  You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied.  See the License for the
+ specific language governing permissions and limitations
+ under the License.
+-->
+<project name="exec" default="jar" basedir=".">
+
+  <!-- include optional user-defined properties -->
+  <property file="user.properties" />
+  
+  <property file="build.properties" />
+  <property name="maven.build.version" value="1.1"/>
+  <property name="maven.build.output" value="target/classes"/>
+  <property name="maven.build.directory" value="target"/>
+  <property name="maven.build.final.name" value="commons-exec-${maven.build.version}"/>
+  <property name="maven.test.reports" value="${maven.build.directory}/test-reports"/>
+  <property name="maven.test.output" value="target/test-classes"/>
+  <property name="maven.repo.local" value="${user.home}/.m2/repository"/>
+  <!-- JUnit version should agree with the version in pom.xml -->
+  <property name="maven.junit.jar" value="${maven.repo.local}/junit/junit/3.8.1/junit-3.8.1.jar"/>
+  <!-- These must agree with the versions in pom.xml -->
+  <property name="maven.compile.source" value="1.3"/>
+  <property name="maven.compile.target" value="1.3"/>
+
+  <path id="junit">
+      <pathelement location="${maven.junit.jar}"/>
+  </path>
+
+  <target name="clean" description="Clean the output directory">
+    <delete dir="${maven.build.directory}"/>
+  </target>
+
+  <target name="compile" description="Compile the code">
+    <mkdir dir="${maven.build.output}"/>
+    <javac destdir="${maven.build.output}" excludes="**/package.html" debug="true" deprecation="true" optimize="false"
+        includeAntRuntime="false"
+        source="${maven.compile.source}" target="${maven.compile.target}">
+      <src>
+        <pathelement location="${basedir}/src/main/java"/>
+      </src>
+    </javac>
+  </target>
+
+  <target name="jar" depends="compile,test" description="Build the JAR">
+    <jar jarfile="${maven.build.directory}/${maven.build.final.name}.jar" basedir="${maven.build.output}" excludes="**/package.html"/>
+  </target>
+
+  <target name="compile-tests" depends="junit-present, compile" description="Compile the test code" if="junit.present">
+    <mkdir dir="${maven.test.output}"/>
+    <javac destdir="${maven.test.output}" excludes="**/package.html" debug="true" deprecation="true" optimize="false" 
+        includeAntRuntime="false"
+        source="${maven.compile.source}" target="${maven.compile.target}">
+      <src>
+        <pathelement location="${basedir}/src/test/java"/>
+      </src>
+      <classpath>
+        <pathelement location="${maven.build.output}"/>
+        <path refid="junit"/>
+      </classpath>
+    </javac>
+  </target>
+
+  <target name="test" depends="junit-present, compile-tests" if="junit.present" description="Run the test cases">
+    <mkdir dir="${maven.test.reports}"/>
+    <junit printSummary="yes" haltonerror="true" haltonfailure="true" fork="true" dir=".">
+      <sysproperty key="basedir" value="."/>
+      <sysproperty key="COMMONS_EXEC_DEBUG" value="true"/>
+      <sysproperty key="COMMONS_EXEC_LENIENT" value="false"/>
+      <!-- 2008-12-30 fix sgoeschl using xml formatter breaks junit on JDK 1.3.1 -->
+      <!-- <formatter type="xml"/> -->
+      <formatter type="plain" usefile="false"/>
+      <classpath>
+        <pathelement location="${maven.build.output}"/>
+        <pathelement location="${maven.test.output}"/>
+        <path refid="junit"/>
+      </classpath>
+      <batchtest todir="${maven.test.reports}">
+        <fileset dir="${basedir}/src/test/java">
+          <include name="**/*Test.java"/>
+          <exclude name="**/*Abstract*Test.java"/>
+        </fileset>
+      </batchtest>
+    </junit>
+  </target>
+
+  <target name="test-junit-present">
+    <available classname="junit.framework.Test" property="junit.present" classpathref="junit"/>
+  </target>
+
+  <target name="junit-present" depends="test-junit-present" unless="junit.present">
+    <echo>================================= WARNING ================================</echo>
+    <echo> JUnit isn't present in your classpath. Tests not executed. </echo>
+    <echo>==========================================================================</echo>
+  </target>
+
+  <!-- Starts preparing a test distribution -->
+  <target name="test-distribution-prepare" depends="clean, compile, compile-tests" if="junit.present">
+    <mkdir dir="${maven.build.directory}/dist/lib"/>
+    <!-- create the libraries -->
+    <copy file="${maven.junit.jar}" todir="${maven.build.directory}/dist/lib"/>
+    <jar jarfile="${maven.build.directory}/dist/lib/commons-exec-test-${maven.build.version}.jar" basedir="${maven.test.output}" excludes="**/package.html"/>
+    <jar jarfile="${maven.build.directory}/dist/lib/${maven.build.final.name}.jar" basedir="${maven.build.output}" excludes="**/package.html"/>
+    <!-- copy the scripts -->
+    <copy todir="${maven.build.directory}/dist">
+      <fileset dir="${basedir}/src/test/bin"/>
+      <filterset>
+        <filter token="VERSION" value="${maven.build.version}"/>
+      </filterset>
+    </copy>
+    <copy todir="${maven.build.directory}/dist/src/test/scripts">
+      <fileset dir="${basedir}/src/test/scripts"/>
+    </copy>
+    <!-- make the shell script files readable/executable -->
+    <chmod dir="${maven.build.directory}/dist" perm="ugo+rx" includes="**/*.sh"/>
+    <!-- copy the various legal files -->
+    <copy file="${basedir}/NOTICE.txt" tofile="${maven.build.directory}/dist/NOTICE.txt"/>    
+    <copy file="${basedir}/LICENSE.txt" tofile="${maven.build.directory}/dist/LICENSE.txt"/>        
+  </target>
+  
+  <!-- Create a zip containing the test environment -->
+  <target name="test-distribution-zip" depends="test-distribution-prepare" if="junit.present">
+    <zip destfile="${maven.build.directory}/commons-exec-test-${maven.build.version}.zip">
+      <fileset dir="${maven.build.directory}/dist">
+        <filename name="**/*.*"/>
+      </fileset>
+    </zip>
+  </target>
+
+  <!-- Create a tar.gz containing the test environment -->
+  <target name="test-distribution-tar" depends="test-distribution-prepare" if="junit.present">
+    <tar tarfile="${maven.build.directory}/dist/commons-exec-test-${maven.build.version}.tar" basedir="${maven.build.directory}/dist"/>
+    <gzip zipfile="${maven.build.directory}/commons-exec-test-${maven.build.version}.tar.gz" src="${maven.build.directory}/dist/commons-exec-test-${maven.build.version}.tar"/>
+    <delete file="${maven.build.directory}/dist/commons-exec-test-${maven.build.version}.tar"/>
+  </target>
+
+  <target name="test-distribution" depends="junit-present,test-distribution-zip,test-distribution-tar" description="Creates a test distribution" if="junit.present" >
+  </target>
+  
+</project>
diff --git a/findbugs-exclude-filter.xml b/findbugs-exclude-filter.xml
new file mode 100644
index 0000000..39990fe
--- /dev/null
+++ b/findbugs-exclude-filter.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0"?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+
+<!--
+  This file contains some false positive bugs detected by findbugs. Their
+  false positive nature has been analyzed individually and they have been
+  put here to instruct findbugs it must ignore them.
+-->
+<FindBugsFilter>
+
+  <!-- The following cases are intentional hard-coded paths for different operating systems -->
+  <Match>
+    <Class name="org.apache.commons.exec.environment.DefaultProcessingEnvironment" />
+    <Method name="getProcEnvCommand" params="" returns="org.apache.commons.exec.CommandLine" />
+    <Bug pattern="DMI_HARDCODED_ABSOLUTE_FILENAME" />
+  </Match>
+
+</FindBugsFilter>
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..bd6a598
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,270 @@
+<?xml version="1.0"?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <parent>
+        <groupId>org.apache.commons</groupId>
+        <artifactId>commons-parent</artifactId>
+        <version>17</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <name>Commons Exec</name>
+    <groupId>org.apache.commons</groupId>
+    <artifactId>commons-exec</artifactId>
+    <version>1.1</version>
+    <description>A library to reliably execute external processes from within the JVM</description>
+    <url>http://commons.apache.org/exec/</url>
+    <issueManagement>
+        <system>jira</system>
+        <url>http://issues.apache.org/jira/browse/EXEC</url>
+    </issueManagement>
+    <dependencies>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <!-- Must agree with the version in build.xml -->
+            <version>3.8.1</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+    <developers>
+        <developer>
+            <id>brett</id>
+            <name>Brett Porter</name>
+            <organization>Apache</organization>
+            <timezone>+10</timezone>
+        </developer>
+        <developer>
+            <id>trygvis</id>
+            <name>Trygve Laugstøl</name>
+            <organization>Apache</organization>
+            <timezone>+1</timezone>
+        </developer>
+        <developer>
+            <id>sgoeschl</id>
+            <name>Siegfried Goeschl</name>
+            <organization>Apache</organization>
+            <timezone>+1</timezone>
+        </developer>
+        <developer>
+            <id>sebb</id>
+            <name>Sebastian Bazley</name>
+            <organization>Apache</organization>
+            <timezone>+1</timezone>
+        </developer>
+    </developers>
+    <contributors>
+        <contributor>
+            <name>Niklas Gustavsson</name>
+        </contributor>
+        <contributor>
+            <name>Benjamin Bentmann</name>
+        </contributor>
+        <contributor>
+            <name>Marco Ferrante</name>
+        </contributor>
+        <contributor>
+            <name>Jerome Lacoste</name>
+        </contributor>
+        <contributor>
+            <name>Milos Kleint</name>
+        </contributor>
+        <contributor>
+            <name>Pablo Hoertner</name>
+        </contributor>
+        <contributor>
+            <name>Niall Pemberton</name>
+        </contributor>
+    </contributors>
+    <scm>
+        <connection>scm:svn:http://svn.apache.org/repos/asf/commons/proper/exec/tags/COMMONS_EXEC_1_1_RC1</connection>
+        <developerConnection>scm:svn:https://svn.apache.org/repos/asf/commons/proper/exec/tags/COMMONS_EXEC_1_1_RC1</developerConnection>
+        <url>http://svn.apache.org/viewvc/commons/proper/exec/tags/COMMONS_EXEC_1_1_RC1</url>
+    </scm>
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <configuration>
+                    <excludes>
+                        <exclude>**/TestUtil.java</exclude>
+                    </excludes>
+                </configuration>
+            </plugin>
+            <plugin>
+                <artifactId>maven-assembly-plugin</artifactId>
+                <configuration>
+                    <descriptors>
+                        <descriptor>src/assembly/bin.xml</descriptor>
+                        <descriptor>src/assembly/src.xml</descriptor>
+                    </descriptors>
+                    <tarLongFileMode>gnu</tarLongFileMode>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-jar-plugin</artifactId>
+                <configuration>
+                    <archive>
+                        <manifest>
+                            <addDefaultImplementationEntries>true</addDefaultImplementationEntries>
+                            <addDefaultSpecificationEntries>true</addDefaultSpecificationEntries>
+                        </manifest>
+                    </archive>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-release-plugin</artifactId>
+                <version>2.0</version>
+                <configuration>
+                    <mavenExecutorId>forked-path</mavenExecutorId>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+    <distributionManagement>
+        <site>
+            <id>website</id>
+            <name>Apache Website</name>
+            <url>${commons.deployment.protocol}://people.apache.org/www/commons.apache.org/exec/</url>
+        </site>
+    </distributionManagement>
+    <reporting>
+        <plugins>
+            <!-- generate the changes report from changes.xml -->
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-changes-plugin</artifactId>
+                <version>2.0</version>
+                <configuration>
+                    <issueLinkTemplate>%URL%/%ISSUE%</issueLinkTemplate>
+                </configuration>
+                <reportSets>
+                    <reportSet>
+                        <reports>
+                            <report>changes-report</report>
+                        </reports>
+                    </reportSet>
+                </reportSets>
+            </plugin>
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>findbugs-maven-plugin</artifactId>
+                <version>1.2</version>
+                <configuration>
+                    <threshold>Normal</threshold>
+                    <effort>Default</effort>
+                    <excludeFilterFile>${basedir}/findbugs-exclude-filter.xml</excludeFilterFile>
+                </configuration>
+            </plugin>
+        </plugins>
+    </reporting>
+    <profiles>
+        <!--
+          enable this profile if you like to create cobertura report not being part of the official website
+        -->
+        <profile>
+            <id>coverage</id>
+            <reporting>
+                <plugins>
+                    <!-- Avoid broken Cobertura 2.1 plugin -->
+                    <plugin>
+                        <groupId>org.codehaus.mojo</groupId>
+                        <artifactId>cobertura-maven-plugin</artifactId>
+                        <version>2.0</version>
+                    </plugin>
+                </plugins>
+            </reporting>
+        </profile>
+        <profile>
+            <id>rc</id>
+            <distributionManagement>
+                <!-- Cannot define in parent ATM, see COMMONSSITE-26 -->
+                <site>
+                    <id>apache.website</id>
+                    <name>Apache Commons Release Candidate Staging Site</name>
+                    <url>
+                        ${commons.deployment.protocol}://people.apache.org/www/people.apache.org/builds/commons/${commons.componentid}/${commons.release.version}/${commons.rc.version}/site
+                    </url>
+                </site>
+            </distributionManagement>
+        </profile>
+        <profile>
+            <!-- Create archives containing stand-alone test cases -->
+            <!-- e.g. mvn assembly:assembly -Ptest-distribution [-DskipTests] -->
+            <id>test-distribution</id>
+            <build>
+                <plugins>
+                    <plugin>
+                        <artifactId>maven-assembly-plugin</artifactId>
+                        <configuration>
+                            <descriptors>
+                                <descriptor>src/assembly/test-distribution.xml</descriptor>
+                            </descriptors>
+                            <tarLongFileMode>gnu</tarLongFileMode>
+                        </configuration>
+                        <executions>
+                            <execution>
+                                <phase>package</phase>
+                                <goals>
+                                    <goal>assembly</goal>
+                                </goals>
+                            </execution>
+                        </executions>
+                    </plugin>
+                    <plugin>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-jar-plugin</artifactId>
+                        <configuration>
+                            <archive>
+                                <manifest>
+                                    <addDefaultImplementationEntries>true</addDefaultImplementationEntries>
+                                    <addDefaultSpecificationEntries>true</addDefaultSpecificationEntries>
+                                </manifest>
+                            </archive>
+                        </configuration>
+                        <executions>
+                            <execution>
+                                <goals>
+                                    <goal>jar</goal>
+                                    <goal>test-jar</goal>
+                                </goals>
+                            </execution>
+                        </executions>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+    </profiles>
+    <properties>
+        <!-- Compiler source and target JVM (see parent pom). Must agree with versions in build.xml -->
+        <maven.compile.source>1.3</maven.compile.source>
+        <maven.compile.target>1.3</maven.compile.target>
+        <test>*Test</test>
+        <commons.componentid>exec</commons.componentid>
+        <commons.jira.id>EXEC</commons.jira.id>
+        <commons.jira.pid>12310814</commons.jira.pid>
+        <commons.release.version>1.1</commons.release.version>
+        <!-- The RC version used in the staging repository URL. -->
+        <commons.rc.version>RC1</commons.rc.version>
+        <!-- Tell M2 not to use platform specific encodings for copying resources -->
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+    </properties>
+</project>
diff --git a/src/assembly/bin.xml b/src/assembly/bin.xml
new file mode 100644
index 0000000..2a08ac1
--- /dev/null
+++ b/src/assembly/bin.xml
@@ -0,0 +1,43 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<assembly>
+    <id>bin</id>
+    <formats>
+        <format>tar.gz</format>
+        <format>zip</format>
+    </formats>
+    <includeSiteDirectory>false</includeSiteDirectory>
+    <fileSets>
+        <fileSet>
+            <includes>
+                <include>LICENSE.txt</include>
+                <include>NOTICE.txt</include>
+            </includes>
+        </fileSet>
+        <fileSet>
+            <directory>target</directory>
+            <outputDirectory></outputDirectory>
+            <includes>
+                <include>*.jar</include>
+            </includes>
+        </fileSet>
+        <fileSet>
+            <directory>target/site/apidocs</directory>
+            <outputDirectory>apidocs</outputDirectory>
+        </fileSet>
+    </fileSets>
+</assembly>
diff --git a/src/assembly/src.xml b/src/assembly/src.xml
new file mode 100644
index 0000000..c93543b
--- /dev/null
+++ b/src/assembly/src.xml
@@ -0,0 +1,39 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<assembly>
+    <id>src</id>
+    <formats>
+        <format>tar.gz</format>
+        <format>zip</format>
+    </formats>
+    <baseDirectory>${artifactId}-${commons.release.version}-src</baseDirectory>
+    <fileSets>
+        <fileSet>
+            <includes>
+                <include>build.xml</include>
+                <include>LICENSE.txt</include>
+                <include>NOTICE.txt</include>
+                <include>pom.xml</include>
+                <include>STATUS</include>
+                <include>findbugs-exclude-filter.xml</include>                
+            </includes>
+        </fileSet>
+        <fileSet>
+            <directory>src</directory>
+        </fileSet>
+    </fileSets>
+</assembly>
diff --git a/src/assembly/test-distribution.xml b/src/assembly/test-distribution.xml
new file mode 100644
index 0000000..ce79336
--- /dev/null
+++ b/src/assembly/test-distribution.xml
@@ -0,0 +1,68 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<assembly>
+    <id>test</id>
+    <formats>
+        <format>tar.gz</format>
+        <format>zip</format>
+    </formats>
+    <baseDirectory></baseDirectory>
+    <fileSets>
+        <fileSet>
+            <includes>
+                <include>LICENSE.txt</include>
+                <include>NOTICE.txt</include>
+            </includes>
+        </fileSet>
+        <fileSet>
+            <directory>src/test/bin</directory>
+            <outputDirectory></outputDirectory>
+            <includes>
+                <include>testme.bat</include>
+                <include>testme.dcl</include>
+            </includes>
+            <filtered>true</filtered>
+        </fileSet>
+        <fileSet>
+            <directory>src/test/scripts</directory>
+        </fileSet>
+        <fileSet>
+            <directory>target</directory>
+            <outputDirectory>lib</outputDirectory>
+            <includes>
+                <include>${artifact.artifactId}-${artifact.version}.jar</include>
+                <include>${artifact.artifactId}-${artifact.version}-tests.jar</include>
+            </includes>
+        </fileSet>
+    </fileSets>
+    <files>
+        <file>
+            <source>src/test/bin/testme.sh</source>
+            <fileMode>775</fileMode>
+            <filtered>true</filtered>
+        </file>
+    </files>
+    <dependencySets>
+        <dependencySet>
+            <scope>test</scope>
+            <includes>
+                <include>junit:junit</include>
+            </includes>
+            <outputDirectory>lib</outputDirectory>
+        </dependencySet>
+    </dependencySets>
+</assembly>
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
new file mode 100644
index 0000000..9ad7215
--- /dev/null
+++ b/src/changes/changes.xml
@@ -0,0 +1,206 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements.  See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership.  The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License.  You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied.  See the License for the
+ specific language governing permissions and limitations
+ under the License.
+-->
+<document>
+    <properties>
+        <title>commons-exec</title>
+        <author email="sgoeschl at apache.org">Siegfried Goeschl</author>
+    </properties>
+    <body>
+        <release version="1.1" date="2010-10-08" description="Maintenance Release">
+            <action dev="sebb" type="fix" date="2010-10-05" >
+                OpenVMS now uses symbols instead of logicals for environment variables.
+            </action>
+            <action dev="sgoeschl" type="add" date="2010-09-21" >
+                Adding 'Argument' class and quote the arguments after expansion.                
+            </action>
+            <action dev="sgoeschl" type="add" date="2010-09-02" >
+                Reverting changes of [EXEC-41] because the patch does not fix the problem.
+                Also added test case for the broken patch.
+            </action>
+            <action dev="sgoeschl" type="add" date="2010-08-17" >
+                Added TutorialTest as a playground for new user and removed
+                similar code from DefaultExecutorTest.
+            </action>
+            <action dev="sgoeschl" type="fix" date="2010-08-16" >
+                String substitution handles now java.io.File instances in order
+                to create a cross-platform file name.            
+            </action>
+            <action dev="sgoeschl" type="fix" date="2010-08-16" >
+                The 'forever.bat' accidentally overwrite the 'forever.txt' instead of
+                appending.
+            </action>
+            <action dev="sgoeschl" type="update" date="2010-08-16" >
+                DefaultExecutor() now sets the working directory with the current working
+                directory.
+            </action>
+            <action dev="sgoeschl" type="update" date="2010-08-15">
+                Added 'DefaultExecutorTest#testStdInHandling' to show how
+                commons-exec can feed the 'stdin' of a child process.
+            </action>
+            <action dev="sgoeschl" type="update" date="2010-08-15" issue="EXEC-42" due-to="Konrad Windzus">
+                Improved the documentation.
+            </action>
+            <action dev="sgoeschl" type="update" date="2010-08-15" issue="EXEC-41" due-to="Ernest Mishkin">
+                Added a PumpStreamHandler.setAlwaysWaitForStreamThreads() which allows to skip
+                joining with the pumper threads. Having said that - using that flag is for the
+                desperate because it could leave up to three worker threads behind but there
+                might be situations where this is the only escape.
+            </action>
+            <action dev="sgoeschl" type="fix" date="2010-08-15" issue="EXEC-46" due-to="Zimmermann Nir">
+                Process.waitFor should clear interrupt status when throwing InterruptedException
+            </action>
+            <action dev="sgoeschl" type="update" date="2010-06-01">
+                Added 'DefaultExecuteResultHandler'
+            </action>
+            <action dev="sgoeschl" type="update" date="2010-06-01" issue="EXEC-42" due-to="Pablo Hoertner">
+                Added a new section to the tutorial to show working with asynchronous
+                processes. Thanks to Pablo for providing this documentation update.
+            </action>
+            <action dev="sgoeschl" type="fix" date="2010-05-31" issue="EXEC-44">
+                Because the ExecuteWatchdog is the only way to destroy asynchronous processes,
+                it should be possible to set it to an infinite timeout, for processes which
+                should not timeout, but manually destroyed under some circumstances.
+            </action>
+        </release>
+        <release version="1.0.1" date="2009-09-28" description="Maintenance Release">
+            <action dev="henrib" type="fix" date="2009-09-25" issue="EXEC-33">
+                On a Mac, the unit tests never finish. Culprit is InputStreamPumper which
+                sets its stop member in the run method; however, run might really be executed
+                after the stopProcessing method is called if the process
+                thread completes before the InputStreamPumper starts.
+            </action>
+            <action dev="sgoeschl" type="fix" due-to="Peter Henderson" issue="EXEC-40">
+                Fixes NPE in DefaultExecutor.setExitValues().
+            </action>
+            <action dev="sgoeschl" type="fix" due-to="Milos Kleint" issue="EXEC-33">
+                Copies all data from an System.input stream to an output stream of
+                the executed process.
+            </action>
+        </release>
+        <release version="1.0" date="2009-03-15" description="First Public Release">
+            <action dev="sgoeschl" type="fix" due-to="Sebastien Bazley" issue="EXEC-37">
+                Removed useless synchronized statement in
+                OpenVmsProcessingEnvironment.createProcEnvironment
+            </action>
+            <action dev="sgoeschl" type="fix" issue="EXEC-33">
+                Using System.in for child process will actually hang your application -
+                see JIRA for more details. Since there is no easy fix an
+                IllegalRuntimeException is thrown when System.in is passed.
+            </action>
+            <action dev="sgoeschl" type="fix" due-to="Luc Maisonobe" issue="EXEC-35">
+                Fixing a few findbugs issues.
+            </action>
+            <action dev="sgoeschl" type="fix" due-to="Marco Ferrante" issue="EXEC-32">
+                Handle null streams consistently.
+            </action>
+            <action dev="sgoeschl" type="fix">
+                After a long discussion we decided to stick to following groupId
+                "org.apache.commons" instead of "commons-exec".
+            </action>
+            <action dev="sgoeschl" type="fix" due-to="Kevin Jackson">
+                The Ant build now works even when junit is not on the classpath
+            </action>
+            <action dev="sgoeschl" type="fix">
+                Fixed broken "groupId" from "org.apache.commons" to "commons-exec"
+            </action>
+            <action dev="sgoeschl" type="fix" issue="EXEC-27" due-to="Benjamin Bentmann">
+                Renamed EnvironmentUtil to EnvironmentUtils to align with other classes
+                in this project and commons in general. Please note that this change
+                could break existing clients (but would be rather unlikely).
+            </action>
+            <action dev="sgoeschl" type="fix" issue="EXEC-30" due-to="Benjamin Bentmann">
+                Make environment variables respect casing rules of platforms. Under Windows
+                "PATH", "Path" and "path" would access the same environment variable whereas
+                the real name is "Path".
+            </action>
+            <action dev="sgoeschl" type="fix" issue="EXEC-31" due-to="Benjamin Bentmann">
+                Invoking DefaultExecutor.execute(CommandLine command, Map environment) using
+                a 'null' Map results in inheriting all environment variables of the current
+                process while passing an empty map implies starting the new process with no
+                environment variables. In short 'null' is not the same as an empty map.
+            </action>
+            <action dev="sgoeschl" type="add" issue="EXEC-26" due-to="Benjamin Bentmann">
+                Added one additional test : DefaultExecutorTest.testExecuteWithFancyArg
+            </action>
+            <action dev="sgoeschl" issue="EXEC-25" type="fix">
+                Using variable substitution within CommandLine broke the regression tests
+                under Windows. Found also another bug when calling CommandLine.getExecutable()
+                the result was not substituted at all. As a general rule we do variable
+                substitution and file separator fixing on the command line executable and
+                variable substitution but NO file separator fixing for the command line
+                arguments.
+            </action>
+            <action dev="sgoeschl" type="add">
+                Added convinience method to add two parameters to the CommandLine
+                using one method invocation.
+            </action>
+            <action dev="sgoeschl" type="fix">
+                Implemented better regression test for OpenVMS affecting also
+                the Executor and CommandLauncher interface.
+            </action>
+            <action dev="sebb" type="add">
+                Added test scripts for OpenVMS - he seems to be the last human
+                having access to an OpenVMS box ... :-)
+            </action>
+            <action dev="sgoeschl" type="add" due-to="Simone Gianni,Bindul Bhowmik,Niall Pemberton,Sebastian Bazley">
+                With the help of the Apache Commons community I added the first results
+                of cross-OS testing.
+            </action>
+            <action dev="sgoeschl" type="add">
+                The regression tests now also works on Windows - so it should
+                work now on Linux, Windows and Mac OS X
+            </action>
+            <action dev="sgoeschl" type="add">
+                Added DebugUtils to improve cross-platform testing.
+            </action>
+            <action dev="sgoeschl" type="remove">
+                Removed commons-logging integration
+            </action>
+            <action dev="sgoeschl" type="add" issue="SANDBOX-62" due-to="Jeremy Lacoste">
+                Made DefaultExecutor.launch() protected to enable mocking.
+            </action>
+            <action dev="sgoeschl" type="add" issue="SANDBOX-107" due-to="Niklas Gustavsson">
+                Made ProcessDestroyer optional and pluggable when using Executor.
+            </action>
+            <action dev="sgoeschl" type="add">
+                CommandLine can now expand the given command line by a user-suppied
+                map. This allows to execute something like "${JAVA_HOME}/bin/java -jar ${myapp}"
+            </action>
+            <action dev="sgoeschl" type="add" issue="SANDBOX-192" due-to="Reinhold Fuereder">
+                Added methods to provide pre-quoted arguments.
+            </action>
+            <action dev="sgoeschl" type="add" issue="SANDBOX-193" due-to="Reinhold Fuereder">
+                Exposing a ExecuteWatchdog.destroy() to kill an asynchrounous process
+                manually. This formalizes a workaround described in the JIRA
+            </action>
+            <action dev="sgoeschl" type="add" issue="SANDBOX-203">
+                Extending exit value handling to support applications returning an error
+                code.
+            </action>
+            <action dev="sgoeschl" type="fix" issue="SANDBOX-204">
+                Cleaned up the source code to get rid of javadoc errors and
+                unused imports.
+            </action>
+            <action dev="sgoeschl" type="add" issue="SANDBOX-204">
+                Added a few regression tests for the watchdog since they were missing.
+            </action>
+        </release>
+    </body>
+</document>
diff --git a/src/main/java/org/apache/commons/exec/CommandLine.java b/src/main/java/org/apache/commons/exec/CommandLine.java
new file mode 100644
index 0000000..ff9e6d0
--- /dev/null
+++ b/src/main/java/org/apache/commons/exec/CommandLine.java
@@ -0,0 +1,441 @@
+/* 
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+package org.apache.commons.exec;
+
+import org.apache.commons.exec.util.StringUtils;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.StringTokenizer;
+import java.util.Vector;
+import java.util.Map;
+
+/**
+ * CommandLine objects help handling command lines specifying processes to
+ * execute. The class can be used to a command line by an application.
+ */
+public class CommandLine {
+
+    /**
+     * The arguments of the command.
+     */
+    private final Vector arguments = new Vector();
+
+    /**
+     * The program to execute.
+     */
+    private final String executable;
+
+    /**
+     * A map of name value pairs used to expand command line arguments
+     */
+    private Map substitutionMap;
+
+    /**
+     * Was a file being used to set the executable?
+     */
+    private final boolean isFile;
+
+    /**
+     * Create a command line from a string.
+     * 
+     * @param line the first element becomes the executable, the rest the arguments
+     * @return the parsed command line
+     * @throws IllegalArgumentException If line is null or all whitespace
+     */
+    public static CommandLine parse(final String line) {
+        return parse(line, null);
+    }
+
+    /**
+     * Create a command line from a string.
+     *
+     * @param line the first element becomes the executable, the rest the arguments
+     * @param substitutionMap the name/value pairs used for substitution
+     * @return the parsed command line
+     * @throws IllegalArgumentException If line is null or all whitespace
+     */
+    public static CommandLine parse(final String line, Map substitutionMap) {
+                
+        if (line == null) {
+            throw new IllegalArgumentException("Command line can not be null");
+        } else if (line.trim().length() == 0) {
+            throw new IllegalArgumentException("Command line can not be empty");
+        } else {
+            String[] tmp = translateCommandline(line);
+
+            CommandLine cl = new CommandLine(tmp[0]);
+            cl.setSubstitutionMap(substitutionMap);
+            for (int i = 1; i < tmp.length; i++) {
+                cl.addArgument(tmp[i]);
+            }
+
+            return cl;
+        }
+    }
+
+    /**
+     * Create a command line without any arguments.
+     *
+     * @param executable the executable
+     */
+    public CommandLine(String executable) {
+        this.isFile=false;
+        this.executable=getExecutable(executable);
+    }
+
+    /**
+     * Create a command line without any arguments.
+     *
+     * @param  executable the executable file
+     */
+    public CommandLine(File executable) {
+        this.isFile=true;
+        this.executable=getExecutable(executable.getAbsolutePath());
+    }
+
+    /**
+     * Copy constructor.
+     *
+     * @param other the instance to copy
+     */
+    public CommandLine(CommandLine other)
+    {
+        this.executable = other.getExecutable();
+        this.isFile = other.isFile();
+        this.arguments.addAll(other.arguments);
+
+        if(other.getSubstitutionMap() != null)
+        {
+            this.substitutionMap = new HashMap();
+            Iterator iterator = other.substitutionMap.keySet().iterator();
+            while(iterator.hasNext())
+            {
+                Object key = iterator.next();
+                this.substitutionMap.put(key, other.getSubstitutionMap().get(key));
+            }
+        }
+    }
+
+    /**
+     * Returns the executable.
+     * 
+     * @return The executable
+     */
+    public String getExecutable() {
+        // Expand the executable and replace '/' and '\\' with the platform
+        // specific file separator char. This is safe here since we know
+        // that this is a platform specific command.
+        return StringUtils.fixFileSeparatorChar(expandArgument(executable));
+    }
+
+    /**
+     * Was a file being used to set the executable?
+     *
+     * @return true if a file was used for setting the executable 
+     */
+    public boolean isFile(){
+        return isFile;
+    }
+
+    /**
+     * Add multiple arguments. Handles parsing of quotes and whitespace.
+     * 
+     * @param arguments An array of arguments
+     * @return The command line itself
+     */
+    public CommandLine addArguments(final String[] arguments) {
+        return this.addArguments(arguments, true);
+    }
+
+    /**
+     * Add multiple arguments.
+     *
+     * @param arguments An array of arguments
+     * @param handleQuoting Add the argument with/without handling quoting
+     * @return The command line itself
+     */
+    public CommandLine addArguments(final String[] arguments, boolean handleQuoting) {
+        if (arguments != null) {
+            for (int i = 0; i < arguments.length; i++) {
+                addArgument(arguments[i], handleQuoting);
+            }
+        }
+
+        return this;
+    }
+
+    /**
+     * Add multiple arguments. Handles parsing of quotes and whitespace.
+     * Please note that the parsing can have undesired side-effects therefore
+     * it is recommended to build the command line incrementally.
+     * 
+     * @param arguments An string containing multiple arguments. 
+     * @return The command line itself
+     */
+    public CommandLine addArguments(final String arguments) {
+        return this.addArguments(arguments, true);
+    }
+
+    /**
+     * Add multiple arguments. Handles parsing of quotes and whitespace.
+     * Please note that the parsing can have undesired side-effects therefore
+     * it is recommended to build the command line incrementally.
+     *
+     * @param arguments An string containing multiple arguments.
+     * @param handleQuoting Add the argument with/without handling quoting
+     * @return The command line itself
+     */
+    public CommandLine addArguments(final String arguments, boolean handleQuoting) {
+        if (arguments != null) {
+            String[] argumentsArray = translateCommandline(arguments);
+            addArguments(argumentsArray, handleQuoting);
+        }
+
+        return this;
+    }
+
+    /**
+     * Add a single argument. Handles quoting.
+     *
+     * @param argument The argument to add
+     * @return The command line itself
+     * @throws IllegalArgumentException If argument contains both single and double quotes
+     */
+    public CommandLine addArgument(final String argument) {
+        return this.addArgument(argument, true);
+    }
+
+   /**
+    * Add a single argument.
+    *
+    * @param argument The argument to add
+    * @param handleQuoting Add the argument with/without handling quoting
+    * @return The command line itself
+    */
+   public CommandLine addArgument(final String argument, boolean handleQuoting) {
+
+       if (argument == null)
+       {
+           return this;
+       }
+
+       // check if we can really quote the argument - if not throw an
+       // IllegalArgumentException
+       if (handleQuoting)
+       {
+           StringUtils.quoteArgument(argument);
+       }
+
+       arguments.add(new Argument(argument, handleQuoting));
+       return this;
+   }
+
+    /**
+     * Returns the expanded and quoted command line arguments.
+     *  
+     * @return The quoted arguments
+     */
+    public String[] getArguments() {
+
+        Argument currArgument;
+        String expandedArgument;
+        String[] result = new String[arguments.size()];
+
+        for(int i=0; i<result.length; i++) {
+            currArgument = (Argument) arguments.get(i);
+            expandedArgument = expandArgument(currArgument.getValue());
+            result[i] = (currArgument.isHandleQuoting() ? StringUtils.quoteArgument(expandedArgument) : expandedArgument);
+        }
+
+        return result;
+    }
+
+    /**
+     * @return the substitution map
+     */
+    public Map getSubstitutionMap() {
+        return substitutionMap;
+    }
+
+    /**
+     * Set the substitutionMap to expand variables in the
+     * command line.
+     * 
+     * @param substitutionMap the map
+     */
+    public void setSubstitutionMap(Map substitutionMap) {
+        this.substitutionMap = substitutionMap;
+    }
+
+    /**
+     * Returns the command line as an array of strings.
+     *
+     * @return The command line as an string array
+     */
+    public String[] toStrings() {
+        final String[] result = new String[arguments.size() + 1];
+        result[0] = this.getExecutable();
+        System.arraycopy(getArguments(), 0, result, 1, result.length-1);
+        return result;
+    }
+
+    /**
+     * Stringify operator returns the command line as a string.
+     * Parameters are correctly quoted when containing a space or
+     * left untouched if the are already quoted. 
+     *
+     * @return the command line as single string
+     */
+    public String toString() {
+        return StringUtils.toString(toStrings(), " ");
+    }
+
+    // --- Implementation ---------------------------------------------------
+
+    /**
+     * Expand variables in a command line argument.
+     *
+     * @param argument the argument
+     * @return the expanded string
+     */
+    private String expandArgument(final String argument) {
+        StringBuffer stringBuffer = StringUtils.stringSubstitution(argument, this.getSubstitutionMap(), true);
+        return stringBuffer.toString();
+    }
+
+    /**
+     * Crack a command line.
+     *
+     * @param toProcess
+     *            the command line to process
+     * @return the command line broken into strings. An empty or null toProcess
+     *         parameter results in a zero sized array
+     */
+    private static String[] translateCommandline(final String toProcess) {
+        if (toProcess == null || toProcess.length() == 0) {
+            // no command? no string
+            return new String[0];
+        }
+
+        // parse with a simple finite state machine
+
+        final int normal = 0;
+        final int inQuote = 1;
+        final int inDoubleQuote = 2;
+        int state = normal;
+        StringTokenizer tok = new StringTokenizer(toProcess, "\"\' ", true);
+        Vector v = new Vector();
+        StringBuffer current = new StringBuffer();
+        boolean lastTokenHasBeenQuoted = false;
+
+        while (tok.hasMoreTokens()) {
+            String nextTok = tok.nextToken();
+            switch (state) {
+            case inQuote:
+                if ("\'".equals(nextTok)) {
+                    lastTokenHasBeenQuoted = true;
+                    state = normal;
+                } else {
+                    current.append(nextTok);
+                }
+                break;
+            case inDoubleQuote:
+                if ("\"".equals(nextTok)) {
+                    lastTokenHasBeenQuoted = true;
+                    state = normal;
+                } else {
+                    current.append(nextTok);
+                }
+                break;
+            default:
+                if ("\'".equals(nextTok)) {
+                    state = inQuote;
+                } else if ("\"".equals(nextTok)) {
+                    state = inDoubleQuote;
+                } else if (" ".equals(nextTok)) {
+                    if (lastTokenHasBeenQuoted || current.length() != 0) {
+                        v.addElement(current.toString());
+                        current = new StringBuffer();
+                    }
+                } else {
+                    current.append(nextTok);
+                }
+                lastTokenHasBeenQuoted = false;
+                break;
+            }
+        }
+
+        if (lastTokenHasBeenQuoted || current.length() != 0) {
+            v.addElement(current.toString());
+        }
+
+        if (state == inQuote || state == inDoubleQuote) {
+            throw new IllegalArgumentException("Unbalanced quotes in "
+                    + toProcess);
+        }
+
+        String[] args = new String[v.size()];
+        v.copyInto(args);
+        return args;
+    }
+
+    /**
+     * Get the executable - the argument is trimmed and '/' and '\\' are
+     * replaced with the platform specific file separator char
+     *
+     * @param executable the executable
+     * @return the platform-specific executable string
+     */
+    private String getExecutable(final String executable) {
+        if (executable == null) {
+            throw new IllegalArgumentException("Executable can not be null");
+        } else if(executable.trim().length() == 0) {
+            throw new IllegalArgumentException("Executable can not be empty");
+        } else {
+            return StringUtils.fixFileSeparatorChar(executable);
+        }
+    }
+
+    /**
+     * Encapsulates a command line argument.
+     */
+    class Argument {
+
+        private final String value;
+        private final boolean handleQuoting;
+
+        private Argument(String value, boolean handleQuoting)
+        {
+            this.value = value.trim();
+            this.handleQuoting = handleQuoting;
+        }
+
+        private String getValue()
+        {
+            return value;
+        }
+
+        private boolean isHandleQuoting()
+        {
+            return handleQuoting;
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/exec/DefaultExecuteResultHandler.java b/src/main/java/org/apache/commons/exec/DefaultExecuteResultHandler.java
new file mode 100644
index 0000000..e85d8a2
--- /dev/null
+++ b/src/main/java/org/apache/commons/exec/DefaultExecuteResultHandler.java
@@ -0,0 +1,144 @@
+/* 
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+package org.apache.commons.exec;
+
+/**
+ * A default implementation of 'ExecuteResultHandler' used for asynchronous
+ * process handling.
+ */
+public class DefaultExecuteResultHandler implements ExecuteResultHandler {
+
+    /** the interval polling the result */
+    private static final int SLEEP_TIME_MS = 50;
+
+    /** Keep track if the process is still running */
+    private volatile boolean hasResult;
+
+    /** The exit value of the finished process */
+    private volatile int exitValue;
+
+    /** Any offending exception */
+    private volatile ExecuteException exception;
+
+    /**
+     * Constructor.
+     */
+    public DefaultExecuteResultHandler() {
+        this.hasResult = false;
+        this.exitValue = Executor.INVALID_EXITVALUE;
+    }
+
+    /**
+     * @see org.apache.commons.exec.ExecuteResultHandler#onProcessComplete(int)
+     */
+    public void onProcessComplete(int exitValue) {
+        this.exitValue = exitValue;
+        this.exception = null;
+        this.hasResult = true;
+    }
+
+    /**
+     * @see org.apache.commons.exec.ExecuteResultHandler#onProcessFailed(org.apache.commons.exec.ExecuteException)
+     */
+    public void onProcessFailed(ExecuteException e) {
+        this.exitValue = e.getExitValue();            
+        this.exception = e;
+        this.hasResult = true;
+    }
+
+    /**
+     * Get the <code>exception<code> causing the process execution to fail.
+     *
+     * @return Returns the exception.
+     * @throws IllegalStateException if the process has not exited yet
+     */
+    public ExecuteException getException() {
+
+        if(!hasResult) {
+            throw new IllegalStateException("The process has not exited yet therefore no result is available ...");
+        }
+
+        return exception;
+    }
+
+    /**
+     * Get the <code>exitValue<code> of the process.
+     *
+     * @return Returns the exitValue.
+     * @throws IllegalStateException if the process has not exited yet
+     */
+    public int getExitValue() {
+
+        if(!hasResult) {
+            throw new IllegalStateException("The process has not exited yet therefore no result is available ...");
+        }
+
+        return exitValue;
+    }
+
+    /**
+     * Has the process exited and a result is available, i.e. exitCode or exception?
+     *
+     * @return true if a result of the execution is available
+     */
+    public boolean hasResult() {
+        return hasResult;
+    }
+
+    /**
+     * Causes the current thread to wait, if necessary, until the
+     * process has terminated. This method returns immediately if
+     * the process has already terminated. If the process has
+     * not yet terminated, the calling thread will be blocked until the
+     * process exits.
+     *
+     * @exception  InterruptedException if the current thread is
+     *             {@linkplain Thread#interrupt() interrupted} by another
+     *             thread while it is waiting, then the wait is ended and
+     *             an {@link InterruptedException} is thrown.
+     */
+    public void waitFor() throws InterruptedException {
+
+        while (!hasResult()) {
+            Thread.sleep(SLEEP_TIME_MS);
+        }
+    }
+
+    /**
+     * Causes the current thread to wait, if necessary, until the
+     * process has terminated. This method returns immediately if
+     * the process has already terminated. If the process has
+     * not yet terminated, the calling thread will be blocked until the
+     * process exits.
+     *
+     * @param timeout the maximum time to wait in milliseconds
+     * @exception  InterruptedException if the current thread is
+     *             {@linkplain Thread#interrupt() interrupted} by another
+     *             thread while it is waiting, then the wait is ended and
+     *             an {@link InterruptedException} is thrown.
+     */
+    public void waitFor(long timeout) throws InterruptedException {
+
+        long until = System.currentTimeMillis() + timeout;
+
+        while (!hasResult() && (System.currentTimeMillis() < until)) {
+            Thread.sleep(SLEEP_TIME_MS);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/commons/exec/DefaultExecutor.java b/src/main/java/org/apache/commons/exec/DefaultExecutor.java
new file mode 100644
index 0000000..d397060
--- /dev/null
+++ b/src/main/java/org/apache/commons/exec/DefaultExecutor.java
@@ -0,0 +1,388 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.exec;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Map;
+
+import org.apache.commons.exec.launcher.CommandLauncher;
+import org.apache.commons.exec.launcher.CommandLauncherFactory;
+
+/**
+ * The default class to start a subprocess. The implementation
+ * allows to
+ * <ul>
+ *  <li>set a current working directory for the subprocess</li>
+ *  <li>provide a set of environment variables passed to the subprocess</li>
+ *  <li>capture the subprocess output of stdout and stderr using an ExecuteStreamHandler</li>
+ *  <li>kill long-running processes using an ExecuteWatchdog</li>
+ *  <li>define a set of expected exit values</li>
+ *  <li>terminate any started processes when the main process is terminating using a ProcessDestroyer</li>
+ * </ul>
+ *
+ * The following example shows the basic usage:
+ *
+ * <pre>
+ * Executor exec = new DefaultExecutor();
+ * CommandLine cl = new CommandLine("ls -l");
+ * int exitvalue = exec.execute(cl);
+ * </pre>
+ */
+public class DefaultExecutor implements Executor {
+
+    /** taking care of output and error stream */
+    private ExecuteStreamHandler streamHandler;
+
+    /** the working directory of the process */
+    private File workingDirectory;
+
+    /** monitoring of long running processes */
+    private ExecuteWatchdog watchdog;
+
+    /** the exit values considered to be successful */
+    private int[] exitValues;
+
+    /** launches the command in a new process */
+    private final CommandLauncher launcher;
+
+    /** optional cleanup of started processes */ 
+    private ProcessDestroyer processDestroyer;
+
+    /** worker thread for asynchronous execution */
+    private Thread executorThread;
+
+    /**
+     * Default constructor creating a default <code>PumpStreamHandler</code>
+     * and sets the working directory of the subprocess to the current
+     * working directory.
+     *
+     * The <code>PumpStreamHandler</code> pumps the output of the subprocess
+     * into our <code>System.out</code> and <code>System.err</code> to avoid
+     * into our <code>System.out</code> and <code>System.err</code> to avoid
+     * a blocked or deadlocked subprocess (see{@link java.lang.Process Process}).
+     */
+    public DefaultExecutor() {
+        this.streamHandler = new PumpStreamHandler();
+        this.launcher = CommandLauncherFactory.createVMLauncher();
+        this.exitValues = new int[0];
+        this.workingDirectory = new File(".");
+    }
+
+    /**
+     * @see org.apache.commons.exec.Executor#getStreamHandler()
+     */
+    public ExecuteStreamHandler getStreamHandler() {
+        return streamHandler;
+    }
+
+    /**
+     * @see org.apache.commons.exec.Executor#setStreamHandler(org.apache.commons.exec.ExecuteStreamHandler)
+     */
+    public void setStreamHandler(ExecuteStreamHandler streamHandler) {
+        this.streamHandler = streamHandler;
+    }
+
+    /**
+     * @see org.apache.commons.exec.Executor#getWatchdog()
+     */
+    public ExecuteWatchdog getWatchdog() {
+        return watchdog;
+    }
+
+    /**
+     * @see org.apache.commons.exec.Executor#setWatchdog(org.apache.commons.exec.ExecuteWatchdog)
+     */
+    public void setWatchdog(ExecuteWatchdog watchDog) {
+        this.watchdog = watchDog;
+    }
+
+    /**
+     * @see org.apache.commons.exec.Executor#getProcessDestroyer()
+     */
+    public ProcessDestroyer getProcessDestroyer() {
+      return this.processDestroyer;
+    }
+
+    /**
+     * @see org.apache.commons.exec.Executor#setProcessDestroyer(ProcessDestroyer)
+     */
+    public void setProcessDestroyer(ProcessDestroyer processDestroyer) {
+      this.processDestroyer = processDestroyer;
+    }
+
+    /**
+     * @see org.apache.commons.exec.Executor#getWorkingDirectory()
+     */
+    public File getWorkingDirectory() {
+        return workingDirectory;
+    }
+
+    /**
+     * @see org.apache.commons.exec.Executor#setWorkingDirectory(java.io.File)
+     */
+    public void setWorkingDirectory(File dir) {
+        this.workingDirectory = dir;
+    }
+
+    /**
+     * @see org.apache.commons.exec.Executor#execute(CommandLine)
+     */
+    public int execute(final CommandLine command) throws ExecuteException,
+            IOException {
+        return execute(command, (Map) null);
+    }
+
+    /**
+     * @see org.apache.commons.exec.Executor#execute(CommandLine, java.util.Map)
+     */
+    public int execute(final CommandLine command, Map environment)
+            throws ExecuteException, IOException {
+
+        if (workingDirectory != null && !workingDirectory.exists()) {
+            throw new IOException(workingDirectory + " doesn't exist.");
+        }
+        
+        return executeInternal(command, environment, workingDirectory, streamHandler);
+
+    }
+
+    /**
+     * @see org.apache.commons.exec.Executor#execute(CommandLine,
+     *      org.apache.commons.exec.ExecuteResultHandler)
+     */
+    public void execute(final CommandLine command, ExecuteResultHandler handler)
+            throws ExecuteException, IOException {
+        execute(command, null, handler);
+    }
+
+    /**
+     * @see org.apache.commons.exec.Executor#execute(CommandLine,
+     *      java.util.Map, org.apache.commons.exec.ExecuteResultHandler)
+     */
+    public void execute(final CommandLine command, final Map environment,
+            final ExecuteResultHandler handler) throws ExecuteException, IOException {
+
+        if (workingDirectory != null && !workingDirectory.exists()) {
+            throw new IOException(workingDirectory + " doesn't exist.");
+        }
+
+        executorThread = new Thread() {
+            public void run() {
+                int exitValue = Executor.INVALID_EXITVALUE;
+                try {                    
+                    exitValue = executeInternal(command, environment, workingDirectory, streamHandler);
+                    handler.onProcessComplete(exitValue);
+                } catch (ExecuteException e) {
+                    handler.onProcessFailed(e);
+                } catch(Exception e) {
+                    handler.onProcessFailed(new ExecuteException("Execution failed", exitValue, e));
+                }
+            }
+        };
+
+        getExecutorThread().start();
+    }
+
+    /** @see org.apache.commons.exec.Executor#setExitValue(int) */
+    public void setExitValue(final int value) {
+        this.setExitValues(new int[] {value});
+    }
+
+
+    /** @see org.apache.commons.exec.Executor#setExitValues(int[]) */
+    public void setExitValues(final int[] values) {
+        this.exitValues = (values == null ? null : (int[]) values.clone());
+    }
+
+    /** @see org.apache.commons.exec.Executor#isFailure(int) */
+    public boolean isFailure(final int exitValue) {
+
+        if(this.exitValues == null) {
+            return false;
+        }
+        else if(this.exitValues.length == 0) {
+            return this.launcher.isFailure(exitValue);
+        }
+        else {
+            for(int i=0; i<this.exitValues.length; i++) {
+                if(this.exitValues[i] == exitValue) {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Creates a process that runs a command.
+     *
+     * @param command
+     *            the command to run
+     * @param env
+     *            the environment for the command
+     * @param dir
+     *            the working directory for the command
+     * @return the process started
+     * @throws IOException
+     *             forwarded from the particular launcher used
+     */
+    protected Process launch(final CommandLine command, final Map env,
+            final File dir) throws IOException {
+
+        if (this.launcher == null) {
+            throw new IllegalStateException("CommandLauncher can not be null");
+        }
+
+        if (dir != null && !dir.exists()) {
+            throw new IOException(dir + " doesn't exist.");
+        }
+        return this.launcher.exec(command, env, dir);
+    }
+
+    /**
+     * Get the worker thread being used for asynchronous execution.
+     *
+     * @return the worker thread
+     */
+    protected Thread getExecutorThread() {
+        return executorThread;
+    }
+    
+    /**
+     * Close the streams belonging to the given Process. In the
+     * original implementation all exceptions were dropped which
+     * is probably not a good thing. On the other hand the signature
+     * allows throwing an IOException so the current implementation
+     * might be quite okay.
+     * 
+     * @param process the <CODE>Process</CODE>.
+     * @throws IOException closing one of the three streams failed
+     */
+    private void closeStreams(final Process process) throws IOException {
+
+        IOException caught = null;
+
+        try {
+            process.getInputStream().close();
+        }
+        catch(IOException e) {
+            caught = e;
+        }
+
+        try {
+            process.getOutputStream().close();
+        }
+        catch(IOException e) {
+            caught = e;
+        }
+
+        try {
+            process.getErrorStream().close();
+        }
+        catch(IOException e) {
+            caught = e;
+        }
+
+        if(caught != null) {
+            throw caught;
+        }
+    }
+
+    /**
+     * Execute an internal process.
+     *
+     * @param command the command to execute
+     * @param environment the execution enviroment
+     * @param dir the working directory
+     * @param streams process the streams (in, out, err) of the process
+     * @return the exit code of the process
+     * @throws IOException executing the process failed
+     */
+    private int executeInternal(final CommandLine command, final Map environment,
+            final File dir, final ExecuteStreamHandler streams) throws IOException {
+
+        final Process process = this.launch(command, environment, dir);
+
+        try {
+            streams.setProcessInputStream(process.getOutputStream());
+            streams.setProcessOutputStream(process.getInputStream());
+            streams.setProcessErrorStream(process.getErrorStream());
+        } catch (IOException e) {
+            process.destroy();
+            throw e;
+        }
+
+        streams.start();
+
+        try {
+
+            // add the process to the list of those to destroy if the VM exits
+            if(this.getProcessDestroyer() != null) {
+              this.getProcessDestroyer().add(process);
+            }
+
+            // associate the watchdog with the newly created process
+            if (watchdog != null) {
+                watchdog.start(process);
+            }
+
+            int exitValue = Executor.INVALID_EXITVALUE;
+
+            try {
+                exitValue = process.waitFor();
+            } catch (InterruptedException e) {
+                process.destroy();
+            }
+            finally {
+                // see http://bugs.sun.com/view_bug.do?bug_id=6420270
+                // see https://issues.apache.org/jira/browse/EXEC-46
+                // Process.waitFor should clear interrupt status when throwing InterruptedException
+                // but we have to do that manually
+                Thread.interrupted();
+            }            
+
+            if (watchdog != null) {
+                watchdog.stop();
+            }
+
+            streams.stop();
+            closeStreams(process);
+
+            if (watchdog != null) {
+                try {
+                    watchdog.checkException();
+                } catch (IOException e) {
+                    throw e;
+                } catch (Exception e) {
+                    throw new IOException(e.getMessage());
+                }
+            }
+
+            if(this.isFailure(exitValue)) {
+                throw new ExecuteException("Process exited with an error: " + exitValue, exitValue);
+            }
+
+            return exitValue;
+        } finally {
+            // remove the process to the list of those to destroy if the VM exits
+            if(this.getProcessDestroyer() != null) {
+              this.getProcessDestroyer().remove(process);
+            }
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/exec/ExecuteException.java b/src/main/java/org/apache/commons/exec/ExecuteException.java
new file mode 100644
index 0000000..b05f7d1
--- /dev/null
+++ b/src/main/java/org/apache/commons/exec/ExecuteException.java
@@ -0,0 +1,85 @@
+/* 
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+package org.apache.commons.exec;
+
+import java.io.IOException;
+
+/**
+ * An exception indicating that the executing a subprocesses failed.
+ */
+public class ExecuteException extends IOException {
+
+    /**
+     * Comment for <code>serialVersionUID</code>.
+     */
+    private static final long serialVersionUID = 3256443620654331699L;
+
+	/**
+	 * The underlying cause of this exception.
+	 */
+	private final Throwable cause;
+
+	/**
+	 * The exit value returned by the failed process
+	 */
+	private final int exitValue;
+    
+    /**
+     * Construct a new exception with the specified detail message.
+     * 
+     * @param message
+     *            The detail message
+     * @param exitValue The exit value
+     */
+    public ExecuteException(final String message, int exitValue) {
+        super(message + " (Exit value: " + exitValue + ")");
+        this.cause = null;
+        this.exitValue = exitValue;
+    }
+
+    /**
+     * Construct a new exception with the specified detail message and cause.
+     * 
+     * @param message
+     *            The detail message
+     * @param exitValue The exit value
+     * @param cause
+     *            The underlying cause
+     */
+    public ExecuteException(final String message, int exitValue, final Throwable cause) {
+        super(message + " (Exit value: " + exitValue + ". Caused by " + cause + ")");
+        this.cause = cause; // Two-argument version requires JDK 1.4 or later
+        this.exitValue = exitValue;
+    }
+
+    /**
+     * Return the underlying cause of this exception (if any).
+     */
+    public Throwable getCause() {
+        return (this.cause);
+    }
+
+    /**
+     * Gets the exit value returned by the failed process
+     * @return The exit value
+     */
+    public int getExitValue() {
+    	return exitValue;
+    }
+}
diff --git a/src/main/java/org/apache/commons/exec/ExecuteResultHandler.java b/src/main/java/org/apache/commons/exec/ExecuteResultHandler.java
new file mode 100644
index 0000000..c46f346
--- /dev/null
+++ b/src/main/java/org/apache/commons/exec/ExecuteResultHandler.java
@@ -0,0 +1,43 @@
+/* 
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+package org.apache.commons.exec;
+
+/**
+ * The callback handlers for the result of asynchronous process execution. When a
+ * process is started asynchronously the callback provides you with the result of
+ * the executed process, i.e. the exit value or an exception. 
+ *
+ * @see org.apache.commons.exec.Executor#execute(CommandLine, java.util.Map, ExecuteResultHandler) 
+ */
+public interface ExecuteResultHandler {
+
+  /**
+   * The asynchronous execution completed.
+   *
+   * @param exitValue the exit value of the sub-process
+   */
+    void onProcessComplete(int exitValue);
+
+  /**
+   * The asynchronous execution failed.
+   *
+   * @param e the <code>ExecuteException</code> containing the root cause
+   */
+    void onProcessFailed(ExecuteException e);
+}
diff --git a/src/main/java/org/apache/commons/exec/ExecuteStreamHandler.java b/src/main/java/org/apache/commons/exec/ExecuteStreamHandler.java
new file mode 100644
index 0000000..83d0ab9
--- /dev/null
+++ b/src/main/java/org/apache/commons/exec/ExecuteStreamHandler.java
@@ -0,0 +1,66 @@
+/* 
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+package org.apache.commons.exec;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * Used by <code>Execute</code> to handle input and output stream of
+ * subprocesses.
+ */
+public interface ExecuteStreamHandler {
+
+    /**
+     * Install a handler for the input stream of the subprocess.
+     * 
+     * @param os
+     *            output stream to write to the standard input stream of the
+     *            subprocess
+     */
+    void setProcessInputStream(OutputStream os) throws IOException;
+
+    /**
+     * Install a handler for the error stream of the subprocess.
+     * 
+     * @param is
+     *            input stream to read from the error stream from the subprocess
+     */
+    void setProcessErrorStream(InputStream is) throws IOException;
+
+    /**
+     * Install a handler for the output stream of the subprocess.
+     * 
+     * @param is
+     *            input stream to read from the error stream from the subprocess
+     */
+    void setProcessOutputStream(InputStream is) throws IOException;
+
+    /**
+     * Start handling of the streams.
+     */
+    void start() throws IOException;
+
+    /**
+     * Stop handling of the streams - will not be restarted.
+     * Will wait for pump threads to complete.
+     */
+    void stop();
+}
diff --git a/src/main/java/org/apache/commons/exec/ExecuteWatchdog.java b/src/main/java/org/apache/commons/exec/ExecuteWatchdog.java
new file mode 100644
index 0000000..81e5bd3
--- /dev/null
+++ b/src/main/java/org/apache/commons/exec/ExecuteWatchdog.java
@@ -0,0 +1,207 @@
+/* 
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+package org.apache.commons.exec;
+
+import org.apache.commons.exec.util.DebugUtils;
+
+/**
+ * Destroys a process running for too long. For example:
+ *
+ * <pre>
+ * ExecuteWatchdog watchdog = new ExecuteWatchdog(30000);
+ * Executer exec = new Executer(myloghandler, watchdog);
+ * exec.setCommandLine(mycmdline);
+ * int exitvalue = exec.execute();
+ * if (Execute.isFailure(exitvalue) && watchdog.killedProcess()) {
+ *     // it was killed on purpose by the watchdog
+ * }
+ * </pre>
+ *
+ * When starting an asynchronous process than 'ExecuteWatchdog' is the
+ * keeper of the process handle. In some cases it is useful not to define
+ * a timeout (and pass 'INFINITE_TIMEOUT') and to kill the process explicitly
+ * using 'destroyProcess()'.
+ * <p>
+ * Please note that ExecuteWatchdog is processed asynchronously, e.g. it might
+ * be still attached to a process even after the DefaultExecutor.execute 
+ * has returned.
+ *
+ * @see org.apache.commons.exec.Executor
+ * @see org.apache.commons.exec.Watchdog
+ */
+public class ExecuteWatchdog implements TimeoutObserver {
+
+    /** The marker for an infinite timeout */
+    public static final long INFINITE_TIMEOUT = -1;
+    
+    /** The process to execute and watch for duration. */
+    private Process process;
+
+    /** Is a user-supplied timeout in use */
+    private final boolean hasWatchdog;
+
+    /** Say whether or not the watchdog is currently monitoring a process. */
+    private boolean watch;
+
+    /** Exception that might be thrown during the process execution. */
+    private Exception caught;
+
+    /** Say whether or not the process was killed due to running overtime. */
+    private boolean killedProcess;
+
+    /** Will tell us whether timeout has occurred. */
+    private final Watchdog watchdog;
+
+    /**
+     * Creates a new watchdog with a given timeout.
+     * 
+     * @param timeout
+     *            the timeout for the process in milliseconds. It must be
+     *            greater than 0 or 'INFINITE_TIMEOUT'
+     */
+    public ExecuteWatchdog(final long timeout) {
+        this.killedProcess = false;
+        this.watch = false;
+        this.hasWatchdog = (timeout != INFINITE_TIMEOUT);
+        if(this.hasWatchdog) {
+            this.watchdog = new Watchdog(timeout);
+            this.watchdog.addTimeoutObserver(this);
+        }
+        else {
+            this.watchdog = null;
+        }
+    }
+
+    /**
+     * Watches the given process and terminates it, if it runs for too long. All
+     * information from the previous run are reset.
+     * 
+     * @param process
+     *            the process to monitor. It cannot be <tt>null</tt>
+     * @throws IllegalStateException
+     *             if a process is still being monitored.
+     */
+    public synchronized void start(final Process process) {
+        if (process == null) {
+            throw new NullPointerException("process is null.");
+        }
+        if (this.process != null) {
+            throw new IllegalStateException("Already running.");
+        }
+        this.caught = null;
+        this.killedProcess = false;
+        this.watch = true;
+        this.process = process;
+        if(this.hasWatchdog) {
+            watchdog.start();
+        }
+    }
+
+    /**
+     * Stops the watcher. It will notify all threads possibly waiting on this
+     * object.
+     */
+    public synchronized void stop() {
+        if(hasWatchdog) {
+            watchdog.stop();
+        }
+        watch = false;
+        process = null;
+    }
+
+    /**
+     * Destroys the running process manually.
+     */
+    public synchronized void destroyProcess() {
+        this.timeoutOccured(null);
+        this.stop();
+    }
+
+    /**
+     * Called after watchdog has finished.
+     */
+    public synchronized void timeoutOccured(final Watchdog w) {
+        try {
+            try {
+                // We must check if the process was not stopped
+                // before being here
+                if(process != null) {
+                    process.exitValue();
+                }
+            } catch (IllegalThreadStateException itse) {
+                // the process is not terminated, if this is really
+                // a timeout and not a manual stop then destroy it.
+                if (watch) {
+                    killedProcess = true;
+                    process.destroy();
+                }
+            }
+        } catch (Exception e) {
+            caught = e;
+            DebugUtils.handleException("Getting the exit value of the process failed", e);
+        } finally {
+            cleanUp();
+        }
+    }
+
+
+    /**
+     * This method will rethrow the exception that was possibly caught during
+     * the run of the process. It will only remains valid once the process has
+     * been terminated either by 'error', timeout or manual intervention.
+     * Information will be discarded once a new process is ran.
+     * 
+     * @throws Exception
+     *             a wrapped exception over the one that was silently swallowed
+     *             and stored during the process run.
+     */
+    public synchronized void checkException() throws Exception {
+        if (caught != null) {
+            throw caught;
+        }
+    }
+
+    /**
+     * Indicates whether or not the watchdog is still monitoring the process.
+     * 
+     * @return <tt>true</tt> if the process is still running, otherwise
+     *         <tt>false</tt>.
+     */
+    public synchronized boolean isWatching() {
+        return watch;
+    }
+
+    /**
+     * Indicates whether the last process run was killed.
+     * 
+     * @return <tt>true</tt> if the process was killed
+     *         <tt>false</tt>.
+     */
+    public synchronized boolean killedProcess() {
+        return killedProcess;
+    }
+
+    /**
+     * reset the monitor flag and the process.
+     */
+    protected synchronized void cleanUp() {
+        watch = false;
+        process = null;
+    }    
+}
diff --git a/src/main/java/org/apache/commons/exec/Executor.java b/src/main/java/org/apache/commons/exec/Executor.java
new file mode 100644
index 0000000..bef3d94
--- /dev/null
+++ b/src/main/java/org/apache/commons/exec/Executor.java
@@ -0,0 +1,208 @@
+/* 
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+package org.apache.commons.exec;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Map;
+
+/**
+ * The main abstraction to start an external process.
+ *
+ * The interface allows to
+ * <ul>
+ *  <li>set a current working directory for the subprocess</li>
+ *  <li>provide a set of environment variables passed to the subprocess</li>
+ *  <li>capture the subprocess output of stdout and stderr using an ExecuteStreamHandler</li>
+ *  <li>kill long-running processes using an ExecuteWatchdog</li>
+ *  <li>define a set of expected exit values</li>
+ *  <li>terminate any started processes when the main process is terminating using a ProcessDestroyer</li>
+ * </ul>
+ *
+ * The following example shows the basic usage:
+ *
+ * <pre>
+ * Executor exec = new DefaultExecutor();
+ * CommandLine cl = new CommandLine("ls -l");
+ * int exitvalue = exec.execute(cl);
+ * </pre>
+ */
+
+public interface Executor {
+
+    /** Invalid exit code. */
+    int INVALID_EXITVALUE = 0xdeadbeef;
+
+    /**
+     * Define the <code>exitValue</code> of the process to be considered
+     * successful. If a different exit value is returned by
+     * the process then {@link org.apache.commons.exec.Executor#execute(CommandLine)}
+     * will throw an {@link org.apache.commons.exec.ExecuteException} 
+     *
+     * @param value the exit code representing successful execution
+     */
+    void setExitValue(final int value);
+
+    /**
+     * Define a list of <code>exitValue</code> of the process to be considered
+     * successful. The caller can pass one of the following values
+     * <ul>
+     *  <li>an array of exit values to be considered successful</li>
+     *  <li>an empty array for auto-detect of successful exit codes relying on {@link org.apache.commons.exec.Executor#isFailure(int)}</li>
+     *  <li>null to indicate to skip checking of exit codes</li>
+     * </ul>
+     *
+     * If an undefined exit value is returned by the process then
+     * {@link org.apache.commons.exec.Executor#execute(CommandLine)}  will
+     * throw an {@link org.apache.commons.exec.ExecuteException}.
+     *
+     * @param values a list of the exit codes
+     */
+    void setExitValues(final int[] values);
+
+    /**
+     * Checks whether <code>exitValue</code> signals a failure. If no
+     * exit values are set than the default conventions of the OS is
+     * used. e.g. most OS regard an exit code of '0' as successful
+     * execution and everything else as failure.
+     *
+     * @param exitValue the exit value (return code) to be checked
+     * @return <code>true</code> if <code>exitValue</code> signals a failure
+     */
+    boolean isFailure(final int exitValue);
+
+    /**
+     * Get the StreamHandler used for providing input and
+     * retrieving the output.
+     * 
+     * @return the StreamHandler 
+     */
+    ExecuteStreamHandler getStreamHandler();
+
+    /**
+     * Set a custom the StreamHandler used for providing
+     * input and retrieving the output. If you don't provide
+     * a proper stream handler the executed process might block
+     * when writing to stdout and/or stderr (see
+     * {@link java.lang.Process Process}).
+     *
+     * @param streamHandler the stream handler
+     */
+    void setStreamHandler(ExecuteStreamHandler streamHandler);
+
+    /**
+     * Get the watchdog used to kill of processes running,
+     * typically, too long time.
+     *
+     * @return the watchdog
+     */
+    ExecuteWatchdog getWatchdog();
+
+    /**
+     * Set the watchdog used to kill of processes running, 
+     * typically, too long time.
+     *
+     * @param watchDog the watchdog
+     */
+    void setWatchdog(ExecuteWatchdog watchDog);
+
+    /**
+     * Set the handler for cleanup of started processes if the main process
+     * is going to terminate.
+     *
+     * @return the ProcessDestroyer
+     */
+    ProcessDestroyer getProcessDestroyer();
+
+    /**
+     * Get the handler for cleanup of started processes if the main process
+     * is going to terminate.
+     *
+     * @param processDestroyer the ProcessDestroyer
+     */
+    void setProcessDestroyer(ProcessDestroyer processDestroyer);
+
+    /**
+     * Get the working directory of the created process.
+     *
+     * @return the working directory
+     */
+    File getWorkingDirectory();
+
+    /**
+     * Set the working directory of the created process. The
+     * working directory must exist when you start the process.
+     *
+     * @param dir the working directory
+     */
+    void setWorkingDirectory(File dir);
+
+    /**
+     * Methods for starting synchronous execution. The child process inherits
+     * all environment variables of the parent process.
+     *
+     * @param command the command to execute
+     * @return process exit value
+     * @throws ExecuteException execution of subprocess failed or the
+     *          subprocess returned a exit value indicating a failure
+     *          {@link Executor#setExitValue(int)}.
+     */
+    int execute(CommandLine command)
+        throws ExecuteException, IOException;
+
+    /**
+     * Methods for starting synchronous execution.
+     *
+     * @param command the command to execute
+     * @param environment The environment for the new process. If null, the
+     *          environment of the current process is used.
+     * @return process exit value
+     * @throws ExecuteException execution of subprocess failed or the
+     *          subprocess returned a exit value indicating a failure
+     *          {@link Executor#setExitValue(int)}.
+     */
+    int execute(CommandLine command, Map environment)
+        throws ExecuteException, IOException;
+    
+    /**
+     * Methods for starting asynchronous execution. The child process inherits
+     * all environment variables of the parent process. Result provided to
+     * callback handler.
+     *
+     * @param command the command to execute
+     * @param handler capture process termination and exit code
+     * @throws ExecuteException execution of subprocess failed
+     */
+    void execute(CommandLine command, ExecuteResultHandler handler)
+        throws ExecuteException, IOException;
+
+    /**
+     * Methods for starting asynchronous execution. The child process inherits
+     * all environment variables of the parent process. Result provided to
+     * callback handler.
+     *
+     * @param command the command to execute
+     * @param environment The environment for the new process. If null, the
+     *          environment of the current process is used.
+     * @param handler capture process termination and exit code 
+     * @throws ExecuteException execution of subprocess failed     
+     */
+    void execute(CommandLine command, Map environment, ExecuteResultHandler handler)
+        throws ExecuteException, IOException;
+}
diff --git a/src/main/java/org/apache/commons/exec/InputStreamPumper.java b/src/main/java/org/apache/commons/exec/InputStreamPumper.java
new file mode 100644
index 0000000..e70499c
--- /dev/null
+++ b/src/main/java/org/apache/commons/exec/InputStreamPumper.java
@@ -0,0 +1,83 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+package org.apache.commons.exec;
+
+import org.apache.commons.exec.util.DebugUtils;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * Copies all data from an System.input stream to an output stream of the executed process.
+ *
+ * @author mkleint
+ */
+public class InputStreamPumper implements Runnable {
+
+    public static final int SLEEPING_TIME = 100;
+
+    /** the input stream to pump from */
+    private final InputStream is;
+
+    /** the output stream to pmp into */
+    private final OutputStream os;
+
+    /** flag to stop the stream pumping */
+    private volatile boolean stop;
+
+
+    /**
+     * Create a new stream pumper.
+     *
+     * @param is input stream to read data from
+     * @param os output stream to write data to.
+     */
+    public InputStreamPumper(final InputStream is, final OutputStream os) {
+        this.is = is;
+        this.os = os;
+        this.stop = false;
+    }
+
+
+    /**
+     * Copies data from the input stream to the output stream. Terminates as
+     * soon as the input stream is closed or an error occurs.
+     */
+    public void run() {
+        try {
+            while (!stop) {
+                while (is.available() > 0 && !stop) {
+                    os.write(is.read());
+                }
+                os.flush();
+                Thread.sleep(SLEEPING_TIME);
+            }
+        } catch (Exception e) {
+            String msg = "Got exception while reading/writing the stream";
+            DebugUtils.handleException(msg ,e);
+        } finally {
+        }
+    }
+
+
+    public void stopProcessing() {
+        stop = true;
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/exec/LogOutputStream.java b/src/main/java/org/apache/commons/exec/LogOutputStream.java
new file mode 100644
index 0000000..353f0eb
--- /dev/null
+++ b/src/main/java/org/apache/commons/exec/LogOutputStream.java
@@ -0,0 +1,176 @@
+/* 
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+package org.apache.commons.exec;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * Base class to connect a logging system to the output and/or
+ * error stream of then external process. The implementation
+ * parses the incoming data to construct a line and passes
+ * the complete line to an user-defined implementation.
+ */
+public abstract class LogOutputStream
+  extends OutputStream {
+
+    /** Initial buffer size. */
+    private static final int INTIAL_SIZE = 132;
+
+    /** Carriage return */
+    private static final int CR = 0x0d;
+
+    /** Linefeed */
+    private static final int LF = 0x0a;
+
+    /** the internal buffer */
+    private final ByteArrayOutputStream buffer = new ByteArrayOutputStream(
+      INTIAL_SIZE);
+
+    private boolean skip = false;
+
+    private final int level;
+
+    /**
+     * Creates a new instance of this class.
+     * Uses the default level of 999.
+     */
+    public LogOutputStream() {
+        this(999);
+    }
+
+    /**
+     * Creates a new instance of this class.
+     *
+     * @param level loglevel used to log data written to this stream.
+     */
+    public LogOutputStream(final int level) {
+        this.level = level;
+    }
+
+    /**
+     * Write the data to the buffer and flush the buffer, if a line separator is
+     * detected.
+     *
+     * @param cc data to log (byte).
+     * @see java.io.OutputStream#write(int)
+     */
+    public void write(final int cc) throws IOException {
+        final byte c = (byte) cc;
+        if ((c == '\n') || (c == '\r')) {
+            if (!skip) {
+                processBuffer();
+            }
+        } else {
+            buffer.write(cc);
+        }
+        skip = (c == '\r');
+    }
+
+    /**
+     * Flush this log stream.
+     *
+     * @see java.io.OutputStream#flush()
+     */
+    public void flush() {
+        if (buffer.size() > 0) {
+            processBuffer();
+        }
+    }
+
+    /**
+     * Writes all remaining data from the buffer.
+     *
+     * @see java.io.OutputStream#close()
+     */
+    public void close() throws IOException {
+        if (buffer.size() > 0) {
+            processBuffer();
+        }
+        super.close();
+    }
+
+    /**
+     * @return the trace level of the log system
+     */
+    public int getMessageLevel() {
+        return level;
+    }
+
+    /**
+     * Write a block of characters to the output stream
+     *
+     * @param b the array containing the data
+     * @param off the offset into the array where data starts
+     * @param len the length of block
+     * @throws java.io.IOException if the data cannot be written into the stream.
+     * @see java.io.OutputStream#write(byte[], int, int)
+     */
+    public void write(final byte[] b, final int off, final int len)
+            throws IOException {
+        // find the line breaks and pass other chars through in blocks
+        int offset = off;
+        int blockStartOffset = offset;
+        int remaining = len;
+        while (remaining > 0) {
+            while (remaining > 0 && b[offset] != LF && b[offset] != CR) {
+                offset++;
+                remaining--;
+            }
+            // either end of buffer or a line separator char
+            int blockLength = offset - blockStartOffset;
+            if (blockLength > 0) {
+                buffer.write(b, blockStartOffset, blockLength);
+            }
+            while (remaining > 0 && (b[offset] == LF || b[offset] == CR)) {
+                write(b[offset]);
+                offset++;
+                remaining--;
+            }
+            blockStartOffset = offset;
+        }
+    }
+
+    /**
+     * Converts the buffer to a string and sends it to <code>processLine</code>.
+     */
+    protected void processBuffer() {
+        processLine(buffer.toString());
+        buffer.reset();
+    }
+
+    /**
+     * Logs a line to the log system of the user.
+     *
+     * @param line
+     *            the line to log.
+     */
+    protected void processLine(final String line) {
+        processLine(line, level);
+    }
+
+    /**
+     * Logs a line to the log system of the user.
+     *
+     * @param line the line to log.
+     * @param level the log level to use
+     */
+    protected abstract void processLine(final String line, final int level);
+}
diff --git a/src/main/java/org/apache/commons/exec/OS.java b/src/main/java/org/apache/commons/exec/OS.java
new file mode 100644
index 0000000..64184da
--- /dev/null
+++ b/src/main/java/org/apache/commons/exec/OS.java
@@ -0,0 +1,244 @@
+/* 
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+package org.apache.commons.exec;
+
+import java.util.Locale;
+
+/**
+ * Condition that tests the OS type.
+ */
+public final class OS {
+    private static final String FAMILY_OS_400 = "os/400";
+
+    private static final String FAMILY_Z_OS = "z/os";
+
+    private static final String FAMILY_WIN9X = "win9x";
+
+    private static final String FAMILY_OPENVMS = "openvms";
+
+    private static final String FAMILY_UNIX = "unix";
+
+    private static final String FAMILY_TANDEM = "tandem";
+
+    private static final String FAMILY_MAC = "mac";
+
+    private static final String FAMILY_DOS = "dos";
+
+    private static final String FAMILY_NETWARE = "netware";
+
+    private static final String FAMILY_OS_2 = "os/2";
+
+    private static final String FAMILY_WINDOWS = "windows";
+
+    private static final String OS_NAME = System.getProperty("os.name")
+            .toLowerCase(Locale.US);
+
+    private static final String OS_ARCH = System.getProperty("os.arch")
+            .toLowerCase(Locale.US);
+
+    private static final String OS_VERSION = System.getProperty("os.version")
+            .toLowerCase(Locale.US);
+
+    private static final String PATH_SEP = System.getProperty("path.separator");
+
+    /**
+     * Default constructor
+     */
+    private OS() {
+    }
+
+    /**
+     * Determines if the OS on which Ant is executing matches the given OS
+     * family. * Possible values:<br />
+     * <ul>
+     * <li>dos</li>
+     * <li>mac</li>
+     * <li>netware</li>
+     * <li>os/2</li>
+     * <li>tandem</li>
+     * <li>unix</li>
+     * <li>windows</li>
+     * <li>win9x</li>
+     * <li>z/os</li>
+     * <li>os/400</li>
+     * </ul>
+     * 
+     * @param family
+     *            the family to check for
+     * @return true if the OS matches
+     */
+    private static boolean isFamily(final String family) {
+        return isOs(family, null, null, null);
+    }
+
+    public static boolean isFamilyDOS() {
+        return isFamily(FAMILY_DOS);
+    }
+
+    public static boolean isFamilyMac() {
+        return isFamily(FAMILY_MAC);
+    }
+
+    public static boolean isFamilyNetware() {
+        return isFamily(FAMILY_NETWARE);
+    }
+
+    public static boolean isFamilyOS2() {
+        return isFamily(FAMILY_OS_2);
+    }
+
+    public static boolean isFamilyTandem() {
+        return isFamily(FAMILY_TANDEM);
+    }
+
+    public static boolean isFamilyUnix() {
+        return isFamily(FAMILY_UNIX);
+    }
+
+    public static boolean isFamilyWindows() {
+        return isFamily(FAMILY_WINDOWS);
+    }
+
+    public static boolean isFamilyWin9x() {
+        return isFamily(FAMILY_WIN9X);
+    }
+
+    public static boolean isFamilyZOS() {
+        return isFamily(FAMILY_Z_OS);
+    }
+
+    public static boolean isFamilyOS400() {
+        return isFamily(FAMILY_OS_400);
+    }
+
+    public static boolean isFamilyOpenVms() {
+        return isFamily(FAMILY_OPENVMS);
+    }
+
+    /**
+     * Determines if the OS on which Ant is executing matches the given OS name.
+     * 
+     * @param name
+     *            the OS name to check for
+     * @return true if the OS matches
+     */
+    public static boolean isName(final String name) {
+        return isOs(null, name, null, null);
+    }
+
+    /**
+     * Determines if the OS on which Ant is executing matches the given OS
+     * architecture.
+     * 
+     * @param arch
+     *            the OS architecture to check for
+     * @return true if the OS matches
+     */
+    public static boolean isArch(final String arch) {
+        return isOs(null, null, arch, null);
+    }
+
+    /**
+     * Determines if the OS on which Ant is executing matches the given OS
+     * version.
+     * 
+     * @param version
+     *            the OS version to check for
+     * @return true if the OS matches
+     */
+    public static boolean isVersion(final String version) {
+        return isOs(null, null, null, version);
+    }
+
+    /**
+     * Determines if the OS on which Ant is executing matches the given OS
+     * family, name, architecture and version
+     * 
+     * @param family
+     *            The OS family
+     * @param name
+     *            The OS name
+     * @param arch
+     *            The OS architecture
+     * @param version
+     *            The OS version
+     * @return true if the OS matches
+     */
+    public static boolean isOs(final String family, final String name,
+            final String arch, final String version) {
+        boolean retValue = false;
+
+        if (family != null || name != null || arch != null || version != null) {
+
+            boolean isFamily = true;
+            boolean isName = true;
+            boolean isArch = true;
+            boolean isVersion = true;
+
+            if (family != null) {
+                if (family.equals(FAMILY_WINDOWS)) {
+                    isFamily = OS_NAME.indexOf(FAMILY_WINDOWS) > -1;
+                } else if (family.equals(FAMILY_OS_2)) {
+                    isFamily = OS_NAME.indexOf(FAMILY_OS_2) > -1;
+                } else if (family.equals(FAMILY_NETWARE)) {
+                    isFamily = OS_NAME.indexOf(FAMILY_NETWARE) > -1;
+                } else if (family.equals(FAMILY_DOS)) {
+                    isFamily = PATH_SEP.equals(";")
+                            && !isFamily(FAMILY_NETWARE);
+                } else if (family.equals(FAMILY_MAC)) {
+                    isFamily = OS_NAME.indexOf(FAMILY_MAC) > -1;
+                } else if (family.equals(FAMILY_TANDEM)) {
+                    isFamily = OS_NAME.indexOf("nonstop_kernel") > -1;
+                } else if (family.equals(FAMILY_UNIX)) {
+                    isFamily = PATH_SEP.equals(":")
+                            && !isFamily(FAMILY_OPENVMS)
+                            && (!isFamily(FAMILY_MAC) || OS_NAME.endsWith("x"));
+                } else if (family.equals(FAMILY_WIN9X)) {
+                    isFamily = isFamily(FAMILY_WINDOWS)
+                            && (OS_NAME.indexOf("95") >= 0
+                                    || OS_NAME.indexOf("98") >= 0
+                                    || OS_NAME.indexOf("me") >= 0 || OS_NAME
+                                    .indexOf("ce") >= 0);
+                } else if (family.equals(FAMILY_Z_OS)) {
+                    isFamily = OS_NAME.indexOf(FAMILY_Z_OS) > -1
+                            || OS_NAME.indexOf("os/390") > -1;
+                } else if (family.equals(FAMILY_OS_400)) {
+                    isFamily = OS_NAME.indexOf(FAMILY_OS_400) > -1;
+                } else if (family.equals(FAMILY_OPENVMS)) {
+                    isFamily = OS_NAME.indexOf(FAMILY_OPENVMS) > -1;
+                } else {
+                    throw new IllegalArgumentException(
+                            "Don\'t know how to detect os family \"" + family
+                                    + "\"");
+                }
+            }
+            if (name != null) {
+                isName = name.equals(OS_NAME);
+            }
+            if (arch != null) {
+                isArch = arch.equals(OS_ARCH);
+            }
+            if (version != null) {
+                isVersion = version.equals(OS_VERSION);
+            }
+            retValue = isFamily && isName && isArch && isVersion;
+        }
+        return retValue;
+    }
+}
diff --git a/src/main/java/org/apache/commons/exec/ProcessDestroyer.java b/src/main/java/org/apache/commons/exec/ProcessDestroyer.java
new file mode 100644
index 0000000..a0c61ab
--- /dev/null
+++ b/src/main/java/org/apache/commons/exec/ProcessDestroyer.java
@@ -0,0 +1,60 @@
+/* 
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+package org.apache.commons.exec;
+
+/**
+ * Destroys all registered {@link java.lang.Process} after a certain event,
+ * typically when the VM exits 
+ * @see org.apache.commons.exec.ShutdownHookProcessDestroyer
+ */
+public interface ProcessDestroyer {
+
+	/**
+	 * Returns <code>true</code> if the specified 
+	 * {@link java.lang.Process} was
+	 * successfully added to the list of processes to be destroy.
+	 * 
+	 * @param process
+	 *      the process to add
+	 * @return <code>true</code> if the specified 
+	 * 		{@link java.lang.Process} was
+	 *      successfully added
+	 */
+	boolean add(Process process);
+	
+	/**
+	 * Returns <code>true</code> if the specified 
+	 * {@link java.lang.Process} was
+	 * successfully removed from the list of processes to be destroy.
+	 * 
+	 * @param process
+	 *            the process to remove
+	 * @return <code>true</code> if the specified 
+	 * 		{@link java.lang.Process} was
+	 *      successfully removed
+	 */
+	boolean remove(Process process);
+
+    /**
+     * Returns the number of registered processes.
+     *
+     * @return the number of register process
+     */
+    int size();
+}
diff --git a/src/main/java/org/apache/commons/exec/PumpStreamHandler.java b/src/main/java/org/apache/commons/exec/PumpStreamHandler.java
new file mode 100644
index 0000000..e941cce
--- /dev/null
+++ b/src/main/java/org/apache/commons/exec/PumpStreamHandler.java
@@ -0,0 +1,304 @@
+/* 
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+package org.apache.commons.exec;
+
+import org.apache.commons.exec.util.DebugUtils;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * Copies standard output and error of subprocesses to standard output and error
+ * of the parent process. If output or error stream are set to null, any feedback
+ * from that stream will be lost. 
+ */
+public class PumpStreamHandler implements ExecuteStreamHandler {
+
+    private Thread outputThread;
+
+    private Thread errorThread;
+
+    private Thread inputThread;
+
+    private final OutputStream out;
+
+    private final OutputStream err;
+
+    private final InputStream input;
+
+    private InputStreamPumper inputStreamPumper;
+    
+    /**
+     * Construct a new <CODE>PumpStreamHandler</CODE>.
+     */
+    public PumpStreamHandler() {
+        this(System.out, System.err);
+    }
+
+    /**
+     * Construct a new <CODE>PumpStreamHandler</CODE>.
+     *
+     * @param outAndErr
+     *            the output/error <CODE>OutputStream</CODE>.
+     */
+    public PumpStreamHandler(final OutputStream outAndErr) {
+        this(outAndErr, outAndErr);
+    }
+    
+    /**
+     * Construct a new <CODE>PumpStreamHandler</CODE>.
+     *
+     * @param out
+     *            the output <CODE>OutputStream</CODE>.
+     * @param err
+     *            the error <CODE>OutputStream</CODE>.
+     */
+    public PumpStreamHandler(final OutputStream out, final OutputStream err) {
+        this(out, err, null);
+    }
+
+    /**
+     * Construct a new <CODE>PumpStreamHandler</CODE>.
+     * 
+     * @param out
+     *            the output <CODE>OutputStream</CODE>.
+     * @param err
+     *            the error <CODE>OutputStream</CODE>.
+     * @param input
+     *            the input <CODE>InputStream</CODE>.
+     */
+    public PumpStreamHandler(final OutputStream out, final OutputStream err,
+            final InputStream input) {
+
+        this.out = out;
+        this.err = err;
+        this.input = input;
+    }
+
+    /**
+     * Set the <CODE>InputStream</CODE> from which to read the standard output
+     * of the process.
+     * 
+     * @param is
+     *            the <CODE>InputStream</CODE>.
+     */
+    public void setProcessOutputStream(final InputStream is) {
+        if (out != null) {
+            createProcessOutputPump(is, out);
+        }
+    }
+
+    /**
+     * Set the <CODE>InputStream</CODE> from which to read the standard error
+     * of the process.
+     * 
+     * @param is
+     *            the <CODE>InputStream</CODE>.
+     */
+    public void setProcessErrorStream(final InputStream is) {
+        if (err != null) {
+            createProcessErrorPump(is, err);
+        }
+    }
+
+    /**
+     * Set the <CODE>OutputStream</CODE> by means of which input can be sent
+     * to the process.
+     * 
+     * @param os
+     *            the <CODE>OutputStream</CODE>.
+     */
+    public void setProcessInputStream(final OutputStream os) {
+        if (input != null) {
+            if (input == System.in) {
+                inputThread = createSystemInPump(input, os);
+        } else {
+                inputThread = createPump(input, os, true);
+            }        } 
+        else {
+            try {
+                os.close();
+            } catch (IOException e) {
+                String msg = "Got exception while closing output stream";
+                DebugUtils.handleException(msg ,e);
+            }
+        }
+    }
+        
+    /**
+     * Start the <CODE>Thread</CODE>s.
+     */
+    public void start() {
+        if (outputThread != null) {
+            outputThread.start();
+        }
+        if (errorThread != null) {
+            errorThread.start();
+        }
+        if (inputThread != null) {
+            inputThread.start();
+        }
+    }
+
+    /**
+     * Stop pumping the streams.
+     */
+    public void stop() {
+
+        if (inputStreamPumper != null) {
+            inputStreamPumper.stopProcessing();
+        }
+
+        if (outputThread != null) {
+            try {
+                outputThread.join();
+                outputThread = null;
+            } catch (InterruptedException e) {
+                // ignore
+            }
+        }
+
+        if (errorThread != null) {
+            try {
+                errorThread.join();
+                errorThread = null;
+            } catch (InterruptedException e) {
+                // ignore
+            }
+        }
+
+        if (inputThread != null) {
+            try {
+                inputThread.join();
+                inputThread = null;
+            } catch (InterruptedException e) {
+                // ignore
+            }
+        }
+
+         if (err != null && err != out) {
+             try {
+                 err.flush();
+             } catch (IOException e) {
+                 String msg = "Got exception while flushing the error stream : " + e.getMessage();
+                 DebugUtils.handleException(msg ,e);
+             }
+         }
+
+         if (out != null) {
+             try {
+                 out.flush();
+             } catch (IOException e) {
+                 String msg = "Got exception while flushing the output stream";
+                 DebugUtils.handleException(msg ,e);
+             }
+         }
+    }
+
+    /**
+     * Get the error stream.
+     * 
+     * @return <CODE>OutputStream</CODE>.
+     */
+    protected OutputStream getErr() {
+        return err;
+    }
+
+    /**
+     * Get the output stream.
+     * 
+     * @return <CODE>OutputStream</CODE>.
+     */
+    protected OutputStream getOut() {
+        return out;
+    }
+
+    /**
+     * Create the pump to handle process output.
+     * 
+     * @param is
+     *            the <CODE>InputStream</CODE>.
+     * @param os
+     *            the <CODE>OutputStream</CODE>.
+     */
+    protected void createProcessOutputPump(final InputStream is,
+            final OutputStream os) {
+        outputThread = createPump(is, os);
+    }
+
+    /**
+     * Create the pump to handle error output.
+     * 
+     * @param is
+     *            the <CODE>InputStream</CODE>.
+     * @param os
+     *            the <CODE>OutputStream</CODE>.
+     */
+    protected void createProcessErrorPump(final InputStream is,
+            final OutputStream os) {
+        errorThread = createPump(is, os);
+    }
+
+    /**
+     * Creates a stream pumper to copy the given input stream to the given
+     * output stream.
+     *
+     * @param is the input stream to copy from
+     * @param os the output stream to copy into
+     * @return the stream pumper thread
+     */
+    protected Thread createPump(final InputStream is, final OutputStream os) {
+        return createPump(is, os, false);
+    }
+
+    /**
+     * Creates a stream pumper to copy the given input stream to the given
+     * output stream.
+     *
+     * @param is the input stream to copy from
+     * @param os the output stream to copy into
+     * @param closeWhenExhausted close the output stream when the input stream is exhausted
+     * @return the stream pumper thread
+     */
+    protected Thread createPump(final InputStream is, final OutputStream os,
+            final boolean closeWhenExhausted) {
+        final Thread result = new Thread(new StreamPumper(is, os,
+                closeWhenExhausted));
+        result.setDaemon(true);
+        return result;
+    }
+
+
+    /**
+     * Creates a stream pumper to copy the given input stream to the given
+     * output stream.
+     *
+     * @param is the System.in input stream to copy from
+     * @param os the output stream to copy into
+     * @return the stream pumper thread
+     */
+    private Thread createSystemInPump(InputStream is, OutputStream os) {
+        inputStreamPumper = new InputStreamPumper(is, os);
+        final Thread result = new Thread(inputStreamPumper);
+        result.setDaemon(true);
+        return result;
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/exec/ShutdownHookProcessDestroyer.java b/src/main/java/org/apache/commons/exec/ShutdownHookProcessDestroyer.java
new file mode 100644
index 0000000..d93a0b2
--- /dev/null
+++ b/src/main/java/org/apache/commons/exec/ShutdownHookProcessDestroyer.java
@@ -0,0 +1,194 @@
+/* 
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+package org.apache.commons.exec;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ * Destroys all registered <code>Process</code>es when the VM exits.
+ */
+public class ShutdownHookProcessDestroyer implements ProcessDestroyer, Runnable {
+
+    /** the list of currently running processes */
+    private final Vector processes = new Vector();
+
+    /** The thread registered at the JVM to execute the shutdown handler */
+    private ProcessDestroyerImpl destroyProcessThread = null;
+
+    /** Whether or not this ProcessDestroyer has been registered as a shutdown hook */
+    private boolean added = false;
+
+    /**
+     * Whether or not this ProcessDestroyer is currently running as shutdown hook
+     */
+  	private volatile boolean running = false;
+
+    private class ProcessDestroyerImpl extends Thread {
+
+        private boolean shouldDestroy = true;
+
+        public ProcessDestroyerImpl() {
+            super("ProcessDestroyer Shutdown Hook");
+        }
+
+        public void run() {
+            if (shouldDestroy) {
+                ShutdownHookProcessDestroyer.this.run();
+            }
+        }
+
+        public void setShouldDestroy(final boolean shouldDestroy) {
+            this.shouldDestroy = shouldDestroy;
+        }
+    }
+
+    /**
+     * Constructs a <code>ProcessDestroyer</code> and obtains
+     * <code>Runtime.addShutdownHook()</code> and
+     * <code>Runtime.removeShutdownHook()</code> through reflection. The
+     * ProcessDestroyer manages a list of processes to be destroyed when the VM
+     * exits. If a process is added when the list is empty, this
+     * <code>ProcessDestroyer</code> is registered as a shutdown hook. If
+     * removing a process results in an empty list, the
+     * <code>ProcessDestroyer</code> is removed as a shutdown hook.
+     */
+    public ShutdownHookProcessDestroyer() {
+    }
+
+    /**
+     * Registers this <code>ProcessDestroyer</code> as a shutdown hook, uses
+     * reflection to ensure pre-JDK 1.3 compatibility.
+     */
+    private void addShutdownHook() {
+        if (!running) {
+            destroyProcessThread = new ProcessDestroyerImpl();
+            Runtime.getRuntime().addShutdownHook(destroyProcessThread);
+            added = true;
+        }
+    }
+
+	/**
+	 * Removes this <code>ProcessDestroyer</code> as a shutdown hook, uses
+	 * reflection to ensure pre-JDK 1.3 compatibility
+	 */
+	private void removeShutdownHook() {
+		if (added && !running) {
+			boolean removed = Runtime.getRuntime().removeShutdownHook(
+					destroyProcessThread);
+			if (!removed) {
+				System.err.println("Could not remove shutdown hook");
+			}
+			/*
+			 * start the hook thread, a unstarted thread may not be eligible for
+			 * garbage collection Cf.: http://developer.java.sun.com/developer/
+			 * bugParade/bugs/4533087.html
+			 */
+
+			destroyProcessThread.setShouldDestroy(false);
+			destroyProcessThread.start();
+			// this should return quickly, since it basically is a NO-OP.
+			try {
+				destroyProcessThread.join(20000);
+			} catch (InterruptedException ie) {
+				// the thread didn't die in time
+				// it should not kill any processes unexpectedly
+			}
+			destroyProcessThread = null;
+			added = false;
+		}
+	}
+
+	/**
+	 * Returns whether or not the ProcessDestroyer is registered as as shutdown
+	 * hook
+	 * 
+	 * @return true if this is currently added as shutdown hook
+	 */
+	public boolean isAddedAsShutdownHook() {
+		return added;
+	}
+
+	/**
+	 * Returns <code>true</code> if the specified <code>Process</code> was
+	 * successfully added to the list of processes to destroy upon VM exit.
+	 * 
+	 * @param process
+	 *            the process to add
+	 * @return <code>true</code> if the specified <code>Process</code> was
+	 *         successfully added
+	 */
+	public boolean add(final Process process) {
+		synchronized (processes) {
+			// if this list is empty, register the shutdown hook
+			if (processes.size() == 0) {
+				addShutdownHook();
+			}
+			processes.addElement(process);
+			return processes.contains(process);
+		}
+	}
+
+	/**
+	 * Returns <code>true</code> if the specified <code>Process</code> was
+	 * successfully removed from the list of processes to destroy upon VM exit.
+	 * 
+	 * @param process
+	 *            the process to remove
+	 * @return <code>true</code> if the specified <code>Process</code> was
+	 *         successfully removed
+	 */
+	public boolean remove(final Process process) {
+        synchronized (processes) {
+            boolean processRemoved = processes.removeElement(process);
+            if (processRemoved && processes.size() == 0) {
+                removeShutdownHook();
+            }
+            return processRemoved;
+        }
+	}
+
+  /**
+   * Returns the number of registered processes.
+   *
+   * @return the number of register process
+   */
+  public int size() {
+    return processes.size();
+  }
+
+  /**
+	 * Invoked by the VM when it is exiting.
+	 */
+  public void run() {
+      synchronized (processes) {
+          running = true;
+          Enumeration e = processes.elements();
+          while (e.hasMoreElements()) {
+              Process process = (Process) e.nextElement();
+              try {
+                  process.destroy();
+              }
+              catch (Throwable t) {
+                  System.err.println("Unable to terminate process during process shutdown");
+              }
+          }
+      }
+  }
+}
diff --git a/src/main/java/org/apache/commons/exec/StreamPumper.java b/src/main/java/org/apache/commons/exec/StreamPumper.java
new file mode 100644
index 0000000..dcf396c
--- /dev/null
+++ b/src/main/java/org/apache/commons/exec/StreamPumper.java
@@ -0,0 +1,145 @@
+/* 
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+package org.apache.commons.exec;
+
+import org.apache.commons.exec.util.DebugUtils;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * Copies all data from an input stream to an output stream.
+ */
+public class StreamPumper implements Runnable {
+
+    /** the default size of the internal buffer for copying the streams */
+    private static final int DEFAULT_SIZE = 1024;
+
+    /** the input stream to pump from */
+    private final InputStream is;
+
+    /** the output stream to pmp into */
+    private final OutputStream os;
+
+    /** the size of the internal buffer for copying the streams */ 
+    private final int size;
+
+    /** was the end of the stream reached */
+    private boolean finished;
+
+    /** close the output stream when exhausted */
+    private final boolean closeWhenExhausted;
+    
+    /**
+     * Create a new stream pumper.
+     * 
+     * @param is input stream to read data from
+     * @param os output stream to write data to.
+     * @param closeWhenExhausted if true, the output stream will be closed when the input is exhausted.
+     */
+    public StreamPumper(final InputStream is, final OutputStream os,
+            final boolean closeWhenExhausted) {
+        this.is = is;
+        this.os = os;
+        this.size = DEFAULT_SIZE;
+        this.closeWhenExhausted = closeWhenExhausted;
+    }
+
+    /**
+     * Create a new stream pumper.
+     *
+     * @param is input stream to read data from
+     * @param os output stream to write data to.
+     * @param closeWhenExhausted if true, the output stream will be closed when the input is exhausted.
+     * @param size the size of the internal buffer for copying the streams
+     */
+    public StreamPumper(final InputStream is, final OutputStream os,
+            final boolean closeWhenExhausted, final int size) {
+        this.is = is;
+        this.os = os;
+        this.size = (size > 0 ? size : DEFAULT_SIZE);
+        this.closeWhenExhausted = closeWhenExhausted;
+    }
+
+    /**
+     * Create a new stream pumper.
+     * 
+     * @param is input stream to read data from
+     * @param os output stream to write data to.
+     */
+    public StreamPumper(final InputStream is, final OutputStream os) {
+        this(is, os, false);
+    }
+
+    /**
+     * Copies data from the input stream to the output stream. Terminates as
+     * soon as the input stream is closed or an error occurs.
+     */
+    public void run() {
+        synchronized (this) {
+            // Just in case this object is reused in the future
+            finished = false;
+        }
+
+        final byte[] buf = new byte[this.size];
+
+        int length;
+        try {
+            while ((length = is.read(buf)) > 0) {
+                os.write(buf, 0, length);
+            }
+        } catch (Exception e) {
+            // nothing to do - happens quite often with watchdog
+        } finally {
+            if (closeWhenExhausted) {
+                try {
+                    os.close();
+                } catch (IOException e) {
+                    String msg = "Got exception while closing exhausted output stream";
+                    DebugUtils.handleException(msg ,e);
+                }
+            }
+            synchronized (this) {
+                finished = true;
+                notifyAll();
+            }
+        }
+    }
+
+    /**
+     * Tells whether the end of the stream has been reached.
+     * 
+     * @return true is the stream has been exhausted.
+     */
+    public synchronized boolean isFinished() {
+        return finished;
+    }
+
+    /**
+     * This method blocks until the stream pumper finishes.
+     * 
+     * @see #isFinished()
+     */
+    public synchronized void waitFor() throws InterruptedException {
+        while (!isFinished()) {
+            wait();
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/exec/TimeoutObserver.java b/src/main/java/org/apache/commons/exec/TimeoutObserver.java
new file mode 100644
index 0000000..dc7c0f2
--- /dev/null
+++ b/src/main/java/org/apache/commons/exec/TimeoutObserver.java
@@ -0,0 +1,34 @@
+/* 
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+package org.apache.commons.exec;
+
+/**
+ * Interface for classes that want to be notified by Watchdog.
+ * 
+ * @see org.apache.commons.exec.Watchdog
+ */
+public interface TimeoutObserver {
+
+    /**
+     * Called when the watchdog times out.
+     * 
+     * @param w the watchdog that timed out.
+     */
+    void timeoutOccured(Watchdog w);
+}
diff --git a/src/main/java/org/apache/commons/exec/Watchdog.java b/src/main/java/org/apache/commons/exec/Watchdog.java
new file mode 100644
index 0000000..f9eecff
--- /dev/null
+++ b/src/main/java/org/apache/commons/exec/Watchdog.java
@@ -0,0 +1,84 @@
+/* 
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+package org.apache.commons.exec;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ * Generalization of <code>ExecuteWatchdog</code>
+ * 
+ * @see org.apache.commons.exec.ExecuteWatchdog
+ */
+public class Watchdog implements Runnable {
+
+    private Vector observers = new Vector(1);
+
+    private final long timeout;
+
+    private boolean stopped = false;
+
+    public Watchdog(final long timeout) {
+        if (timeout < 1) {
+            throw new IllegalArgumentException("timeout must not be less than 1.");
+        }
+        this.timeout = timeout;
+    }
+
+    public void addTimeoutObserver(final TimeoutObserver to) {
+        observers.addElement(to);
+    }
+
+    public void removeTimeoutObserver(final TimeoutObserver to) {
+        observers.removeElement(to);
+    }
+
+    protected final void fireTimeoutOccured() {
+        Enumeration e = observers.elements();
+        while (e.hasMoreElements()) {
+            ((TimeoutObserver) e.nextElement()).timeoutOccured(this);
+        }
+    }
+
+    public synchronized void start() {
+        stopped = false;
+        Thread t = new Thread(this, "WATCHDOG");
+        t.setDaemon(true);
+        t.start();
+    }
+
+    public synchronized void stop() {
+        stopped = true;
+        notifyAll();
+    }
+
+    public synchronized void run() {
+        final long until = System.currentTimeMillis() + timeout;
+        long now;
+        while (!stopped && until > (now = System.currentTimeMillis())) {
+            try {
+                wait(until - now);
+            } catch (InterruptedException e) {
+            }
+        }
+        if (!stopped) {
+            fireTimeoutOccured();
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/exec/environment/DefaultProcessingEnvironment.java b/src/main/java/org/apache/commons/exec/environment/DefaultProcessingEnvironment.java
new file mode 100644
index 0000000..79e8def
--- /dev/null
+++ b/src/main/java/org/apache/commons/exec/environment/DefaultProcessingEnvironment.java
@@ -0,0 +1,241 @@
+/* 
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.exec.environment;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.StringReader;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.TreeMap;
+
+import org.apache.commons.exec.CommandLine;
+import org.apache.commons.exec.DefaultExecutor;
+import org.apache.commons.exec.Executor;
+import org.apache.commons.exec.OS;
+import org.apache.commons.exec.PumpStreamHandler;
+
+/**
+ * Helper class to determine the environment variable
+ * for the OS. Depending on the JDK the environment
+ * variables can be either retrieved directly from the
+ * JVM or requires starting a process to get them running
+ * an OS command line. 
+ */
+public class DefaultProcessingEnvironment {
+
+    /** the line separator of the system */
+    private static final String LINE_SEPARATOR = System.getProperty("line.separator");
+
+    /** the environment variables of the process */
+    protected Map procEnvironment;
+
+    /**
+     * Find the list of environment variables for this process.
+     *
+     * @return a map containing the environment variables
+     * @throws IOException obtaining the environment variables failed
+     */
+    public synchronized Map getProcEnvironment() throws IOException {
+
+        if(procEnvironment == null) {
+            procEnvironment = this.createProcEnvironment();
+        }
+
+        // create a copy of the map just in case that
+        // anyone is going to modifiy it, e.g. removing
+        // or setting an evironment variable
+        Map copy = createEnvironmentMap();
+        copy.putAll(procEnvironment);
+        return copy;
+    }
+
+    /**
+     * Find the list of environment variables for this process.
+     *
+     * @return a amp containing the environment variables
+     * @throws IOException the operation failed 
+     */
+    protected Map createProcEnvironment() throws IOException {
+        if (procEnvironment == null) {
+            try {
+                Method getenvs = System.class.getMethod( "getenv", (java.lang.Class[]) null );
+                Map env = (Map) getenvs.invoke( null, (java.lang.Object[]) null );
+                procEnvironment = createEnvironmentMap();
+                procEnvironment.putAll(env);
+            } catch ( NoSuchMethodException e ) {
+                // ok, just not on JDK 1.5
+            } catch ( IllegalAccessException e ) {
+                // Unexpected error obtaining environment - using JDK 1.4 method
+            } catch ( InvocationTargetException e ) {
+                // Unexpected error obtaining environment - using JDK 1.4 method
+            }
+        }
+
+        if(procEnvironment == null) {
+            procEnvironment = createEnvironmentMap();
+            BufferedReader in = runProcEnvCommand();
+
+            String var = null;
+            String line;
+            while ((line = in.readLine()) != null) {
+                if (line.indexOf('=') == -1) {
+                    // Chunk part of previous env var (UNIX env vars can
+                    // contain embedded new lines).
+                    if (var == null) {
+                        var = LINE_SEPARATOR + line;
+                    } else {
+                        var += LINE_SEPARATOR + line;
+                    }
+                } else {
+                    // New env var...append the previous one if we have it.
+                    if (var != null) {
+                    	EnvironmentUtils.addVariableToEnvironment(procEnvironment, var);
+                    }
+                    var = line;
+                }
+            }
+            // Since we "look ahead" before adding, there's one last env var.
+            if (var != null) {
+            	EnvironmentUtils.addVariableToEnvironment(procEnvironment, var);
+            }
+        }
+        return procEnvironment;
+    }
+
+    /**
+     * Start a process to list the environment variables.
+     *
+     * @return a reader containing the output of the process 
+     * @throws IOException starting the process failed
+     */
+    protected BufferedReader runProcEnvCommand() throws IOException {
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        Executor exe = new DefaultExecutor();
+        exe.setStreamHandler(new PumpStreamHandler(out));
+        // ignore the exit value - Just try to use what we got
+        exe.execute(getProcEnvCommand());
+        return new BufferedReader(new StringReader(toString(out)));
+    }
+
+    /**
+     * Determine the OS specific command line to get a list of environment
+     * variables.
+     *
+     * @return the command line
+     */
+    protected CommandLine getProcEnvCommand() {
+        String executable;
+        String[] arguments = null;
+        if (OS.isFamilyOS2()) {
+            // OS/2 - use same mechanism as Windows 2000
+            executable = "cmd";
+            
+            arguments = new String[] {"/c", "set"};
+        } else if (OS.isFamilyWindows()) {
+            // Determine if we're running under XP/2000/NT or 98/95
+            if (OS.isFamilyWin9x()) {
+                executable = "command.com";
+                // Windows 98/95
+            } else {
+                executable = "cmd";
+                // Windows XP/2000/NT/2003
+            }
+            arguments = new String[] {"/c", "set"};
+        } else if (OS.isFamilyZOS() || OS.isFamilyUnix()) {
+            // On most systems one could use: /bin/sh -c env
+
+            // Some systems have /bin/env, others /usr/bin/env, just try
+            if (new File("/bin/env").canRead()) {
+                executable = "/bin/env";
+            } else if (new File("/usr/bin/env").canRead()) {
+                executable = "/usr/bin/env";
+            } else {
+                // rely on PATH
+                executable = "env";
+            }
+        } else if (OS.isFamilyNetware() || OS.isFamilyOS400()) {
+            // rely on PATH
+            executable = "env";
+        } else {
+            // MAC OS 9 and previous
+            // TODO: I have no idea how to get it, someone must fix it
+            executable = null;
+        }
+        CommandLine commandLine = null;
+        if(executable != null) {
+            commandLine = new CommandLine(executable);
+            commandLine.addArguments(arguments);
+        }
+        return commandLine;
+    }
+
+    /**
+     * ByteArrayOutputStream#toString doesn't seem to work reliably on OS/390,
+     * at least not the way we use it in the execution context.
+     * 
+     * @param bos
+     *            the output stream that one wants to read
+     * @return the output stream as a string, read with special encodings in the
+     *         case of z/os and os/400
+     */
+    private String toString(final ByteArrayOutputStream bos) {
+        if (OS.isFamilyZOS()) {
+            try {
+                return bos.toString("Cp1047");
+            } catch (java.io.UnsupportedEncodingException e) {
+                // noop default encoding used
+            }
+        } else if (OS.isFamilyOS400()) {
+            try {
+                return bos.toString("Cp500");
+            } catch (java.io.UnsupportedEncodingException e) {
+                // noop default encoding used
+            }
+        }
+        return bos.toString();
+    }
+
+    /**
+     * Creates a map that obeys the casing rules of the current platform for key
+     * lookup. E.g. on a Windows platform, the map keys will be
+     * case-insensitive.
+     * 
+     * @return The map for storage of environment variables, never
+     *         <code>null</code>.
+     */
+    private Map createEnvironmentMap() {
+        if (OS.isFamilyWindows()) {
+            return new TreeMap(new Comparator() {
+                public int compare(Object arg0, Object arg1) {
+                    String key0 = (String) arg0;
+                    String key1 = (String) arg1;
+                    return key0.compareToIgnoreCase(key1);
+                }
+            });
+        } else {
+            return new HashMap();
+        }
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/exec/environment/EnvironmentUtils.java b/src/main/java/org/apache/commons/exec/environment/EnvironmentUtils.java
new file mode 100644
index 0000000..fd41b7b
--- /dev/null
+++ b/src/main/java/org/apache/commons/exec/environment/EnvironmentUtils.java
@@ -0,0 +1,119 @@
+/* 
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+package org.apache.commons.exec.environment;
+
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.Map;
+
+import org.apache.commons.exec.OS;
+
+/**
+ * Wrapper for environment variables.
+ */
+public class EnvironmentUtils
+{
+
+	private static final DefaultProcessingEnvironment PROCESSING_ENVIRONMENT_IMPLEMENTATION;
+	
+	static {
+        if (OS.isFamilyOpenVms()) {
+        	PROCESSING_ENVIRONMENT_IMPLEMENTATION = new OpenVmsProcessingEnvironment();
+        } else {
+        	PROCESSING_ENVIRONMENT_IMPLEMENTATION = new DefaultProcessingEnvironment();
+        }
+	}
+	
+    /**
+     * Disable constructor.
+     */
+    private EnvironmentUtils() {
+
+    }
+
+    /**
+     * Get the variable list as an array.
+     *
+     * @param environment the environment to use, may be <code>null</code>
+     * @return array of key=value assignment strings or <code>null</code> if and only if
+     *     the input map was <code>null</code>
+     */
+    public static String[] toStrings(Map environment) {
+        if (environment == null) {
+            return null;
+        }
+        String[] result = new String[environment.size()];
+        int i = 0;
+        for (Iterator iter = environment.entrySet().iterator(); iter.hasNext();) {
+            Map.Entry entry = (Map.Entry) iter.next();
+
+            result[i] = entry.getKey().toString() + "=" + entry.getValue().toString();
+            i++;
+        }
+        return result;
+    }
+
+    /**
+     * Find the list of environment variables for this process. The returned map preserves
+     * the casing of a variable's name on all platforms but obeys the casing rules of the
+     * current platform during lookup, e.g. key names will be case-insensitive on Windows
+     * platforms.
+     *
+     * @return a map containing the environment variables, may be empty but never <code>null</code>
+     * @throws IOException the operation failed
+     */
+    public static Map getProcEnvironment() throws IOException {
+    	return PROCESSING_ENVIRONMENT_IMPLEMENTATION.getProcEnvironment();
+    }
+
+    /**
+     * Add a key/value pair to the given environment.
+     * If the key matches an existing key, the previous setting is replaced.
+     *
+     * @param environment the current environment
+     * @param keyAndValue the key/value pair 
+     */
+    public static void addVariableToEnvironment(Map environment, String keyAndValue) {
+		String[] parsedVariable = parseEnvironmentVariable(keyAndValue);		
+		environment.put(parsedVariable[0], parsedVariable[1]);
+	}
+    
+    /**
+     * Split a key/value pair into a String[]. It is assumed
+     * that the ky/value pair contains a '=' character.
+     *
+     * @param keyAndValue the key/value pair
+     * @return a String[] containing the key and value
+     */
+    private static String[] parseEnvironmentVariable(final String keyAndValue) {
+        int index = keyAndValue.indexOf('=');
+        if (index == -1) {
+            throw new IllegalArgumentException(
+                    "Environment variable for this platform "
+                            + "must contain an equals sign ('=')");
+        }
+
+        String[] result = new String[2];
+        result[0] = keyAndValue.substring(0, index);
+        result[1] = keyAndValue.substring(index + 1);
+
+        return result;
+    }
+    
+}
diff --git a/src/main/java/org/apache/commons/exec/environment/OpenVmsProcessingEnvironment.java b/src/main/java/org/apache/commons/exec/environment/OpenVmsProcessingEnvironment.java
new file mode 100644
index 0000000..84338c4
--- /dev/null
+++ b/src/main/java/org/apache/commons/exec/environment/OpenVmsProcessingEnvironment.java
@@ -0,0 +1,86 @@
+/* 
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.exec.environment;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.commons.exec.CommandLine;
+
+/**
+ * Helper class to determine the environment variable
+ * for VMS.
+ */
+public class OpenVmsProcessingEnvironment extends DefaultProcessingEnvironment {
+
+    /**
+     * Find the list of environment variables for this process.
+     *
+     * @return a map containing the environment variables
+     * @throws IOException the operation failed
+     */    
+    protected Map createProcEnvironment() throws IOException {
+        if (procEnvironment == null) {
+            BufferedReader in = runProcEnvCommand();
+            procEnvironment = addVMSenvironmentVariables(new HashMap(), in);
+        }
+
+        return procEnvironment;
+    }
+
+    /**
+     * Determine the OS specific command line to get a list of environment
+     * variables.
+     *
+     * @return the command line
+     */    
+    protected CommandLine getProcEnvCommand() {
+        CommandLine commandLine = new CommandLine("show");
+        commandLine.addArgument("symbol/global"); // the parser assumes symbols are global
+        commandLine.addArgument("*");
+        return commandLine;
+    }
+
+    /**
+     * This method is VMS specific and used by getProcEnvironment(). Parses VMS
+     * symbols from <code>in</code> and adds them to <code>environment</code>.
+     * <code>in</code> is expected to be the output of "SHOW SYMBOL/GLOBAL *".
+     *
+     * @param environment the current environment
+     * @param in the reader from the process to determine VMS env variables
+     * @return the updated environment
+     * @throws IOException operation failed
+     */
+    private Map addVMSenvironmentVariables(final Map environment,
+            final BufferedReader in) throws IOException {
+        String line;
+        while ((line = in.readLine()) != null) {
+            final String SEP = "=="; // global symbol separator
+            int sepidx = line.indexOf(SEP);
+            if (sepidx > 0){
+                String name = line.substring(0, sepidx).trim();
+                String value = line.substring(sepidx+SEP.length()).trim();
+                value = value.substring(1,value.length()-1); // drop enclosing quotes
+                environment.put(name,value);
+            }
+        }
+        return environment;
+    }
+}
diff --git a/src/main/java/org/apache/commons/exec/launcher/CommandLauncher.java b/src/main/java/org/apache/commons/exec/launcher/CommandLauncher.java
new file mode 100644
index 0000000..ff76fa7
--- /dev/null
+++ b/src/main/java/org/apache/commons/exec/launcher/CommandLauncher.java
@@ -0,0 +1,86 @@
+/* 
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+package org.apache.commons.exec.launcher;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Map;
+
+import org.apache.commons.exec.CommandLine;
+
+/**
+ * Interface to shield the caller from the various platform-dependent
+ * implementations.
+ */
+public interface CommandLauncher {
+
+    /**
+     * Launches the given command in a new process.
+     * 
+     * @param cmd
+     *            The command to execute
+     * @param env
+     *            The environment for the new process. If null, the environment
+     *            of the current process is used.
+     * 
+     * @return the newly created process
+     * @throws IOException
+     *             if attempting to run a command in a specific directory
+     */
+    Process exec(final CommandLine cmd, final Map env)
+            throws IOException;
+
+    /**
+     * Launches the given command in a new process, in the given working
+     * directory.
+     * 
+     * @param cmd
+     *            The command to execute
+     * @param env
+     *            The environment for the new process. If null, the environment
+     *            of the current process is used.
+     * @param workingDir
+     *            The directory to start the command in. If null, the current
+     *            directory is used
+     *
+     * @return the newly created process
+     * @throws IOException
+     *             if trying to change directory
+     */
+    Process exec(final CommandLine cmd, final Map env,
+            final File workingDir) throws IOException;
+
+
+    /**
+     * Checks whether <code>exitValue</code> signals a failure on the current
+     * system (OS specific).
+     * <p>
+     * <b>Note</b> that this method relies on the conventions of the OS, it
+     * will return false results if the application you are running doesn't
+     * follow these conventions. One notable exception is the Java VM provided
+     * by HP for OpenVMS - it will return 0 if successful (like on any other
+     * platform), but this signals a failure on OpenVMS. So if you execute a new
+     * Java VM on OpenVMS, you cannot trust this method.
+     * </p>
+     *
+     * @param exitValue the exit value (return code) to be checked
+     * @return <code>true</code> if <code>exitValue</code> signals a failure
+     */
+    boolean isFailure(final int exitValue);
+}
diff --git a/src/main/java/org/apache/commons/exec/launcher/CommandLauncherFactory.java b/src/main/java/org/apache/commons/exec/launcher/CommandLauncherFactory.java
new file mode 100644
index 0000000..320f179
--- /dev/null
+++ b/src/main/java/org/apache/commons/exec/launcher/CommandLauncherFactory.java
@@ -0,0 +1,48 @@
+/* 
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+package org.apache.commons.exec.launcher;
+
+import org.apache.commons.exec.OS;
+
+/**
+ * Builds a command launcher for the OS and JVM we are running under.
+ */
+public final class CommandLauncherFactory {
+
+    private CommandLauncherFactory() {
+    }
+
+    /**
+     * Factory method to create an appropriate launcher.
+     *
+     * @return the command launcher
+     */
+    public static CommandLauncher createVMLauncher() {
+        // Try using a JDK 1.3 launcher
+        CommandLauncher launcher;
+
+        if (OS.isFamilyOpenVms()) {
+            launcher = new VmsCommandLauncher();
+        } else {
+            launcher = new Java13CommandLauncher();
+        }
+
+        return launcher;
+    }
+}
diff --git a/src/main/java/org/apache/commons/exec/launcher/CommandLauncherImpl.java b/src/main/java/org/apache/commons/exec/launcher/CommandLauncherImpl.java
new file mode 100644
index 0000000..5b41a53
--- /dev/null
+++ b/src/main/java/org/apache/commons/exec/launcher/CommandLauncherImpl.java
@@ -0,0 +1,50 @@
+/* 
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+package org.apache.commons.exec.launcher;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Map;
+
+import org.apache.commons.exec.CommandLine;
+import org.apache.commons.exec.environment.EnvironmentUtils;
+
+/**
+ * A command launcher for a particular JVM/OS platform. This class is a general
+ * purpose command launcher which can only launch commands in the current
+ * working directory.
+ */
+public abstract class CommandLauncherImpl implements CommandLauncher {
+
+    public Process exec(final CommandLine cmd, final Map env)
+            throws IOException {
+        String[] envVar = EnvironmentUtils.toStrings(env);
+        return Runtime.getRuntime().exec(cmd.toStrings(), envVar);
+    }
+
+    public abstract Process exec(final CommandLine cmd, final Map env,
+            final File workingDir) throws IOException;
+
+    /** @see org.apache.commons.exec.launcher.CommandLauncher#isFailure(int) */    
+    public boolean isFailure(final int exitValue)
+    {
+        // non zero exit value signals failure
+        return exitValue != 0;
+    }
+}
diff --git a/src/main/java/org/apache/commons/exec/launcher/CommandLauncherProxy.java b/src/main/java/org/apache/commons/exec/launcher/CommandLauncherProxy.java
new file mode 100644
index 0000000..64f997a
--- /dev/null
+++ b/src/main/java/org/apache/commons/exec/launcher/CommandLauncherProxy.java
@@ -0,0 +1,53 @@
+/* 
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+package org.apache.commons.exec.launcher;
+
+import java.io.IOException;
+import java.util.Map;
+
+import org.apache.commons.exec.CommandLine;
+
+/**
+ * A command launcher that proxies another command launcher. Sub-classes
+ * override exec(args, env, workdir)
+ */
+public abstract class CommandLauncherProxy extends CommandLauncherImpl {
+
+    public CommandLauncherProxy(final CommandLauncher launcher) {
+        myLauncher = launcher;
+    }
+
+    private final CommandLauncher myLauncher;
+
+    /**
+     * Launches the given command in a new process. Delegates this method to the
+     * proxied launcher
+     * 
+     * @param cmd
+     *            the command line to execute as an array of strings
+     * @param env
+     *            the environment to set as an array of strings
+     * @throws IOException
+     *             forwarded from the exec method of the command launcher
+     */
+    public Process exec(final CommandLine cmd, final Map env)
+            throws IOException {
+        return myLauncher.exec(cmd, env);
+    }
+}
diff --git a/src/main/java/org/apache/commons/exec/launcher/Java13CommandLauncher.java b/src/main/java/org/apache/commons/exec/launcher/Java13CommandLauncher.java
new file mode 100644
index 0000000..dc76b10
--- /dev/null
+++ b/src/main/java/org/apache/commons/exec/launcher/Java13CommandLauncher.java
@@ -0,0 +1,61 @@
+/* 
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+package org.apache.commons.exec.launcher;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Map;
+
+import org.apache.commons.exec.CommandLine;
+import org.apache.commons.exec.environment.EnvironmentUtils;
+
+/**
+ * A command launcher for JDK/JRE 1.3 (and higher). Uses the built-in
+ * Runtime.exec() command
+ */
+public class Java13CommandLauncher extends CommandLauncherImpl {
+
+    /**
+     * Constructor
+     */
+    public Java13CommandLauncher() {
+	}
+
+	/**
+	 * Launches the given command in a new process, in the given working
+	 * directory
+	 * 
+	 * @param cmd
+	 *            the command line to execute as an array of strings
+	 * @param env
+	 *            the environment to set as an array of strings
+	 * @param workingDir
+	 *            the working directory where the command should run
+	 * @throws IOException
+	 *             probably forwarded from Runtime#exec
+	 */
+	public Process exec(final CommandLine cmd, final Map env,
+			final File workingDir) throws IOException {
+
+		String[] envVars = EnvironmentUtils.toStrings(env);
+
+		return Runtime.getRuntime().exec(cmd.toStrings(),
+                envVars, workingDir);
+	}
+}
diff --git a/src/main/java/org/apache/commons/exec/launcher/OS2CommandLauncher.java b/src/main/java/org/apache/commons/exec/launcher/OS2CommandLauncher.java
new file mode 100644
index 0000000..89ac3a8
--- /dev/null
+++ b/src/main/java/org/apache/commons/exec/launcher/OS2CommandLauncher.java
@@ -0,0 +1,68 @@
+/* 
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+package org.apache.commons.exec.launcher;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Map;
+
+import org.apache.commons.exec.CommandLine;
+
+/**
+ * A command launcher for OS/2 that uses 'cmd.exe' when launching commands in
+ * directories other than the current working directory.
+ * <p>
+ * Unlike Windows NT and friends, OS/2's cd doesn't support the /d switch to
+ * change drives and directories in one go.
+ * </p>
+ * Please not that this class is currently unused because the Java13CommandLauncher
+ * is used for 0S/2
+ */
+public class OS2CommandLauncher extends CommandLauncherProxy {
+
+    public OS2CommandLauncher(final CommandLauncher launcher) {
+        super(launcher);
+    }
+
+    /**
+     * Launches the given command in a new process, in the given working
+     * directory.
+     * 
+     * @param cmd
+     *            the command line to execute as an array of strings
+     * @param env
+     *            the environment to set as an array of strings
+     * @param workingDir
+     *            working directory where the command should run
+     * @throws IOException
+     *             forwarded from the exec method of the command launcher
+     */
+    public Process exec(final CommandLine cmd, final Map env,
+            final File workingDir) throws IOException {
+        if (workingDir == null) {
+            return exec(cmd, env);
+        }
+
+        CommandLine newCmd = new CommandLine("cmd");
+        newCmd.addArgument("/c");
+        newCmd.addArguments(cmd.toStrings());
+
+        return exec(newCmd, env);
+    }
+}
diff --git a/src/main/java/org/apache/commons/exec/launcher/VmsCommandLauncher.java b/src/main/java/org/apache/commons/exec/launcher/VmsCommandLauncher.java
new file mode 100644
index 0000000..f21f979
--- /dev/null
+++ b/src/main/java/org/apache/commons/exec/launcher/VmsCommandLauncher.java
@@ -0,0 +1,146 @@
+/* 
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+package org.apache.commons.exec.launcher;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import org.apache.commons.exec.CommandLine;
+import org.apache.commons.exec.util.StringUtils;
+
+/**
+ * A command launcher for VMS that writes the command to a temporary DCL script
+ * before launching commands. This is due to limitations of both the DCL
+ * interpreter and the Java VM implementation.
+ */
+public class VmsCommandLauncher extends Java13CommandLauncher {
+
+    /**
+     * Launches the given command in a new process.
+     */
+    public Process exec(final CommandLine cmd, final Map env)
+            throws IOException {
+        CommandLine vmsCmd = new CommandLine(
+                createCommandFile(cmd, env).getPath()
+        );
+
+        return super.exec(vmsCmd, env);
+    }
+
+    /**
+     * Launches the given command in a new process, in the given working
+     * directory. Note that under Java 1.3.1, 1.4.0 and 1.4.1 on VMS this method
+     * only works if <code>workingDir</code> is null or the logical
+     * JAVA$FORK_SUPPORT_CHDIR needs to be set to TRUE.
+     */
+    public Process exec(final CommandLine cmd, final Map env,
+            final File workingDir) throws IOException {
+        CommandLine vmsCmd = new CommandLine(
+                createCommandFile(cmd, env).getPath()
+        );
+
+        return super.exec(vmsCmd, env, workingDir);
+    }
+
+    /** @see org.apache.commons.exec.launcher.CommandLauncher#isFailure(int) */
+    public boolean isFailure( final int exitValue )
+    {
+        // even exit value signals failure
+        return (exitValue % 2) == 0;        
+    }
+
+    /*
+     * Writes the command into a temporary DCL script and returns the
+     * corresponding File object. The script will be deleted on exit.
+     */
+    private File createCommandFile(final CommandLine cmd, final Map env)
+            throws IOException {
+        File script = File.createTempFile("EXEC", ".TMP");
+        script.deleteOnExit();
+        PrintWriter out = null;
+        try {
+            out = new PrintWriter(new FileWriter(script.getAbsolutePath(),true));
+
+            // add the environment as global symbols for the DCL script
+            if (env != null) {
+                Set entries = env.entrySet();
+
+                for (Iterator iter = entries.iterator(); iter.hasNext();) {
+                    Entry entry = (Entry) iter.next();
+                    out.print("$ ");
+                    out.print(entry.getKey());
+                    out.print(" == "); // define as global symbol
+                    out.println('\"');
+                    String value = (String) entry.getValue();
+                    // Any embedded " values need to be doubled
+                    if (value.indexOf('\"') > 0){
+                        StringBuffer sb = new StringBuffer();
+                        for (int i = 0; i < value.length(); i++) {
+                            char c = value.charAt(i);
+                            if (c == '\"') {
+                                sb.append('\"');
+                            }
+                            sb.append(c);
+                        }
+                        value=sb.toString();
+                    }
+                    out.print(value);
+                    out.println('\"');
+                }
+            }
+
+            final String command = cmd.getExecutable();
+            if (cmd.isFile()){// We assume it is it a script file
+                out.print("$ @");
+                // This is a bit crude, but seems to work
+                String parts[] = StringUtils.split(command,"/");
+                out.print(parts[0]); // device
+                out.print(":[");
+                out.print(parts[1]); // top level directory
+                final int lastPart = parts.length-1;
+                for(int i=2; i< lastPart; i++){
+                    out.print(".");
+                    out.print(parts[i]);
+                }
+                out.print("]");
+                out.print(parts[lastPart]);
+            } else {
+                out.print("$ ");
+                out.print(command);                
+            }
+            String[] args = cmd.getArguments();
+            for (int i = 0; i < args.length; i++) {
+                out.println(" -");
+                out.print(args[i]);
+            }
+            out.println();
+        } finally {
+            if (out != null) {
+                out.close();
+            }
+        }
+        return script;
+    }
+}
diff --git a/src/main/java/org/apache/commons/exec/launcher/WinNTCommandLauncher.java b/src/main/java/org/apache/commons/exec/launcher/WinNTCommandLauncher.java
new file mode 100644
index 0000000..c6da3b0
--- /dev/null
+++ b/src/main/java/org/apache/commons/exec/launcher/WinNTCommandLauncher.java
@@ -0,0 +1,63 @@
+/* 
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+package org.apache.commons.exec.launcher;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Map;
+
+import org.apache.commons.exec.CommandLine;
+
+/**
+ * A command launcher for Windows XP/2000/NT that uses 'cmd.exe' when launching
+ * commands in directories other than the current working directory.
+ */
+public class WinNTCommandLauncher extends CommandLauncherProxy {
+    public WinNTCommandLauncher(final CommandLauncher launcher) {
+        super(launcher);
+    }
+
+    /**
+     * Launches the given command in a new process, in the given working
+     * directory.
+     * 
+     * @param cmd
+     *            the command line to execute as an array of strings
+     * @param env
+     *            the environment to set as an array of strings
+     * @param workingDir
+     *            working directory where the command should run
+     * @throws IOException
+     *             forwarded from the exec method of the command launcher
+     */
+    public Process exec(final CommandLine cmd, final Map env,
+            final File workingDir) throws IOException {
+        if (workingDir == null) {
+            return exec(cmd, env);
+        }
+
+        // Use cmd.exe to change to the specified directory before running
+        // the command
+        CommandLine newCmd = new CommandLine("cmd");
+        newCmd.addArgument("/c");
+        newCmd.addArguments(cmd.toStrings());
+
+        return exec(newCmd, env);
+    }
+}
diff --git a/src/main/java/org/apache/commons/exec/util/DebugUtils.java b/src/main/java/org/apache/commons/exec/util/DebugUtils.java
new file mode 100644
index 0000000..855f9e5
--- /dev/null
+++ b/src/main/java/org/apache/commons/exec/util/DebugUtils.java
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+package org.apache.commons.exec.util;
+
+/**
+ * Helper classes to provide debugging support.
+ *
+ * @author <a href="mailto:siegfried.goeschl at it20one.at">Siegfried Goeschl</a>
+ */
+public class DebugUtils
+{
+    /**
+     * System property to determine how to handle exceptions. When
+     * set to "false" we rethrow the otherwise silently catched
+     * exceptions found in the original code. The default value
+     * is "true"
+     */
+    public static final String COMMONS_EXEC_LENIENT = "org.apache.commons.exec.lenient";
+
+    /**
+     * System property to determine how to dump an exception. When
+     * set to "true" we print any exception to stderr. The default
+     * value is "false"
+     */
+    public static final String COMMONS_EXEC_DEBUG = "org.apache.commons.exec.debug";
+
+    /**
+     * Handle an exception based on the system properties.
+     *
+     * @param msg message describing the problem
+     * @param e an exception being handled
+     */
+    public static void handleException(String msg, Exception e) {
+
+        if(isDebugEnabled()) {
+            System.err.println(msg);
+            e.printStackTrace();
+        }
+
+        if(!isLenientEnabled()) {
+            if(e instanceof RuntimeException) {
+                throw (RuntimeException) e;
+            }
+            else {
+                // can't pass root cause since the constructor is not available on JDK 1.3
+                throw new RuntimeException(e.getMessage());
+            }
+        }
+    }
+
+    /**
+     * Determine if debugging is enabled based on the
+     * system property "COMMONS_EXEC_DEBUG".
+     *
+     * @return true if debug mode is enabled
+     */
+    public static boolean isDebugEnabled() {
+        return "true".equalsIgnoreCase(System.getProperty(COMMONS_EXEC_DEBUG, "false"));
+    }
+
+    /**
+     * Determine if lenient mode is enabled.
+     *
+     * @return true if lenient mode is enabled
+     */
+    public static boolean isLenientEnabled() {
+        return "true".equalsIgnoreCase(System.getProperty(COMMONS_EXEC_LENIENT, "true"));
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/exec/util/MapUtils.java b/src/main/java/org/apache/commons/exec/util/MapUtils.java
new file mode 100644
index 0000000..5e69785
--- /dev/null
+++ b/src/main/java/org/apache/commons/exec/util/MapUtils.java
@@ -0,0 +1,105 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+package org.apache.commons.exec.util;
+
+import java.util.Map;
+import java.util.HashMap;
+import java.util.Iterator;
+
+/**
+ * Helper classes to manipulate maps to pass substition map to the
+ * CommandLine. This class is not part of the public API and
+ * could change without warning.
+ *
+ * @author <a href="mailto:siegfried.goeschl at it20one.at">Siegfried Goeschl</a>
+ */
+public class MapUtils
+{
+    /**
+     * Clones a map.
+     *
+     * @param source the source map
+     * @return the clone of the source map
+     */
+    public static Map copy(Map source) {
+
+        if(source == null) {
+            return null;
+        }
+
+        Map result = new HashMap();
+        result.putAll(source);
+        return result;
+    }
+
+    /**
+     * Clones a map and prefixes the keys in the clone, e.g.
+     * for mapping "JAVA_HOME" to "env.JAVA_HOME" to simulate
+     * the behaviour of ANT.
+     *
+     * @param source the source map
+     * @param prefix the prefix used for all names
+     * @return the clone of the source map
+     */
+    public static Map prefix(Map source, String prefix) {
+
+        if(source == null) {
+            return null;
+        }
+
+        Map result = new HashMap();
+
+        Iterator iter = source.entrySet().iterator();
+
+        while(iter.hasNext()) {
+            Map.Entry entry = (Map.Entry) iter.next();
+            Object key = entry.getKey();
+            Object value = entry.getValue();
+            result.put(prefix + '.' + key.toString(), value);
+        }
+
+        return result;
+    }
+
+    /**
+     * Clones the lhs map and add all things from the
+     * rhs map.
+     *
+     * @param lhs the first map
+     * @param rhs the second map
+     * @return the merged map
+     */
+    public static Map merge(Map lhs, Map rhs) {
+
+        Map result = null;
+
+        if((lhs == null) || (lhs.size() == 0)) {
+            result = copy(rhs);
+        }
+        else if((rhs == null) || (rhs.size() == 0)) {
+            result = copy(lhs);
+        }
+        else {
+            result = copy(lhs);
+            result.putAll(rhs);
+        }
+        
+        return result;
+    }
+}
diff --git a/src/main/java/org/apache/commons/exec/util/StringUtils.java b/src/main/java/org/apache/commons/exec/util/StringUtils.java
new file mode 100644
index 0000000..f84691b
--- /dev/null
+++ b/src/main/java/org/apache/commons/exec/util/StringUtils.java
@@ -0,0 +1,258 @@
+/* 
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+package org.apache.commons.exec.util;
+
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.StringTokenizer;
+import java.io.File;
+
+/**
+ * Supplement of commons-lang, the stringSubstitution() was in a simpler
+ * implementation available in an older commons-lang implementation.
+ *
+ * This class is not part of the public API and could change without
+ * warning.
+ *
+ * @author <a href="mailto:siegfried.goeschl at it20one.at">Siegfried Goeschl</a>
+ */
+public class StringUtils {
+
+    private static final String SINGLE_QUOTE = "\'";
+    private static final String DOUBLE_QUOTE = "\"";
+    private static final char SLASH_CHAR = '/';
+    private static final char BACKSLASH_CHAR = '\\';
+
+    /**
+     * Perform a series of substitutions. The substitutions
+     * are performed by replacing ${variable} in the target
+     * string with the value of provided by the key "variable"
+     * in the provided hash table.
+     * <p/><p/>
+     * A key consists of the following characters:
+     * <ul>
+     *   <li>letter
+     *   <li>digit
+     *   <li>dot character
+     *   <li>hyphen character
+     *   <li>plus character
+     *   <li>underscore character
+     * </ul>
+     *
+     * @param argStr    the argument string to be processed
+     * @param vars      name/value pairs used for substitution
+     * @param isLenient ignore a key not found in vars or throw a RuntimeException?
+     * @return String target string with replacements.
+     */
+    public static StringBuffer stringSubstitution(String argStr, Map vars, boolean isLenient) {
+
+        StringBuffer argBuf = new StringBuffer();
+
+        if (argStr == null || argStr.length() == 0) {
+            return argBuf;
+        }
+
+        if (vars == null || vars.size() == 0) {
+            return argBuf.append(argStr);
+        }
+
+        int argStrLength = argStr.length();
+
+        for (int cIdx = 0; cIdx < argStrLength;) {
+
+            char ch = argStr.charAt(cIdx);
+            char del = ' ';
+
+            switch (ch) {
+
+                case '$':
+                    StringBuffer nameBuf = new StringBuffer();
+                    del = argStr.charAt(cIdx + 1);
+                    if (del == '{') {
+                        cIdx++;
+
+                        for (++cIdx; cIdx < argStr.length(); ++cIdx) {
+                            ch = argStr.charAt(cIdx);
+                            if (ch == '_' || ch == '.' || ch == '-' || ch == '+' || Character.isLetterOrDigit(ch))
+                                nameBuf.append(ch);
+                            else
+                                break;
+                        }
+
+                        if (nameBuf.length() >= 0) {
+
+                            String value;
+                            Object temp = vars.get(nameBuf.toString());
+
+                            if(temp instanceof File) {
+                                // for a file we have to fix the separator chars to allow
+                                // cross-platform compatibility
+                                value = fixFileSeparatorChar(((File) temp).getAbsolutePath());
+                            }
+                            else {
+                                value = (temp != null ? temp.toString() : null);    
+                            }
+
+                            if (value != null) {
+                                argBuf.append(value);
+                            } else {
+                                if (isLenient) {
+                                    // just append the unresolved variable declaration
+                                    argBuf.append("${").append(nameBuf.toString()).append("}");
+                                } else {
+                                    // complain that no variable was found
+                                    throw new RuntimeException("No value found for : " + nameBuf);
+                                }
+                            }
+
+                            del = argStr.charAt(cIdx);
+
+                            if (del != '}') {
+                                throw new RuntimeException("Delimiter not found for : " + nameBuf);
+                            }
+                        }
+
+                        cIdx++;
+                    }
+                    else {
+                        argBuf.append(ch);
+                        ++cIdx;
+                    }
+
+                    break;
+
+                default:
+                    argBuf.append(ch);
+                    ++cIdx;
+                    break;
+            }
+        }
+
+        return argBuf;
+    }
+
+    /**
+     * Split a string into an array of strings based
+     * on a separator.
+     *
+     * @param input     what to split
+     * @param splitChar what to split on
+     * @return the array of strings
+     */
+    public static String[] split(String input, String splitChar) {
+        StringTokenizer tokens = new StringTokenizer(input, splitChar);
+        List strList = new ArrayList();
+        while (tokens.hasMoreTokens()) {
+            strList.add(tokens.nextToken());
+        }
+        return (String[]) strList.toArray(new String[strList.size()]);
+    }
+
+    /**
+     * Fixes the file separator char for the target platform
+     * using the following replacement.
+     * 
+     * <ul>
+     *  <li> '/' ==>  File.separatorChar
+     *  <li> '\\' ==>  File.separatorChar
+     * </ul>
+     *
+     * @param arg the argument to fix
+     * @return the transformed argument 
+     */
+    public static String fixFileSeparatorChar(String arg) {
+        return arg.replace(SLASH_CHAR, File.separatorChar).replace(
+                BACKSLASH_CHAR, File.separatorChar);
+    }
+
+    /**
+     * Concatenates an array of string using a separator.
+     *
+     * @param strings the strings to concatenate
+     * @param separator the separator between two strings
+     * @return the concatenated strings
+     */
+    public static String toString(String[] strings, String separator) {
+        StringBuffer sb = new StringBuffer();
+        for (int i = 0; i < strings.length; i++) {
+            if (i > 0) {
+                sb.append(separator);
+            }
+            sb.append(strings[i]);
+        }
+        return sb.toString();
+    }
+
+    /**
+     * Put quotes around the given String if necessary.
+     * <p>
+     * If the argument doesn't include spaces or quotes, return it as is. If it
+     * contains double quotes, use single quotes - else surround the argument by
+     * double quotes.
+     * </p>
+     *
+     * @param argument the argument to be quoted
+     * @return the quoted argument
+     * @throws IllegalArgumentException If argument contains both types of quotes
+     */
+    public static String quoteArgument(final String argument) {
+
+        String cleanedArgument = argument.trim();
+
+        // strip the quotes from both ends
+        while(cleanedArgument.startsWith(SINGLE_QUOTE) || cleanedArgument.startsWith(DOUBLE_QUOTE)) {
+            cleanedArgument = cleanedArgument.substring(1);
+        }
+        
+        while(cleanedArgument.endsWith(SINGLE_QUOTE) || cleanedArgument.endsWith(DOUBLE_QUOTE)) {
+            cleanedArgument = cleanedArgument.substring(0, cleanedArgument.length() - 1);
+        }
+
+        final StringBuffer buf = new StringBuffer();
+        if (cleanedArgument.indexOf(DOUBLE_QUOTE) > -1) {
+            if (cleanedArgument.indexOf(SINGLE_QUOTE) > -1) {
+                throw new IllegalArgumentException(
+                        "Can't handle single and double quotes in same argument");
+            } else {
+                return buf.append(SINGLE_QUOTE).append(cleanedArgument).append(
+                        SINGLE_QUOTE).toString();
+            }
+        } else if (cleanedArgument.indexOf(SINGLE_QUOTE) > -1
+                || cleanedArgument.indexOf(" ") > -1) {
+            return buf.append(DOUBLE_QUOTE).append(cleanedArgument).append(
+                    DOUBLE_QUOTE).toString();
+        } else {
+            return cleanedArgument;
+        }
+    }
+
+    /**
+     * Determines if this is a quoted argument - either single or
+     * double quoted.
+     *
+     * @param argument the argument to check
+     * @return true when the argument is quoted
+     */
+    public static boolean isQuoted(final String argument) {
+        return ( argument.startsWith( SINGLE_QUOTE ) && argument.endsWith( SINGLE_QUOTE ) ) ||
+            ( argument.startsWith( DOUBLE_QUOTE ) && argument.endsWith( DOUBLE_QUOTE ) );
+    }
+}
\ No newline at end of file
diff --git a/src/site/apt/commandline.apt b/src/site/apt/commandline.apt
new file mode 100644
index 0000000..8136ec3
--- /dev/null
+++ b/src/site/apt/commandline.apt
@@ -0,0 +1,150 @@
+~~
+~~ Licensed to the Apache Software Foundation (ASF) under one or more
+~~  contributor license agreements.  See the NOTICE file distributed with
+~~  this work for additional information regarding copyright ownership.
+~~  The ASF licenses this file to You under the Apache License, Version 2.0
+~~  (the "License"); you may not use this file except in compliance with
+~~  the License.  You may obtain a copy of the License at
+~~
+~~      http://www.apache.org/licenses/LICENSE-2.0
+~~
+~~  Unless required by applicable law or agreed to in writing, software
+~~  distributed under the License is distributed on an "AS IS" BASIS,
+~~  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+~~  See the License for the specific language governing permissions and
+~~  limitations under the License.
+~~
+~~
+
+ --------
+Apache Commons Exec - Building the command line
+ --------
+ --------
+20 August 2010
+ --------
+
+Building the command line
+
+You have two ways to create the command line to be executed
+
+  * parsing the entire command line string
+  
+  * building the command line incrementally
+
+* General Considerations
+
+  No matter which approach you are using commons-exec does change your command
+  line arguments in the following two cases
+
+    * when the executable contains forward or backward slashes
+
+    * when a command line argument contains an unquoted string 
+
+  The following executable arguments
+
+----------------------------------------
+./bin/vim
+----------------------------------------
+
+  will be translated under Windows to
+
+----------------------------------------
+.\\bin\\vim
+----------------------------------------
+
+* Parsing the entire command line string
+
+  Parsing the command line string is easy to use but you might run into
+  problems when tackling complex scenarios. Therefore this functionality
+  was deprecated in the {{{http://ant.apache.org/manual/Tasks/exec.html}Ant Exec task}}.
+
+  Let's have a look at few examples you would like to stick to parsing entire command
+  line strings
+
+** Spaces in command line arguments
+
+  Here we would like to invoke a batch file which contains spaces in the path
+
+----------------------------------------
+cmd.exe /C c:\was51\Web Sphere\AppServer\bin\versionInfo.bat
+----------------------------------------
+
+  Due to the space in the file name we have to quote the file name either with
+  single or double quotes otherwise it falls apart into two command line
+  arguments <c:\was51\Web> and <Sphere\AppServer\bin\versionInfo.bat>.
+
+----------------------------------------
+String line = "cmd.exe /C 'c:\\was51\\Web Sphere\\AppServer\\bin\\versionInfo.bat'";
+----------------------------------------
+
+* Building the Command Line Incrementally
+
+  This is the recommended approach and caters also for pre-quoted command
+  line argument.
+
+** A simple example
+
+  Now we would like to build the following command line
+
+----------------------------------------
+runMemorySud.cmd 10 30 -XX:+UseParallelGC -XX:ParallelGCThreads=2
+----------------------------------------
+
+  using the following code snippet
+  
+----------------------------------------
+CommandLine cmdl = new CommandLine("runMemorySud.cmd");
+cmdl.addArgument("10");
+cmdl.addArgument("30");
+cmdl.addArgument("-XX:+UseParallelGC");
+cmdl.addArgument("-XX:ParallelGCThreads=2");
+----------------------------------------
+
+** A complex example
+
+  Now let's have a look at the following command line found somewhere in the
+  internet
+
+----------------------------------------
+dotnetfx.exe /q:a /c:"install.exe /l ""\Documents and Settings\myusername\Local Settings\Temp\netfx.log"" /q"
+----------------------------------------
+
+  The following code snippet builds the command line using pre-quoted
+  arguments and variable expansion
+
+----------------------------------------
+File file = new File("/Documents and Settings/myusername/Local Settings/Temp/netfx.log");
+Map map = new HashMap();
+map.put("FILE", file);
+
+cmdl = new CommandLine("dotnetfx.exe");
+cmdl.setSubstitutionMap(map);
+cmdl.addArgument("/q:a", false);
+cmdl.addArgument("/c:\"install.exe /l \"\"${FILE}\"\" /q\"", false);
+----------------------------------------
+
+* For the Desperate
+
+  When crafting a command line it would be really helpful to see what
+  happens to your command line arguments. The following scripts can be
+  invoked to print your command line arguments for Unix
+
+----------------------------------------
+while [ $# -gt 0 ]
+do
+    echo "$1"
+    shift
+done
+----------------------------------------
+
+  and for Windows
+
+----------------------------------------
+:Loop
+IF [%1]==[] GOTO Continue
+    @ECHO "%1"
+SHIFT
+GOTO Loop
+:Continue
+----------------------------------------
+
diff --git a/src/site/apt/index.apt b/src/site/apt/index.apt
new file mode 100644
index 0000000..4bb01f4
--- /dev/null
+++ b/src/site/apt/index.apt
@@ -0,0 +1,57 @@
+~~ 
+~~ Licensed to the Apache Software Foundation (ASF) under one or more
+~~  contributor license agreements.  See the NOTICE file distributed with
+~~  this work for additional information regarding copyright ownership.
+~~  The ASF licenses this file to You under the Apache License, Version 2.0
+~~  (the "License"); you may not use this file except in compliance with
+~~  the License.  You may obtain a copy of the License at
+~~
+~~      http://www.apache.org/licenses/LICENSE-2.0
+~~
+~~  Unless required by applicable law or agreed to in writing, software
+~~  distributed under the License is distributed on an "AS IS" BASIS,
+~~  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+~~  See the License for the specific language governing permissions and
+~~  limitations under the License.
+~~
+~~
+
+ --------
+Apache Commons Exec
+ --------
+ --------
+12 November 2008
+ --------
+
+Jakarta Commons Exec
+
+* Rationale
+
+  Executing external processes from Java is a well-known problem area. It is inheriently platform dependent and requires
+  the developer to know and test for platform specific behaviors, for example using cmd.exe on Windows or limited buffer
+  sizes causing deadlocks. The JRE support for this is very limited, albeit better with the new Java SE 1.5
+  ProcessBuilder class.
+
+  Reliably executing external processes can also require knowledge of the environment variables before or after the
+  command is executed. In J2SE 1.1-1.4 there is not support for this, since the method, <<<System.getenv()>>>, for
+  retriving environment variables is deprecated.
+
+  There are currently several different libraries that for their own purposes have implemented frameworks around
+  <<<Runtime.exec()>>> to handle the various issues outlined above. The proposed project should aim at coordinating and
+  learning from these initatives to create and maintain a simple, reusable and well-tested package. Since some of the
+  more problematic platforms are not readily available, it is my hope that the broad Apache community can be a
+  great help.
+
+* Scope of the package
+
+  The package shall create and maintain a process execution package written in the Java language to be distributed
+  under the ASF license. The Java code might also be complemented with scripts (e.g. Perl scripts) to fully enable
+  execution on some operating systems. The package should aim for supporting a wide range of operating systems while
+  still having a consistent API for all platforms.
+
+* Releases 
+   
+  The latest version v1.1, is JDK 1.3 compatible - 
+  {{{http://commons.apache.org/downloads/download_exec.cgi}Download now!}}. 
+
+  For previous releases, see the {{{http://archive.apache.org/dist/commons/exec/}Apache Archive}}. 
\ No newline at end of file
diff --git a/src/site/apt/technical.apt b/src/site/apt/technical.apt
new file mode 100644
index 0000000..a9093cb
--- /dev/null
+++ b/src/site/apt/technical.apt
@@ -0,0 +1,36 @@
+~~ 
+~~ Licensed to the Apache Software Foundation (ASF) under one or more
+~~  contributor license agreements.  See the NOTICE file distributed with
+~~  this work for additional information regarding copyright ownership.
+~~  The ASF licenses this file to You under the Apache License, Version 2.0
+~~  (the "License"); you may not use this file except in compliance with
+~~  the License.  You may obtain a copy of the License at
+~~
+~~      http://www.apache.org/licenses/LICENSE-2.0
+~~
+~~  Unless required by applicable law or agreed to in writing, software
+~~  distributed under the License is distributed on an "AS IS" BASIS,
+~~  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+~~  See the License for the specific language governing permissions and
+~~  limitations under the License.
+~~
+~~
+
+ --------
+Apache Commons Exec Technical Details
+ --------
+ --------
+16 August 2010
+ --------
+
+Apache Commons Exec
+
+* An Implementation Overview
+
+  Looking at commons-exec can be a bit daunting the first time therefore an
+  overview of the implementation helps.
+
+** DefaultExecutor
+  The main class is <<DefaultExecutor>> where you defined the command line
+  of the sub-process to be executed.
+
diff --git a/src/site/apt/tutorial.apt b/src/site/apt/tutorial.apt
new file mode 100644
index 0000000..3ec07d9
--- /dev/null
+++ b/src/site/apt/tutorial.apt
@@ -0,0 +1,179 @@
+~~ 
+~~ Licensed to the Apache Software Foundation (ASF) under one or more
+~~  contributor license agreements.  See the NOTICE file distributed with
+~~  this work for additional information regarding copyright ownership.
+~~  The ASF licenses this file to You under the Apache License, Version 2.0
+~~  (the "License"); you may not use this file except in compliance with
+~~  the License.  You may obtain a copy of the License at
+~~
+~~      http://www.apache.org/licenses/LICENSE-2.0
+~~
+~~  Unless required by applicable law or agreed to in writing, software
+~~  distributed under the License is distributed on an "AS IS" BASIS,
+~~  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+~~  See the License for the specific language governing permissions and
+~~  limitations under the License.
+~~
+~~
+
+ --------
+Apache Commons Exec Tutorial
+ --------
+ --------
+15 September 2010
+ --------
+
+Apache Commons Exec
+
+* The First Encounter
+
+  At this point we can safely assume that you would like to start some subprocesses from within your
+  Java application and you spent some time here to do it properly. You look at Commons Exec and think
+  "Wow - calling Runtime.exec() is easy and the Apache folks are wasting their and my time
+  with tons of code".
+  
+  Well, we learned it the hard way (in my case more than once) that using plain Runtime.exec() can be 
+  a painful experience. Therefore you are invited to delve into commons-exec and have a look at the
+  hard lessons the easy way ...
+  
+* Taming Your First Process
+
+  Let's look at a real example - we would like to print PDF documents from within your Java
+  application. After googling a while it turns out to be a minor headache and using Adobe Acrobat
+  seems to be a good option.
+
+  The command line under Windows should look like "AcroRd32.exe /p /h file" assuming that the
+  Acrobat Reader is found in the path.
+
++----------------------------------------------------------------------------
+String line = "AcroRd32.exe /p /h " + file.getAbsolutePath();
+CommandLine commandLine = CommandLine.parse(line);
+DefaultExecutor executor = new DefaultExecutor();
+int exitValue = executor.execute(commandLine);
++----------------------------------------------------------------------------
+
+  You successfully printed your first PDF document but at the end an exception is thrown - what
+  happend? Oops, Acrobat Reader returned an exit value of '1' on success which is usually
+  considered as an execution failure. So we have to tweak our code to fix this odd behaviour -
+  we define the exit value of "1" to be considered as successful execution.
+
++----------------------------------------------------------------------------
+String line = "AcroRd32.exe /p /h " + file.getAbsolutePath();
+CommandLine commandLine = CommandLine.parse(line);
+DefaultExecutor executor = new DefaultExecutor();
+executor.setExitValue(1);
+int exitValue = executor.execute(commandLine);
++----------------------------------------------------------------------------
+
+* To Watchdog Or Not To Watchdog
+
+  You happily printed for a while but now your application blocks - your printing subprocess
+  hangs for some obvious or not so obvious reason. Starting is easy but what to do with a run-away
+  Acrobat Reader telling you that printing failed due to a lack of paper?! Luckily commons-exec
+  provides a watchdog which does the work for you. Here is the improved code which kills a
+  run-away process after sixty seconds.
+
++----------------------------------------------------------------------------
+String line = "AcroRd32.exe /p /h " + file.getAbsolutePath();
+CommandLine commandLine = CommandLine.parse(line);
+DefaultExecutor executor = new DefaultExecutor();
+executor.setExitValue(1);
+ExecuteWatchdog watchdog = new ExecuteWatchdog(60000);
+executor.setWatchdog(watchdog);
+int exitValue = executor.execute(commandLine);
++----------------------------------------------------------------------------
+
+* Quoting Is Your Friend
+
+  Well, the code worked for quite a while until a new customer complained that
+  no documents are printed. It took half a day to find out that the following file
+  'C:\\Document And Settings\\documents\\432432.pdf' could not be printed. Due to the
+  spaces and without further quoting the command line fell literally apart into
+  the following snippet
+
++----------------------------------------------------------------------------
+> AcroRd32.exe /p /h C:\Document And Settings\documents\432432.pdf
++----------------------------------------------------------------------------
+
+  As a quick fix we added double quotes which tells commons-exec to handle
+  the file as a single command line argument instead of splitting it into
+  parts.
+  
++----------------------------------------------------------------------------
+String line = "AcroRd32.exe /p /h \"" + file.getAbsolutePath() + "\"";
+CommandLine commandLine = CommandLine.parse(line);
+DefaultExecutor executor = new DefaultExecutor();
+executor.setExitValue(1);
+ExecuteWatchdog watchdog = new ExecuteWatchdog(60000);
+executor.setWatchdog(watchdog);
+int exitValue = executor.execute(commandLine);
++----------------------------------------------------------------------------
+
+* Build the Command Line Incrementally
+
+  The previous problem stems from the fact that commons-exec tried to split
+  a single command line string into a string array considering single and
+  double quotes. At the end of the day this is error-prone so we recommend
+  building the command line incrementally - according to the same reasoning
+  the Ant documentation does not recommend passing a single command line to
+  the <exec> target (see deprecated command attribute for 
+  {{{http://ant.apache.org/manual/CoreTasks/exec.html}exec}} task)
+  
++----------------------------------------------------------------------------
+Map map = new HashMap();
+map.put("file", new File("the_document_you_would_like_to_print"));
+CommandLine commandLine = CommandLine.parse("AcroRd32.exe");
+commandLine.addArgument("/p");
+commandLine.addArgument("/h");
+commandLine.addArgument("${file}");
+commandLine.setSubstitutionMap(map);
+DefaultExecutor executor = new DefaultExecutor();
+executor.setExitValue(1);
+ExecuteWatchdog watchdog = new ExecuteWatchdog(60000);
+executor.setWatchdog(watchdog);
+int exitValue = executor.execute(commandLine);
++----------------------------------------------------------------------------
+
+  Please note that we are passing an 'java.io.File' instance for expanding
+  the command line arguments - this allows to convert the resulting file name
+  on the fly to match your OS.
+
+* Unblock Your Execution
+
+  Up to now we have a working example but it would not be good enough for
+  production - because it is blocking.
+
+  Your worker thread will block until the print process has finished or
+  was killed by the watchdog. Therefore  executing the print job
+  asynchronously will do the trick. In this example we create an instance
+  of 'ExecuteResultHandler' and pass it to the 'Executor' instance in order
+  to execute the process asynchronously. The 'resultHandler' picks up any
+  offending exception or the process exit code.
+
++----------------------------------------------------------------------------
+CommandLine commandLine = CommandLine.parse(this.acroRd32Script.getAbsolutePath());
+commandLine.addArgument("/p");
+commandLine.addArgument("/h");
+commandLine.addArgument("${file}");
+HashMap map = new HashMap();
+map.put("file", "./pom.xml");
+commandLine.setSubstitutionMap(map);
+
+DefaultExecuteResultHandler resultHandler = new DefaultExecuteResultHandler();
+
+ExecuteWatchdog watchdog = new ExecuteWatchdog(60*1000);
+Executor executor = new DefaultExecutor();
+executor.setExitValue(1);
+executor.setStreamHandler(new PumpStreamHandler());
+executor.setWatchdog(watchdog);
+executor.execute(commandLine, resultHandler);
+
+// some time later the result handler callback was invoked so we
+// can safely request the exit value
+int exitValue = resultHandler.waitFor();
++----------------------------------------------------------------------------
+  
+* Get Your Hands Dirty
+
+  A tutorial is nice but executing the tutorial code is even nicer. You find
+  the ready-to-run tutorial under {{{http://commons.apache.org/exec/xref-test/org/apache/commons/exec/TutorialTest.html}src/test/java/org/apache/commons/exec/TutorialTest.java}}.
diff --git a/src/site/fml/faq.fml b/src/site/fml/faq.fml
new file mode 100644
index 0000000..a20551d
--- /dev/null
+++ b/src/site/fml/faq.fml
@@ -0,0 +1,84 @@
+<?xml version="1.0"?>
+
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+
+<faqs title="Frequently Asked Questions">
+  <part id="general">
+    <title>General</title>
+    <faq id="maturity">
+      <question>How mature is it?</question>
+      <answer>
+        <p>The code was ported from Ant and extensively tested on various platforms. So there is no
+          reason not to use it and it is very likely better than any home-grown library. </p>
+      </answer>
+    </faq>
+    <faq id="complex-quoting">
+      <question>How do I create a complex command line using single and double quotes?</question>
+      <answer>
+        <p> It is recommended to use CommandLine.addArgument() instead of CommandLine.parse(). Using
+          CommandLine.parse() the implementation tries to figure out the correct quoting using your
+          arguments and file names containing spaces. With CommandLine.addArgument() you can
+          enable/disable quoting depending on your requirements. Having said that this is the
+          recommended approach using Ant anyway. </p>
+      </answer>
+    </faq>
+    <faq id="killing-child-processes">
+      <question>Are child processes automatically killed?</question>
+      <answer>
+        <p> This functionality is largely depend on the operating system - on Unix it works
+        mostly and under Windows not at all (see <a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4770092">
+        Bug 4770092</a>). In terms of stability and cross-platform support try to start your applications directly and
+        avoid various wrapper scripts.</p>
+      </answer>
+    </faq>
+      <faq id="gcj-support">
+        <question>Does commons-exec support java-gcj?</question>
+        <answer>
+          <p> Well - one out of 55 regression tests fails. The
+            EnvironmentUtilTest.testGetProcEnvironment() test fails because it detects no environment
+            variables for the current process but there must be one since we require JAVA_HOME to be
+            set. Not sure if this is a plain bug in java-gcj-4.2.1 or requires a work around in
+            commons-exec</p>
+        </answer>
+      </faq>
+
+    <faq id="environment-testing">
+      <question>How to test commons-exec on my environment?</question>
+      <answer>
+        <p> Assuming that you have an environment not listed on the <a href="./testmatrix.html">test
+          matrix</a> and want to make sure that everything works fine you can run easily run the
+          regression tests. Make a SVN checkout and run 'ant test-distribution' to create the test
+          distribution in './target'. On a production box downloading the ready-to-run test 
+          distribution might be even more handy (<a href="http://people.apache.org/~sgoeschl/download/commons-exec/">
+          http://people.apache.org/~sgoeschl/download/commons-exec/</a>). Unpack the 'zip' or 
+          'tar.gz' file and start the tests. Independent from the result we very much appreciate 
+          your feedback ... :-)</p>
+      </answer>
+      <question>Why is the regression test broken on my Unix box</question>
+      <answer>
+        <p> Please check if the shell scripts under "./src/test/script" are executable - assuming 
+          that they are not executable the "testExecute*" and "testExecuteAsync*" test will 
+          fail. We try very hard to keep the executable bit but they have somehow the tendency 
+          to to be lost ... 
+        </p>
+      </answer>
+    </faq>
+  </part>
+</faqs>
diff --git a/src/site/site.xml b/src/site/site.xml
new file mode 100644
index 0000000..083a26f
--- /dev/null
+++ b/src/site/site.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements.  See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership.  The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License.  You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied.  See the License for the
+ specific language governing permissions and limitations
+ under the License.
+-->
+<project name="Commons Exec">
+  <body>
+
+    <menu name="Commons Exec">
+      <item name="Overview" href="/index.html" />
+      <item name="Tutorial" href="/tutorial.html" />
+      <item name="Command Line" href="/commandline.html" />        
+      <item name="FAQ" href="faq.html"/>
+      <item name="Test Matrix" href="/testmatrix.html" />
+      <item name="Download" href="/download_exec.cgi"/>    
+    </menu>
+    
+    <menu name="Development">
+      <item name="Mailing Lists"           href="/mail-lists.html"/>
+      <item name="Issue Tracking"          href="/issue-tracking.html"/>
+      <item name="Team"                    href="/team-list.html"/>
+      <item name="Source Repository"       href="/source-repository.html"/>
+      <item name="Javadoc (latest)"        href="/apidocs/index.html"/>
+    </menu>
+
+  </body>
+</project>
diff --git a/src/site/xdoc/download_exec.xml b/src/site/xdoc/download_exec.xml
new file mode 100644
index 0000000..6255b11
--- /dev/null
+++ b/src/site/xdoc/download_exec.xml
@@ -0,0 +1,138 @@
+<?xml version="1.0"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<!--
+ +======================================================================+
+ |****                                                              ****|
+ |****      THIS FILE IS GENERATED BY THE COMMONS BUILD PLUGIN      ****|
+ |****                    DO NOT EDIT DIRECTLY                      ****|
+ |****                                                              ****|
+ +======================================================================+
+ | TEMPLATE FILE: download-page-template.xml                            |
+ | commons-build-plugin/trunk/src/main/resources/commons-xdoc-templates |
+ +======================================================================+
+ |                                                                      |
+ | 1) Re-generate using: mvn commons:download-page                      |
+ |                                                                      |
+ | 2) Set the following properties in the component's pom:              |
+ |    - commons.componentid (required, alphabetic, lower case)          |
+ |    - commons.release.version (required)                              |
+ |    - commons.binary.suffix (optional)                                |
+ |      (defaults to "-bin", set to "" for pre-maven2 releases)         |
+ |                                                                      |
+ | 3) Example Properties                                                |
+ |                                                                      |
+ |  <properties>                                                        |
+ |    <commons.componentid>math</commons.componentid>                   |
+ |    <commons.release.version>1.2</commons.release.version>            |
+ |  </properties>                                                       |
+ |                                                                      |
+ +======================================================================+
+-->
+<document>
+  <properties>
+    <title>Download Commons Exec</title>
+    <author email="dev at commons.apache.org">Commons Documentation Team</author>
+  </properties>
+  <body>
+    <section name="Download Commons Exec">
+    <subsection name="Using a Mirror">
+      <p>
+        We recommend you use a mirror to download our release
+        builds, but you <strong>must</strong> verify the integrity of
+        the downloaded files using signatures downloaded from our main 
+        distribution directories. Recent releases (48 hours) may not yet
+        be available from the mirrors.
+      </p>
+
+      <p>
+        You are currently using <b>[preferred]</b>.  If you
+        encounter a problem with this mirror, please select another
+        mirror.  If all mirrors are failing, there are <i>backup</i>
+        mirrors (at the end of the mirrors list) that should be
+        available.
+        <br></br>
+        [if-any logo]<a href="[link]"><img align="right" src="[logo]" border="0"></img></a>[end]
+      </p>
+
+      <form action="[location]" method="get" id="SelectMirror">
+        <p>
+          Other mirrors: 
+          <select name="Preferred">
+          [if-any http]
+            [for http]<option value="[http]">[http]</option>[end]
+          [end]
+          [if-any ftp]
+            [for ftp]<option value="[ftp]">[ftp]</option>[end]
+          [end]
+          [if-any backup]
+            [for backup]<option value="[backup]">[backup] (backup)</option>[end]
+          [end]
+          </select>
+          <input type="submit" value="Change"></input>
+        </p>
+      </form>
+
+      <p>
+        The <a href="http://www.apache.org/dist/commons/KEYS">KEYS</a>
+        link links to the code signing keys used to sign the product.
+        The <code>PGP</code> link downloads the OpenPGP compatible signature from our main site. 
+        The <code>MD5</code> link downloads the checksum from the main site.
+      </p>
+    </subsection>
+    </section>
+    <section name="Commons Exec 1.1 ">
+      <subsection name="Binaries">
+        <table>
+          <tr>
+              <td><a href="[preferred]/commons/exec/binaries/commons-exec-1.1-bin.tar.gz">commons-exec-1.1-bin.tar.gz</a></td>
+              <td><a href="http://www.apache.org/dist/commons/exec/binaries/commons-exec-1.1-bin.tar.gz.md5">md5</a></td>
+              <td><a href="http://www.apache.org/dist/commons/exec/binaries/commons-exec-1.1-bin.tar.gz.asc">pgp</a></td>
+          </tr>
+          <tr>
+              <td><a href="[preferred]/commons/exec/binaries/commons-exec-1.1-bin.zip">commons-exec-1.1-bin.zip</a></td>
+              <td><a href="http://www.apache.org/dist/commons/exec/binaries/commons-exec-1.1-bin.zip.md5">md5</a></td>
+              <td><a href="http://www.apache.org/dist/commons/exec/binaries/commons-exec-1.1-bin.zip.asc">pgp</a></td>
+          </tr>
+        </table>
+      </subsection>
+      <subsection name="Source">
+        <table>
+          <tr>
+              <td><a href="[preferred]/commons/exec/source/commons-exec-1.1-src.tar.gz">commons-exec-1.1-src.tar.gz</a></td>
+              <td><a href="http://www.apache.org/dist/commons/exec/source/commons-exec-1.1-src.tar.gz.md5">md5</a></td>
+              <td><a href="http://www.apache.org/dist/commons/exec/source/commons-exec-1.1-src.tar.gz.asc">pgp</a></td>
+          </tr>
+          <tr>
+              <td><a href="[preferred]/commons/exec/source/commons-exec-1.1-src.zip">commons-exec-1.1-src.zip</a></td>
+              <td><a href="http://www.apache.org/dist/commons/exec/source/commons-exec-1.1-src.zip.md5">md5</a></td>
+              <td><a href="http://www.apache.org/dist/commons/exec/source/commons-exec-1.1-src.zip.asc">pgp</a></td>
+          </tr>
+        </table>
+      </subsection>
+    </section>
+    <section name="Archives">
+        <p>
+          Older releases can be obtained from the archives.
+        </p>
+        <ul>
+          <li class="download"><a href="[preferred]/commons/exec/">browse download area</a></li>
+          <li><a href="http://archive.apache.org/dist/commons/exec/">archives...</a></li>
+        </ul>
+    </section>
+  </body>
+</document>
diff --git a/src/site/xdoc/issue-tracking.xml b/src/site/xdoc/issue-tracking.xml
new file mode 100644
index 0000000..4c48d98
--- /dev/null
+++ b/src/site/xdoc/issue-tracking.xml
@@ -0,0 +1,102 @@
+<?xml version="1.0"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<!--
+ +======================================================================+
+ |****                                                              ****|
+ |****      THIS FILE IS GENERATED BY THE COMMONS BUILD PLUGIN      ****|
+ |****                    DO NOT EDIT DIRECTLY                      ****|
+ |****                                                              ****|
+ +======================================================================+
+ | TEMPLATE FILE: issue-tracking-template.xml                           |
+ | commons-build-plugin/trunk/src/main/resources/commons-xdoc-templates |
+ +======================================================================+
+ |                                                                      |
+ | 1) Re-generate using: mvn commons:jira-page                          |
+ |                                                                      |
+ | 2) Set the following properties in the component's pom:              |
+ |    - commons.jira.id  (required, alphabetic, upper case)             |
+ |    - commons.jira.pid (required, numeric)                            |
+ |                                                                      |
+ | 3) Example Properties                                                |
+ |                                                                      |
+ |  <properties>                                                        |
+ |    <commons.jira.id>MATH</commons.jira.id>                           |
+ |    <commons.jira.pid>12310485</commons.jira.pid>                     |
+ |  </properties>                                                       |
+ |                                                                      |
+ +======================================================================+
+-->
+<document>
+  <properties>
+    <title>Commons Exec Issue tracking</title>
+    <author email="dev at commons.apache.org">Commons Documentation Team</author>
+  </properties>
+  <body>
+
+    <section name="Commons Exec Issue tracking">
+      <p>
+      Commons Exec uses <a href="http://issues.apache.org/jira/">ASF JIRA</a> for tracking issues.
+      See the <a href="http://issues.apache.org/jira/browse/EXEC">Commons Exec JIRA project page</a>.
+      </p>
+
+      <p>
+      To use JIRA you may need to <a href="http://issues.apache.org/jira/secure/Signup!default.jspa">create an account</a>
+      (if you have previously created/updated Commons issues using Bugzilla an account will have been automatically
+      created and you can use the <a href="http://issues.apache.org/jira/secure/ForgotPassword!default.jspa">Forgot Password</a>
+      page to get a new password).
+      </p>
+
+      <p>
+      If you would like to report a bug, or raise an enhancement request with
+      Commons Exec please do the following:
+      <ol>
+        <li><a href="http://issues.apache.org/jira/secure/IssueNavigator.jspa?reset=true&pid=12310814&sorter/field=issuekey&sorter/order=DESC&status=1&status=3&status=4">Search existing open bugs</a>.
+            If you find your issue listed then please add a comment with your details.</li>
+        <li><a href="mail-lists.html">Search the mailing list archive(s)</a>.
+            You may find your issue or idea has already been discussed.</li>
+        <li>Decide if your issue is a bug or an enhancement.</li>
+        <li>Submit either a <a href="http://issues.apache.org/jira/secure/CreateIssueDetails!init.jspa?pid=12310814&issuetype=1&priority=4&assignee=-1">bug report</a>
+            or <a href="http://issues.apache.org/jira/secure/CreateIssueDetails!init.jspa?pid=12310814&issuetype=4&priority=4&assignee=-1">enhancement request</a>.</li>
+      </ol>
+      </p>
+
+      <p>
+      Please also remember these points:
+      <ul>
+        <li>the more information you provide, the better we can help you</li>
+        <li>test cases are vital, particularly for any proposed enhancements</li>
+        <li>the developers of Commons Exec are all unpaid volunteers</li>
+      </ul>
+      </p>
+
+      <p>
+      For more information on subversion and creating patches see the
+      <a href="http://www.apache.org/dev/contributors.html">Apache Contributors Guide</a>.
+      </p>
+
+      <p>
+      You may also find these links useful:
+      <ul>
+        <li><a href="http://issues.apache.org/jira/secure/IssueNavigator.jspa?reset=true&pid=12310814&sorter/field=issuekey&sorter/order=DESC&status=1&status=3&status=4">All Open Commons Exec bugs</a></li>
+        <li><a href="http://issues.apache.org/jira/secure/IssueNavigator.jspa?reset=true&pid=12310814&sorter/field=issuekey&sorter/order=DESC&status=5&status=6">All Resolved Commons Exec bugs</a></li>
+        <li><a href="http://issues.apache.org/jira/secure/IssueNavigator.jspa?reset=true&pid=12310814&sorter/field=issuekey&sorter/order=DESC">All Commons Exec bugs</a></li>
+      </ul>
+      </p>
+    </section>
+  </body>
+</document>
diff --git a/src/site/xdoc/mail-lists.xml b/src/site/xdoc/mail-lists.xml
new file mode 100644
index 0000000..08fe034
--- /dev/null
+++ b/src/site/xdoc/mail-lists.xml
@@ -0,0 +1,202 @@
+<?xml version="1.0"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<!--
+ +======================================================================+
+ |****                                                              ****|
+ |****      THIS FILE IS GENERATED BY THE COMMONS BUILD PLUGIN      ****|
+ |****                    DO NOT EDIT DIRECTLY                      ****|
+ |****                                                              ****|
+ +======================================================================+
+ | TEMPLATE FILE: mail-lists-template.xml                               |
+ | commons-build-plugin/trunk/src/main/resources/commons-xdoc-templates |
+ +======================================================================+
+ |                                                                      |
+ | 1) Re-generate using: mvn commons:mail-page                          |
+ |                                                                      |
+ | 2) Set the following properties in the component's pom:              |
+ |    - commons.componentid (required, alphabetic, lower case)          |
+ |                                                                      |
+ | 3) Example Properties                                                |
+ |                                                                      |
+ |  <properties>                                                        |
+ |    <commons.componentid>math</commons.componentid>                   |
+ |  </properties>                                                       |
+ |                                                                      |
+ +======================================================================+
+-->
+<document>
+  <properties>
+    <title>Commons Exec Mailing Lists</title>
+    <author email="dev at commons.apache.org">Commons Documentation Team</author>
+  </properties>
+  <body>
+
+    <section name="Overview">
+      <p>
+        <a href="index.html">Commons Exec</a> shares mailing lists with all the other 
+        <a href="http://commons.apache.org/components.html">Commons Components</a>.
+        To make it easier for people to only read messages related to components they are interested in,
+        the convention in Commons is to prefix the subject line of messages with the component's name,
+        for example:
+        <ul>
+          <li>[exec] Problem with the ...</li>
+        </ul>
+      </p>
+      <p>
+        Questions related to the usage of Commons Exec should be posted to the
+        <a href="http://mail-archives.apache.org/mod_mbox/commons-user/">User List</a>.
+        <br />
+        The <a href="http://mail-archives.apache.org/mod_mbox/commons-dev/">Developer List</a>
+        is for questions and discussion related to the development of Commons Exec.
+        <br />
+        Please do not cross-post; developers are also subscribed to the user list.
+      </p>
+      <p>
+        <strong>Note:</strong> please don't send patches or attachments to any of the mailing lists.
+        Patches are best handled via the <a href="issue-tracking.html">Issue Tracking</a> system. 
+        Otherwise, please upload the file to a public server and include the URL in the mail. 
+      </p>
+    </section>
+
+    <section name="Commons Exec Mailing Lists">
+      <p>
+        <strong>Please prefix the subject line of any messages for <a href="index.html">Commons Exec</a>
+        with <i>[exec]</i></strong> - <i>thanks!</i>
+        <br />
+        <br />
+      </p>
+
+      <table>
+        <tr>
+          <th>Name</th>
+          <th>Subscribe</th>
+          <th>Unsubscribe</th>
+          <th>Post</th>
+          <th>Archive</th>
+          <th>Other Archives</th>
+        </tr>
+
+
+        <tr>
+          <td>
+            <strong>Commons User List</strong>
+            <br /><br />
+            Questions on using Commons Exec.
+            <br /><br />
+          </td>
+          <td><a href="mailto:user-subscribe at commons.apache.org">Subscribe</a></td>
+          <td><a href="mailto:user-unsubscribe at commons.apache.org">Unsubscribe</a></td>
+          <td><a href="mailto:user at commons.apache.org?subject=[exec]">Post</a></td>
+          <td><a href="http://mail-archives.apache.org/mod_mbox/commons-user/">mail-archives.apache.org</a></td>
+          <td><a href="http://markmail.org/list/org.apache.commons.users/">markmail.org</a><br />
+              <a href="http://www.mail-archive.com/user@commons.apache.org/">www.mail-archive.com</a><br />
+              <a href="http://news.gmane.org/gmane.comp.jakarta.commons.devel">news.gmane.org</a>
+          </td>
+        </tr>
+
+
+        <tr>
+          <td>
+            <strong>Commons Developer List</strong>
+            <br /><br />
+            Discussion of development of Commons Exec.
+            <br /><br />
+          </td>
+          <td><a href="mailto:dev-subscribe at commons.apache.org">Subscribe</a></td>
+          <td><a href="mailto:dev-unsubscribe at commons.apache.org">Unsubscribe</a></td>
+          <td><a href="mailto:dev at commons.apache.org?subject=[exec]">Post</a></td>
+          <td><a href="http://mail-archives.apache.org/mod_mbox/commons-dev/">mail-archives.apache.org</a></td>
+          <td><a href="http://markmail.org/list/org.apache.commons.dev/">markmail.org</a><br />
+              <a href="http://www.mail-archive.com/dev@commons.apache.org/">www.mail-archive.com</a><br />
+              <a href="http://news.gmane.org/gmane.comp.jakarta.commons.devel">news.gmane.org</a>
+          </td>
+        </tr>
+
+
+        <tr>
+          <td>
+            <strong>Commons Issues List</strong>
+            <br /><br />
+            Only for e-mails automatically generated by the <a href="issue-tracking.html">issue tracking</a> system.
+            <br /><br />
+          </td>
+          <td><a href="mailto:issues-subscribe at commons.apache.org">Subscribe</a></td>
+          <td><a href="mailto:issues-unsubscribe at commons.apache.org">Unsubscribe</a></td>
+          <td><i>read only</i></td>
+          <td><a href="http://mail-archives.apache.org/mod_mbox/commons-issues/">mail-archives.apache.org</a></td>
+          <td><a href="http://markmail.org/list/org.apache.commons.issues/">markmail.org</a><br />
+              <a href="http://www.mail-archive.com/issues@commons.apache.org/">www.mail-archive.com</a>
+          </td>
+        </tr>
+
+
+        <tr>
+          <td>
+            <strong>Commons Commits List</strong>
+            <br /><br />
+            Only for e-mails automatically generated by the <a href="source-repository.html">source control</a> sytem.
+            <br /><br />
+          </td>
+          <td><a href="mailto:commits-subscribe at commons.apache.org">Subscribe</a></td>
+          <td><a href="mailto:commits-unsubscribe at commons.apache.org">Unsubscribe</a></td>
+          <td><i>read only</i></td>
+          <td><a href="http://mail-archives.apache.org/mod_mbox/commons-commits/">mail-archives.apache.org</a></td>
+          <td><a href="http://markmail.org/list/org.apache.commons.commits/">markmail.org</a><br />
+              <a href="http://www.mail-archive.com/commits@commons.apache.org/">www.mail-archive.com</a>
+          </td>
+        </tr>
+
+      </table>
+
+    </section>
+    <section name="Apache Mailing Lists">
+      <p>
+        Other mailing lists which you may find useful include:
+      </p>
+
+      <table>
+        <tr>
+          <th>Name</th>
+          <th>Subscribe</th>
+          <th>Unsubscribe</th>
+          <th>Post</th>
+          <th>Archive</th>
+          <th>Other Archives</th>
+        </tr>
+        <tr>
+          <td>
+            <strong>Apache Announce List</strong>
+            <br /><br />
+            General announcements of Apache project releases.
+            <br /><br />
+          </td>
+          <td><a class="externalLink" href="mailto:announce-subscribe at apache.org">Subscribe</a></td> 
+          <td><a class="externalLink" href="mailto:announce-unsubscribe at apache.org">Unsubscribe</a></td> 
+          <td><i>read only</i></td>
+          <td><a class="externalLink" href="http://mail-archives.apache.org/mod_mbox/www-announce/">mail-archives.apache.org</a></td> 
+          <td><a class="externalLink" href="http://markmail.org/list/org.apache.announce/">markmail.org</a><br />
+              <a class="externalLink" href="http://old.nabble.com/Apache-News-and-Announce-f109.html">old.nabble.com</a><br />
+              <a class="externalLink" href="http://www.mail-archive.com/announce@apache.org/">www.mail-archive.com</a><br />
+              <a class="externalLink" href="http://news.gmane.org/gmane.comp.apache.announce">news.gmane.org</a>
+          </td>
+        </tr>
+      </table>
+
+    </section>
+  </body>
+</document>
diff --git a/src/site/xdoc/testmatrix.xml b/src/site/xdoc/testmatrix.xml
new file mode 100644
index 0000000..71a3d17
--- /dev/null
+++ b/src/site/xdoc/testmatrix.xml
@@ -0,0 +1,210 @@
+<?xml version="1.0"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<document>
+  <properties>
+    <title>Commons Exec Test Matrix</title>
+    <author email="siegfried.goeschl at it20one.at">Siegfried Goeschl</author>
+  </properties>
+  <body>
+    <section name="Test Matrix">
+      <p>The following tables contains the test results of running the comons-exec regression tests
+        on different OS/JVMs</p>
+      <subsection name="Linux">
+        <table>
+          <tr>
+            <th>Operating System</th>
+            <th>JVM</th>
+            <th>Status</th>
+            <th>Reporter</th>
+          </tr>
+          <tr>
+            <td>Linux (2.6.34.7-56.fc1, x86, 64 bit)</td>
+            <td>java version "1.6.0_20"<br/>Java(TM) SE Runtime Environment (build
+              1.6.0_20-b02)<br/>Java HotSpot(TM) 64-Bit Server VM (build 16.3-b01, mixed mode)</td>
+            <td>Passed</td>
+            <td>James Carman</td>
+          </tr>
+          <tr>
+            <td>Linux (2.6.35-gentoo-r9, x86, 64 bit)</td>
+            <td>java version "1.6.0_21"<br/>Java(TM) SE Runtime Environment (build
+              1.6.0_21-b06)<br/>Java HotSpot(TM) 64-Bit Server VM (build 17.0-b16, mixed mode)</td>
+            <td>Passed</td>
+            <td>Jörg Schaible</td>
+          </tr>
+          <tr>
+            <td>Linux (2.6.35-gentoo-r9, x86, 64 bit)</td>
+            <td>java version "1.5.0_22"<br/>Java(TM) 2 Runtime Environment, Standard Edition (build
+              1.5.0_22-b03)<br/>Java HotSpot(TM) 64-Bit Server VM (build 1.5.0_22-b03, mixed
+              mode)</td>
+            <td>Passed</td>
+            <td>Jörg Schaible</td>
+          </tr>
+          <tr>
+            <td>Linux (2.6.35-gentoo-r9, x86, 64 bit)</td>
+            <td>java version "1.4.2-03"<br/>Java(TM) 2 Runtime Environment, Standard Edition (build
+              Blackdown-1.4.2-03)<br/>Java HotSpot(TM) 64-Bit Server VM (build Blackdown-1.4.2-03,
+              mixed mode)</td>
+            <td>Failed<br/>DefaultExecutorTest#testEnvironmentVariables<br/>DefaultExecutorTest#testAddEnvironmentVariables</td>
+            <td>Jörg Schaible</td>
+          </tr>
+          <tr>
+            <td>Linux (2.6.35-gentoo-r9, x86, 64 bit)</td>
+            <td>java version "1.5.0"<br/>Java(TM) 2 Runtime Environment, Standard Edition (build pxa64dev-20100813 (SR12 FP1
+              ))<br/>IBM J9 VM (build 2.3, J2RE 1.5.0 IBM J9 2.3 Linux
+              amd64-64<br/>j9vmxa6423-20100808 (JIT enabled)</td>
+            <td>Passed</td>
+            <td>Jörg Schaible</td>
+          </tr>
+          <tr>
+            <td>Linux (2.6.35-gentoo-r9, x86, 64 bit)</td>
+            <td>java version "1.6.0"<br/>Java(TM) SE Runtime Environment (build
+              pxa6460sr8fp1-20100624_01(SR8 FP1))<br/>IBM J9 VM (build 2.4, JRE 1.6.0 IBM J9 2.4
+              Linux amd64-64<br/>jvmxa6460sr8ifx-20100609_59383 (JIT enabled, AOT enabled)</td>
+            <td>Passed</td>
+            <td>Jörg Schaible</td>
+          </tr>
+          <tr>
+            <td>Linux (2.6.35-gentoo-r9, x86, 64 bit)</td>
+            <td>java version "1.6.0_18"<br/>OpenJDK Runtime Environment (IcedTea6 1.8.1) (Gentoo
+              build 1.6.0_18-b18)<br/>OpenJDK 64-Bit Server VM (build 16.0-b13, mixed mode)</td>
+            <td>Passed</td>
+            <td>Jörg Schaible</td>
+          </tr>
+          <tr>
+            <td>Linux (2.6.35-gentoo-r9, x86, 64 bit)</td>
+            <td>java version "1.5.0"<br/>JamVM version 1.5.4<br/>Copyright (C) 2003-2010 Robert
+              Lougher GPL2<br/>Execution Engine: inline-threaded interpreter<br/>Compiled with: gcc
+              4.4.3</td>
+            <td>Failed<br/>DefaultExecutorTest#testExecuteAsyncWithTimelyUserTermination</td>
+            <td>Jörg Schaible</td>
+          </tr>
+        </table>
+      </subsection>
+      <subsection name="Mac OS X">
+        <table>
+          <tr>
+            <th>Operating System</th>
+            <th>JVM</th>
+            <th>Status</th>
+            <th>Reporter</th>
+          </tr>
+          <tr>
+            <td>Mac OS X (Darwin Kernel Version 10.4.0)</td>
+            <td>java version "1.6.0_20"<br/>Java(TM) SE Runtime Environment (build
+              1.6.0_20-b02)<br/>Java HotSpot(TM) 64-Bit Server VM (build 16.3-b01, mixed mode)</td>
+            <td>Passed</td>
+            <td>James Carman</td>
+          </tr>
+          <tr>
+            <td>Mac OS X (Darwin Kernel Version 10.4.0)</td>
+            <td>java version "1.5.0_24"<br/>Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_24-b02-357-10M3065)<br/>Java HotSpot(TM) Client VM (build 1.5.0_24-149, mixed mode, sharing)</td>
+            <td>Passed</td>
+            <td>Simon Tripodi</td>
+          </tr>          
+        </table>
+      </subsection>
+      <subsection name="Windows">
+        <table>
+          <tr>
+            <th>Operating System</th>
+            <th>JVM</th>
+            <th>Status</th>
+            <th>Reporter</th>
+          </tr>
+          <tr>
+            <td>Windows 7 Professional 64 bit</td>
+            <td>java version "1.6.0_20"<br/>Java(TM) SE Runtime Environment (build 1.6.0_20-b02)<br/>Java HotSpot(TM) 64-Bit Server VM (build 16.3-b01, mixed mode)</td>
+            <td>Passed</td>
+            <td>James Carman</td>
+          </tr>
+          <tr>
+            <td>Windows 7 Enterprise</td>
+            <td>java version "1.6.0_21"</td>
+            <td>Passed</td>
+            <td>Niall Pemberton</td>
+          </tr>          
+          <tr>
+            <td>Windows 7 Enterprise</td>
+            <td>java version "1.5.0_22-b03"</td>
+            <td>Passed</td>
+            <td>Niall Pemberton</td>
+          </tr>                    
+          <tr>
+            <td>Windows 7 Enterprise</td>
+            <td>java version "1.4.2_19-b04"</td>
+            <td>Passed</td>
+            <td>Niall Pemberton</td>
+          </tr>                    
+          <tr>
+            <td>Windows 7 Enterprise</td>
+            <td>java version "1.3.1_18-b01"</td>
+            <td>Passed</td>
+            <td>Niall Pemberton</td>
+          </tr>                              
+          <tr>
+            <td>Windows Vista Business</td>
+            <td>java version "1.6.0_21"<br/>Java(TM) SE Runtime Environment (build 1.6.0_21-b07)<br/>Java HotSpot(TM) 64-Bit Server VM (build 17.0-b17, mixed mode)</td>
+            <td>Passed</td>
+            <td>Gary Gregory</td>
+          </tr>          
+        </table>
+      </subsection>
+      <subsection name="OpenVMS">
+        <table>
+          <tr>
+            <th>Operating System</th>
+            <th>JVM</th>
+            <th>Status</th>
+            <th>Reporter</th>
+          </tr>
+          <tr>
+            <td>OpenVMS V8.3 (AlphaServer ES40 - 4 CPUs)</td>
+            <td>Fast VM (build 1.5.0-4, build J2SDK.v.1.5.0:03/31/2008-15:26, native threads, jit_150)</td>
+            <td>Fails tests which try to terminate a subprocess
+            <ul>
+            <li>DefaultExecutorTest#testExecuteAsyncWithTimelyUserTermination</li>
+            <li>DefaultExecutorTest#testExecuteAsyncWithTooLateUserTermination</li>
+            <li>DefaultExecutorTest#testExecuteWatchdogSync - test hangs</li>
+            <li>DefaultExecutorTest#testExecuteWatchdogAsync</li>
+            <li>DefaultExecutorTest#testExecuteAsyncWithProcessDestroyer</li>
+            <li>DefaultExecutorTest#testExec41WithoutStreams</li>
+            </ul>
+            </td>
+            <td>Sebastian Bazley</td>
+          </tr>
+          <tr>
+            <td>OpenVMS V8.3-1H1 (Integrity HP rx2600  (1.30GHz/3.0MB) - 2 CPUs)</td>
+            <td>Java HotSpot(TM) Server VM (build 1.5.0-2 12/02/2006-21:30 IA64, mixed mode)</td>
+            <td>Fails tests which try to terminate a subprocess
+            <ul>
+            <li>DefaultExecutorTest#testExecuteAsyncWithTimelyUserTermination</li>
+            <li>DefaultExecutorTest#testExecuteAsyncWithTooLateUserTermination</li>
+            <li>DefaultExecutorTest#testExecuteWatchdogSync - test hangs</li>
+            <li>DefaultExecutorTest#testExecuteWatchdogAsync</li>
+            <li>DefaultExecutorTest#testExecuteAsyncWithProcessDestroyer</li>
+            <li>DefaultExecutorTest#testExec41WithoutStreams</li>
+            </ul>
+            </td>
+            <td>Sebastian Bazley</td>
+          </tr>
+        </table>
+      </subsection>
+      
+    </section>
+  </body>
+</document>
diff --git a/src/test/bin/testme.bat b/src/test/bin/testme.bat
new file mode 100644
index 0000000..75fef8e
--- /dev/null
+++ b/src/test/bin/testme.bat
@@ -0,0 +1,20 @@
+ at ECHO OFF
+REM
+REM Licensed to the Apache Software Foundation (ASF) under one or more
+REM contributor license agreements.  See the NOTICE file distributed with
+REM this work for additional information regarding copyright ownership.
+REM The ASF licenses this file to You under the Apache License, Version 2.0
+REM (the "License"); you may not use this file except in compliance with
+REM the License.  You may obtain a copy of the License at
+REM
+REM      http://www.apache.org/licenses/LICENSE-2.0
+REM
+REM Unless required by applicable law or agreed to in writing, software
+REM distributed under the License is distributed on an "AS IS" BASIS,
+REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+REM See the License for the specific language governing permissions and
+REM limitations under the License.
+REM
+
+mkdir target
+"%JAVA_HOME%\bin\java" -cp .\lib\junit-3.8.1.jar;.\lib\commons-exec-${project.version}-tests.jar;.\lib\commons-exec-${project.version}.jar org.apache.commons.exec.TestRunner
\ No newline at end of file
diff --git a/src/test/bin/testme.dcl b/src/test/bin/testme.dcl
new file mode 100644
index 0000000..8974f04
--- /dev/null
+++ b/src/test/bin/testme.dcl
@@ -0,0 +1,25 @@
+$!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+$!
+$! Licensed to the Apache Software Foundation (ASF) under one or more
+$! contributor license agreements.  See the NOTICE file distributed with
+$! this work for additional information regarding copyright ownership.
+$! The ASF licenses this file to You under the Apache License, Version 2.0
+$! (the "License"); you may not use this file except in compliance with
+$! the License.  You may obtain a copy of the License at
+$!
+$!      http://www.apache.org/licenses/LICENSE-2.0
+$!
+$! Unless required by applicable law or agreed to in writing, software
+$! distributed under the License is distributed on an "AS IS" BASIS,
+$! WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+$! See the License for the specific language governing permissions and
+$! limitations under the License.
+$!
+$!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+$! 
+$! Run the test suite
+$!
+$ create/directory [.target]
+$ java "-Dorg.apache.commons.exec.lenient=false" "-Dorg.apache.commons.exec.debug=false" -
+  -cp "./lib/junit-3.8.1.jar:./lib/commons-exec-${project.version}-tests.jar:./lib/commons-exec-${project.version}.jar" -
+  "org.apache.commons.exec.TestRunner"
\ No newline at end of file
diff --git a/src/test/bin/testme.sh b/src/test/bin/testme.sh
new file mode 100755
index 0000000..d3e8d24
--- /dev/null
+++ b/src/test/bin/testme.sh
@@ -0,0 +1,22 @@
+#!/bin/sh
+
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+chmod ug+x ./src/test/scripts/*.sh
+mkdir target
+$JAVA_HOME/bin/java -Dorg.apache.commons.exec.lenient=false -Dorg.apache.commons.exec.debug=false -cp ./lib/junit-3.8.1.jar:./lib/commons-exec-${project.version}-tests.jar:./lib/commons-exec-${project.version}.jar org.apache.commons.exec.TestRunner
\ No newline at end of file
diff --git a/src/test/java/org/apache/commons/exec/CommandLineTest.java b/src/test/java/org/apache/commons/exec/CommandLineTest.java
new file mode 100644
index 0000000..0c4a4d8
--- /dev/null
+++ b/src/test/java/org/apache/commons/exec/CommandLineTest.java
@@ -0,0 +1,526 @@
+/* 
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+package org.apache.commons.exec;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+import junit.framework.AssertionFailedError;
+import junit.framework.TestCase;
+import org.apache.commons.exec.util.StringUtils;
+
+public class CommandLineTest extends TestCase {
+
+    private void assertEquals(String[] expected, String[] actual) {
+        if (!Arrays.equals(expected, actual)) {
+            throw new AssertionFailedError("Arrays not equal");
+        }
+    }
+
+    public void testExecutable() {
+        CommandLine cmdl = new CommandLine("test");
+        assertEquals("test", cmdl.toString());
+        assertEquals(new String[] {"test"}, cmdl.toStrings());
+        assertEquals("test", cmdl.getExecutable());
+        assertTrue(cmdl.getArguments().length == 0);
+    }
+
+    public void testExecutableZeroLengthString() {
+        try {
+            new CommandLine("");
+            fail("Must throw IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            // Expected
+        }
+    }
+
+    public void testExecutableWhitespaceString() {
+        try {
+            new CommandLine("   ");
+            fail("Must throw IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            // Expected
+        }
+    }
+
+    public void testNullExecutable() {
+        try {
+            new CommandLine((String)null);
+            fail("Must throw IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            // Expected
+        }
+    }
+
+    public void testAddArgument() {
+        CommandLine cmdl = new CommandLine("test");
+
+        cmdl.addArgument("foo");
+        cmdl.addArgument("bar");
+        assertEquals("test foo bar", cmdl.toString());
+        assertEquals(new String[] {"test", "foo", "bar"}, cmdl.toStrings());
+    }
+
+    public void testAddNullArgument() {
+        CommandLine cmdl = new CommandLine("test");
+
+        cmdl.addArgument(null);
+        assertEquals("test", cmdl.toString());
+        assertEquals(new String[] {"test"}, cmdl.toStrings());
+    }
+
+    public void testAddArgumentWithSpace() {
+        CommandLine cmdl = new CommandLine("test");
+        cmdl.addArgument("foo");
+        cmdl.addArgument("ba r");
+        assertEquals("test foo \"ba r\"", cmdl.toString());
+        assertEquals(new String[] {"test", "foo", "\"ba r\""}, cmdl.toStrings());
+    }
+
+    public void testAddArgumentWithQuote() {
+        CommandLine cmdl = new CommandLine("test");
+        cmdl.addArgument("foo");
+        cmdl.addArgument("ba\"r");
+        assertEquals("test foo 'ba\"r'", cmdl.toString());
+        assertEquals(new String[] {"test", "foo", "'ba\"r'"}, cmdl.toStrings());
+    }
+
+    public void testAddArgumentWithQuotesAround() {
+        CommandLine cmdl = new CommandLine("test");
+        cmdl.addArgument("\'foo\'");
+        cmdl.addArgument("\"bar\"");
+        cmdl.addArgument("\"fe z\"");
+        assertEquals("test foo bar \"fe z\"", cmdl.toString());
+        assertEquals(new String[] {"test", "foo", "bar", "\"fe z\""}, cmdl.toStrings());
+    }
+
+    public void testAddArgumentWithSingleQuote() {
+        CommandLine cmdl = new CommandLine("test");
+
+        cmdl.addArgument("foo");
+        cmdl.addArgument("ba'r");
+        assertEquals("test foo \"ba'r\"", cmdl.toString());
+        assertEquals(new String[] {"test", "foo", "\"ba\'r\""}, cmdl
+                .toStrings());
+    }
+
+    public void testAddArgumentWithBothQuotes() {
+        CommandLine cmdl = new CommandLine("test");
+
+        try {
+            cmdl.addArgument("b\"a'r");
+            fail("IllegalArgumentException should be thrown");
+        } catch (IllegalArgumentException e) {
+            // OK, expected
+        }
+    }
+
+    public void testAddArguments() {
+        CommandLine cmdl = new CommandLine("test");
+        cmdl.addArguments("foo bar");
+        assertEquals("test foo bar", cmdl.toString());
+        assertEquals(new String[] {"test", "foo", "bar"}, cmdl.toStrings());
+    }
+
+    public void testAddArgumentsWithQuotes() {
+        CommandLine cmdl = new CommandLine("test");
+        cmdl.addArguments("'foo' \"bar\"");
+        assertEquals("test foo bar", cmdl.toString());
+        assertEquals(new String[] {"test", "foo", "bar"}, cmdl.toStrings());
+    }
+
+    public void testAddArgumentsWithQuotesAndSpaces() {
+        CommandLine cmdl = new CommandLine("test");
+        cmdl.addArguments("'fo o' \"ba r\"");
+        assertEquals("test \"fo o\" \"ba r\"", cmdl.toString());
+        assertEquals(new String[] {"test", "\"fo o\"", "\"ba r\""}, cmdl
+                .toStrings());
+    }
+
+    public void testAddArgumentsArray() {
+        CommandLine cmdl = new CommandLine("test");
+        cmdl.addArguments(new String[] {"foo", "bar"});
+        assertEquals("test foo bar", cmdl.toString());
+        assertEquals(new String[] {"test", "foo", "bar"}, cmdl.toStrings());
+    }
+
+    public void testAddArgumentsArrayNull() {
+        CommandLine cmdl = new CommandLine("test");
+        cmdl.addArguments((String[]) null);
+        assertEquals("test", cmdl.toString());
+        assertEquals(new String[] {"test"}, cmdl.toStrings());
+    }
+
+    /**
+     * A little example how to add two command line arguments
+     * in one line, e.g. to make commenting out some options
+     * less error prone.
+     */
+    public void testAddTwoArguments() {
+
+        CommandLine userAddCL1 = new CommandLine("useradd");
+        userAddCL1.addArgument("-g");
+        userAddCL1.addArgument("tomcat");
+        userAddCL1.addArgument("foo");
+
+        CommandLine userAddCL2 = new CommandLine("useradd");
+        userAddCL2.addArgument("-g").addArgument("tomcat");
+        userAddCL2.addArgument("foo");
+
+        assertEquals(userAddCL1.toString(), userAddCL2.toString());
+    }
+
+    public void testParseCommandLine() {
+        CommandLine cmdl = CommandLine.parse("test foo bar");
+        assertEquals("test foo bar", cmdl.toString());
+        assertEquals(new String[] {"test", "foo", "bar"}, cmdl.toStrings());
+    }
+
+    public void testParseCommandLineWithQuotes() {
+        CommandLine cmdl = CommandLine.parse("test \"foo\" \'ba r\'");
+        assertEquals("test foo \"ba r\"", cmdl.toString());
+        assertEquals(new String[] {"test", "foo", "\"ba r\""}, cmdl.toStrings());
+    }
+
+    public void testParseCommandLineWithUnevenQuotes() {
+        try {
+            CommandLine.parse("test \"foo bar");
+            fail("IllegalArgumentException must be thrown due to uneven quotes");
+        } catch (IllegalArgumentException e) {
+            // Expected
+        }
+    }
+
+    public void testParseCommandLineWithNull() {
+        try {
+            CommandLine.parse(null);
+            fail("IllegalArgumentException must be thrown due to incorrect command line");
+        } catch (IllegalArgumentException e) {
+            // Expected
+        }
+    }
+
+    public void testParseCommandLineWithOnlyWhitespace() {
+        try {
+            CommandLine.parse("  ");
+            fail("IllegalArgumentException must be thrown due to incorrect command line");
+        } catch (IllegalArgumentException e) {
+            // Expected
+        }
+    }
+
+
+    /**
+     * A command line parsing puzzle from Tino Schoellhorn - ImageMagix expects
+     * a "500x>" parameter (including quotes) and it is simply not possible to
+     * do that without adding a space, e.g. "500x> ".
+     */
+    public void testParseComplexCommandLine1() {
+        HashMap substitutionMap = new HashMap();
+        substitutionMap.put("in", "source.jpg");
+        substitutionMap.put("out", "target.jpg");
+        CommandLine cmdl = CommandLine.parse("cmd /C convert ${in} -resize \"\'500x> \'\" ${out}", substitutionMap);
+        assertEquals("cmd /C convert source.jpg -resize \"500x> \" target.jpg", cmdl.toString());        
+    }
+
+    /**
+     * Another  command line parsing puzzle from Kai Hu - as
+     * far as I understand it there is no way to express that
+     * in a one-line command string.
+     */
+    public void testParseComplexCommandLine2() {
+
+        String commandline = "./script/jrake cruise:publish_installers "
+            + "INSTALLER_VERSION=unstable_2_1 "
+            + "INSTALLER_PATH=\"/var/lib/ cruise-agent/installers\" "
+            + "INSTALLER_DOWNLOAD_SERVER=\'something\' "
+            + "WITHOUT_HELP_DOC=true";
+
+        CommandLine cmdl = CommandLine.parse(commandline);
+        String[] args = cmdl.getArguments();
+        assertEquals(args[0], "cruise:publish_installers");
+        assertEquals(args[1], "INSTALLER_VERSION=unstable_2_1");
+        // assertEquals(args[2], "INSTALLER_PATH=\"/var/lib/ cruise-agent/installers\"");
+        // assertEquals(args[3], "INSTALLER_DOWNLOAD_SERVER='something'");
+        assertEquals(args[4], "WITHOUT_HELP_DOC=true");
+    }
+
+    /**
+     * Test the following command line
+     *
+     * cmd.exe /C c:\was51\Web Sphere\AppServer\bin\versionInfo.bat
+     */
+    public void testParseRealLifeCommandLine_1() {
+
+        String commandline = "cmd.exe /C \"c:\\was51\\Web Sphere\\AppServer\\bin\\versionInfo.bat\"";
+
+        CommandLine cmdl = CommandLine.parse(commandline);
+        String[] args = cmdl.getArguments();
+        assertEquals("/C", args[0]);
+        assertEquals("\"c:\\was51\\Web Sphere\\AppServer\\bin\\versionInfo.bat\"", args[1]);
+    }
+        
+   /**
+    * Create a command line with pre-quoted strings to test SANDBOX-192,
+    * e.g. "runMemorySud.cmd", "10", "30", "-XX:+UseParallelGC", "\"-XX:ParallelGCThreads=2\""
+    */
+    public void testComplexAddArgument() {
+        CommandLine cmdl = new CommandLine("runMemorySud.cmd");
+        cmdl.addArgument("10", false);
+        cmdl.addArgument("30", false);
+        cmdl.addArgument("-XX:+UseParallelGC", false);
+        cmdl.addArgument("\"-XX:ParallelGCThreads=2\"", false);
+        assertEquals(new String[] {"runMemorySud.cmd", "10", "30", "-XX:+UseParallelGC", "\"-XX:ParallelGCThreads=2\""}, cmdl.toStrings());
+    }
+
+    /**
+     * Create a command line with pre-quoted strings to test SANDBOX-192,
+     * e.g. "runMemorySud.cmd", "10", "30", "-XX:+UseParallelGC", "\"-XX:ParallelGCThreads=2\""
+     */
+     public void testComplexAddArguments1() {
+         CommandLine cmdl = new CommandLine("runMemorySud.cmd");
+         cmdl.addArguments(new String[] {"10", "30", "-XX:+UseParallelGC", "\"-XX:ParallelGCThreads=2\""}, false);
+         assertEquals(new String[] {"runMemorySud.cmd", "10", "30", "-XX:+UseParallelGC", "\"-XX:ParallelGCThreads=2\""}, cmdl.toStrings());
+     }
+
+    /**
+     * Create a command line with pre-quoted strings to test SANDBOX-192,
+     * e.g. "runMemorySud.cmd", "10", "30", "-XX:+UseParallelGC", "\"-XX:ParallelGCThreads=2\""
+     * Please not that we re forced to add additional single quotes to get the test working -
+     * don't know if this is a bug or a feature.
+     */
+     public void testComplexAddArguments2() {
+         CommandLine cmdl = new CommandLine("runMemorySud.cmd");
+         cmdl.addArguments("10 30 -XX:+UseParallelGC '\"-XX:ParallelGCThreads=2\"'", false);
+         assertEquals(new String[] {"runMemorySud.cmd", "10", "30", "-XX:+UseParallelGC", "\"-XX:ParallelGCThreads=2\""}, cmdl.toStrings());
+     }
+
+    /**
+     * Test expanding the command line based on a user-supplied map.
+     */
+    public void testCommandLineParsingWithExpansion1() {
+
+        CommandLine cmdl;
+
+        HashMap substitutionMap = new HashMap();
+        substitutionMap.put("JAVA_HOME", "/usr/local/java");
+        substitutionMap.put("appMainClass", "foo.bar.Main");
+        substitutionMap.put("file1", new File("./pom.xml"));
+        substitutionMap.put("file2", new File(".\\temp\\READ ME.txt"));
+
+        HashMap incompleteMap = new HashMap();
+        incompleteMap.put("JAVA_HOME", "/usr/local/java");
+
+        // do not pass substitution map
+        cmdl = CommandLine.parse("${JAVA_HOME}/bin/java ${appMainClass}");
+        assertTrue(cmdl.getExecutable().indexOf("${JAVA_HOME}") == 0 );
+        assertEquals(new String[] {"${appMainClass}"}, cmdl.getArguments());
+
+        // pass arguments with an empty map
+        cmdl = CommandLine.parse("${JAVA_HOME}/bin/java ${appMainClass}", new HashMap());
+        assertTrue(cmdl.getExecutable().indexOf("${JAVA_HOME}") == 0 );
+        assertEquals(new String[] {"${appMainClass}"}, cmdl.getArguments());
+
+        // pass an complete substitution map
+        cmdl = CommandLine.parse("${JAVA_HOME}/bin/java ${appMainClass}", substitutionMap);
+        assertTrue(cmdl.getExecutable().indexOf("${JAVA_HOME}") < 0 );
+        assertTrue(cmdl.getExecutable().indexOf("local") > 0 );
+        assertEquals(new String[] {"foo.bar.Main"}, cmdl.getArguments());
+
+        // pass an incomplete substitution map resulting in unresolved variables
+        cmdl = CommandLine.parse("${JAVA_HOME}/bin/java ${appMainClass}", incompleteMap);
+        assertTrue(cmdl.getExecutable().indexOf("${JAVA_HOME}") < 0 );
+        assertTrue(cmdl.getExecutable().indexOf("local") > 0 );
+        assertEquals(new String[] {"${appMainClass}"}, cmdl.getArguments());
+
+        // pass a file
+        cmdl = CommandLine.parse("${JAVA_HOME}/bin/java ${appMainClass} ${file1} ${file2}", substitutionMap);
+        assertTrue(cmdl.getExecutable().indexOf("${file}") < 0 );
+    }
+
+    /**
+     * Test expanding the command line based on a user-supplied map. The main
+     * goal of the test is to setup a command line using macros and reuse
+     * it multiple times.
+     */
+    public void testCommandLineParsingWithExpansion2() {
+
+        CommandLine cmdl;
+        String[] result;
+
+        // build the user supplied parameters
+        HashMap substitutionMap = new HashMap();
+        substitutionMap.put("JAVA_HOME", "C:\\Programme\\jdk1.5.0_12");
+        substitutionMap.put("appMainClass", "foo.bar.Main");
+
+        // build the command line
+        cmdl = new CommandLine("${JAVA_HOME}\\bin\\java");
+        cmdl.addArgument("-class");
+        cmdl.addArgument("${appMainClass}");
+        cmdl.addArgument("${file}");
+
+        // build the first command line
+        substitutionMap.put("file", "C:\\Document And Settings\\documents\\432431.pdf");
+        cmdl.setSubstitutionMap(substitutionMap);
+        result = cmdl.toStrings();
+
+        // verify the first command line
+        // please note - the executable argument is changed to using platform specific file separator char
+        // whereas all other variable substitution are not touched
+        assertEquals(StringUtils.fixFileSeparatorChar("C:\\Programme\\jdk1.5.0_12\\bin\\java"), result[0]);
+        assertEquals("-class", result[1]);
+        assertEquals("foo.bar.Main", result[2]);
+        assertEquals("\"C:\\Document And Settings\\documents\\432431.pdf\"", result[3]);
+
+        // verify the first command line again but by
+        // accessing the executable and arguments directly
+        String executable = cmdl.getExecutable();
+        String[] arguments = cmdl.getArguments();
+        assertEquals(StringUtils.fixFileSeparatorChar("C:\\Programme\\jdk1.5.0_12\\bin\\java"), executable);
+        assertEquals("-class", arguments[0]);
+        assertEquals("foo.bar.Main", arguments[1]);
+        assertEquals("\"C:\\Document And Settings\\documents\\432431.pdf\"", arguments[2]);
+
+        // build the second command line with updated parameters resulting in  a different command line
+        substitutionMap.put("file", "C:\\Document And Settings\\documents\\432432.pdf");        
+        result = cmdl.toStrings();
+        assertEquals(StringUtils.fixFileSeparatorChar("C:\\Programme\\jdk1.5.0_12\\bin\\java"), result[0]);
+        assertEquals("-class", result[1]);
+        assertEquals("foo.bar.Main", result[2]);
+        assertEquals("\"C:\\Document And Settings\\documents\\432432.pdf\"", result[3]);                
+    }
+
+    public void testCommandLineParsingWithExpansion3(){
+        CommandLine cmdl = CommandLine.parse("AcroRd32.exe");
+        cmdl.addArgument("/p");
+        cmdl.addArgument("/h");
+        cmdl.addArgument("${file}", false);
+        HashMap params = new HashMap();
+        params.put("file", "C:\\Document And Settings\\documents\\432432.pdf");
+        cmdl.setSubstitutionMap(params);
+        String[] result = cmdl.toStrings();
+        assertEquals("AcroRd32.exe", result[0]);
+        assertEquals("/p", result[1]);
+        assertEquals("/h", result[2]);
+        assertEquals("C:\\Document And Settings\\documents\\432432.pdf", result[3]);                
+        
+    }
+    /**
+     * Test the toString() method.
+     *
+     * @throws Exception the test failed
+     */
+    public void testToString() throws Exception {
+        CommandLine cmdl;
+        HashMap params = new HashMap();
+
+        // use no arguments
+        cmdl = CommandLine.parse("AcroRd32.exe", params);
+        assertEquals(cmdl.toString(), "AcroRd32.exe");
+
+        // use an argument containing spaces
+        params.put("file", "C:\\Document And Settings\\documents\\432432.pdf");
+        cmdl = CommandLine.parse("AcroRd32.exe /p /h '${file}'", params);
+        assertEquals(cmdl.toString(), "AcroRd32.exe /p /h \"C:\\Document And Settings\\documents\\432432.pdf\"");
+
+        // use an argument without spaces
+        params.put("file", "C:\\documents\\432432.pdf");
+        cmdl = CommandLine.parse("AcroRd32.exe /p /h '${file}'", params);
+        assertEquals(cmdl.toString(), "AcroRd32.exe /p /h C:\\documents\\432432.pdf");
+    }
+
+    /**
+     * Some complex real-life command line from
+     * http://blogs.msdn.com/b/astebner/archive/2005/12/13/503471.aspx
+     */
+    public void _testExec36_1() throws Exception {
+
+        CommandLine cmdl;
+
+        String line = "./script/jrake "
+          + "cruise:publish_installers "
+          + "INSTALLER_VERSION=unstable_2_1 "
+          + "INSTALLER_PATH=\"/var/lib/cruise-agent/installers\" "
+          + "INSTALLER_DOWNLOAD_SERVER='something'"
+          + "WITHOUT_HELP_DOC=true";
+
+        cmdl = CommandLine.parse(line);
+        String[] args = cmdl.toStrings();
+        assertEquals("./script/jrake", args[0]);
+        assertEquals("cruise:publish_installers", args[1]);
+        assertEquals("INSTALLER_VERSION=unstable_2_1", args[2]);
+        assertEquals("INSTALLER_PATH=\"/var/lib/cruise-agent/installers\"", args[3]);
+        assertEquals("INSTALLER_DOWNLOAD_SERVER='something'", args[4]);
+        assertEquals("WITHOUT_HELP_DOC=true", args[5]);
+    }
+
+    /**
+     * Some complex real-life command line from
+     * http://blogs.msdn.com/b/astebner/archive/2005/12/13/503471.aspx
+     */
+    public void _testExec36_2() {
+
+        CommandLine cmdl;
+
+        String line = "dotnetfx.exe"
+                + " /q:a "
+                + "/c:\"install.exe /l \"\"c:\\Documents and Settings\\myusername\\Local Settings\\Temp\\netfx.log\"\" /q\"";
+
+        cmdl = CommandLine.parse(line);
+        String[] args = cmdl.toStrings();
+        assertEquals("dotnetfx.exe", args[0]);
+        assertEquals("/q:a", args[1]);
+        assertEquals("/c:\"install.exe /l \"\"c:\\Documents and Settings\\myusername\\Local Settings\\Temp\\netfx.log\"\" /q\"", args[2] );
+    }
+
+    /**
+     * Test the following command line
+     *
+     * C:\CVS_DB\WeightsEngine /f WeightsEngine.mak CFG="WeightsEngine - Win32Release"
+     */
+    public void _testExec36_3() {
+
+        String commandline = "C:\\CVS_DB\\WeightsEngine /f WeightsEngine.mak CFG=\"WeightsEngine - Win32Release\"";
+
+        CommandLine cmdl = CommandLine.parse(commandline);
+        String[] args = cmdl.getArguments();
+        assertEquals("/f", args[0]);
+        assertEquals("WeightsEngine.mak", args[1]);
+        assertEquals("CFG=\"WeightsEngine - Win32Release\"", args[2]);
+    }
+
+    public void testCopyConstructor()
+    {
+        Map map = new HashMap();
+        map.put("bar", "bar");
+        CommandLine other = new CommandLine("test");
+        other.addArgument("foo");
+        other.setSubstitutionMap(map);
+
+        CommandLine cmdl = new CommandLine(other);
+        assertEquals(other.getExecutable(), cmdl.getExecutable());
+        assertEquals(other.getArguments(), cmdl.getArguments());
+        assertEquals(other.isFile(), cmdl.isFile());
+        assertEquals(other.getSubstitutionMap(), cmdl.getSubstitutionMap());
+
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/exec/DefaultExecutorTest.java b/src/test/java/org/apache/commons/exec/DefaultExecutorTest.java
new file mode 100644
index 0000000..49c68f2
--- /dev/null
+++ b/src/test/java/org/apache/commons/exec/DefaultExecutorTest.java
@@ -0,0 +1,988 @@
+/* 
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+package org.apache.commons.exec;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.util.HashMap;
+import java.util.Map;
+
+import junit.framework.TestCase;
+import org.apache.commons.exec.environment.EnvironmentUtils;
+
+public class DefaultExecutorTest extends TestCase {
+
+    /** Maximum time to wait (15s) */
+    private static final int WAITFOR_TIMEOUT = 15000;
+
+    private Executor exec = new DefaultExecutor();
+    private File testDir = new File("src/test/scripts");
+    private File foreverOutputFile = new File("./target/forever.txt");
+    private ByteArrayOutputStream baos;
+
+    private File testScript = TestUtil.resolveScriptForOS(testDir + "/test");
+    private File errorTestScript = TestUtil.resolveScriptForOS(testDir + "/error");
+    private File foreverTestScript = TestUtil.resolveScriptForOS(testDir + "/forever");
+    private File nonExistingTestScript = TestUtil.resolveScriptForOS(testDir + "/grmpffffff");
+    private File redirectScript = TestUtil.resolveScriptForOS(testDir + "/redirect");
+    private File pingScript = TestUtil.resolveScriptForOS(testDir + "/ping");
+    private File printArgsScript = TestUtil.resolveScriptForOS(testDir + "/printargs");
+    private File acroRd32Script = TestUtil.resolveScriptForOS(testDir + "/acrord32");
+    private File stdinSript = TestUtil.resolveScriptForOS(testDir + "/stdin");
+    private File environmentSript = TestUtil.resolveScriptForOS(testDir + "/environment");
+
+    // Get suitable exit codes for the OS
+    private static final int SUCCESS_STATUS; // test script successful exit code
+    private static final int ERROR_STATUS;   // test script error exit code
+
+    static{
+
+        int statuses[] = TestUtil.getTestScriptCodesForOS();
+        SUCCESS_STATUS=statuses[0];
+        ERROR_STATUS=statuses[1];
+
+        // turn on debug mode and throw an exception for each encountered problem
+        System.setProperty("org.apache.commons.exec.lenient", "false");
+        System.setProperty("org.apache.commons.exec.debug", "true");                
+    }
+    
+    protected void setUp() throws Exception {
+
+        System.out.println(">>> Executing " + getName() + " ...");
+
+        // delete the marker file
+        this.foreverOutputFile.getParentFile().mkdirs();
+        if(this.foreverOutputFile.exists()) {
+            this.foreverOutputFile.delete();
+        }
+
+        // prepare a ready to Executor
+        this.baos = new ByteArrayOutputStream();
+        this.exec.setStreamHandler(new PumpStreamHandler(baos, baos));
+    }
+
+    protected void tearDown() throws Exception {
+        this.baos.close();
+        foreverOutputFile.delete();
+    }
+
+    // ======================================================================
+    // Start of regression tests
+    // ======================================================================
+
+    /**
+     * The simplest possible test - start a script and
+     * check that the output was pumped into our
+     * <code>ByteArrayOutputStream</code>.
+     *
+     * @throws Exception the test failed
+     */
+    public void testExecute() throws Exception {
+        CommandLine cl = new CommandLine(testScript);
+        int exitValue = exec.execute(cl);
+        assertEquals("FOO..", baos.toString().trim());
+        assertFalse(exec.isFailure(exitValue));
+        assertEquals(new File("."), exec.getWorkingDirectory());        
+    }
+
+    public void testExecuteWithWorkingDirectory() throws Exception {
+        File workingDir = new File("./target");
+        CommandLine cl = new CommandLine(testScript);
+        exec.setWorkingDirectory(workingDir);
+        int exitValue = exec.execute(cl);
+        assertEquals("FOO..", baos.toString().trim());
+        assertFalse(exec.isFailure(exitValue));
+        assertEquals(exec.getWorkingDirectory(), workingDir);
+    }
+
+    public void testExecuteWithInvalidWorkingDirectory() throws Exception {
+        File workingDir = new File("/foo/bar");
+        CommandLine cl = new CommandLine(testScript);
+        exec.setWorkingDirectory(workingDir);
+        try {
+            exec.execute(cl);
+            fail("Expected exception due to invalid working directory");
+        }
+        catch(IOException e) {
+            return;
+        }
+    }
+
+    public void testExecuteWithError() throws Exception {
+        CommandLine cl = new CommandLine(errorTestScript);
+        
+        try{
+            exec.execute(cl);
+            fail("Must throw ExecuteException");
+        } catch(ExecuteException e) {
+            assertTrue(exec.isFailure(e.getExitValue()));
+        }
+    }
+
+    public void testExecuteWithArg() throws Exception {
+        CommandLine cl = new CommandLine(testScript);
+        cl.addArgument("BAR");
+        int exitValue = exec.execute(cl);
+
+        assertEquals("FOO..BAR", baos.toString().trim());
+        assertFalse(exec.isFailure(exitValue));
+    }
+
+    /**
+     * Execute the test script and pass a environment containing
+     * 'TEST_ENV_VAR'.
+     */
+    public void testExecuteWithSingleEnvironmentVariable() throws Exception {
+    	Map env = new HashMap();
+        env.put("TEST_ENV_VAR", "XYZ");
+
+        CommandLine cl = new CommandLine(testScript);
+
+        int exitValue = exec.execute(cl, env);
+
+        assertEquals("FOO.XYZ.", baos.toString().trim());
+        assertFalse(exec.isFailure(exitValue));
+    }
+
+    /**
+     * Start a asynchronous process which returns an success
+     * exit value.
+     *
+     * @throws Exception the test failed
+     */
+    public void testExecuteAsync() throws Exception {
+        CommandLine cl = new CommandLine(testScript);
+        DefaultExecuteResultHandler resultHandler = new DefaultExecuteResultHandler();        
+        exec.execute(cl, resultHandler);
+        resultHandler.waitFor(2000);
+        assertTrue(resultHandler.hasResult());
+        assertNull(resultHandler.getException());
+        assertFalse(exec.isFailure(resultHandler.getExitValue()));
+        assertEquals("FOO..", baos.toString().trim());
+    }
+
+    /**
+     * Start a asynchronous process which returns an error
+     * exit value.
+     *
+     * @throws Exception the test failed
+     */
+    public void testExecuteAsyncWithError() throws Exception {
+        CommandLine cl = new CommandLine(errorTestScript);
+        DefaultExecuteResultHandler resultHandler = new DefaultExecuteResultHandler();
+        exec.execute(cl, resultHandler);
+        resultHandler.waitFor(2000);
+        assertTrue(resultHandler.hasResult());
+        assertTrue(exec.isFailure(resultHandler.getExitValue()));
+        assertNotNull(resultHandler.getException());
+        assertEquals("FOO..", baos.toString().trim());
+    }
+
+    /**
+     * Start a asynchronous process and terminate it manually before the
+     * watchdog timeout occurs.
+     *
+     * @throws Exception the test failed 
+     */
+    public void testExecuteAsyncWithTimelyUserTermination() throws Exception {
+        CommandLine cl = new CommandLine(foreverTestScript);
+        ExecuteWatchdog watchdog = new ExecuteWatchdog(Integer.MAX_VALUE);
+        exec.setWatchdog(watchdog);
+        DefaultExecuteResultHandler handler = new DefaultExecuteResultHandler();
+        exec.execute(cl, handler);
+        // wait for script to run
+        Thread.sleep(2000);
+        assertTrue("Watchdog should watch the process", watchdog.isWatching());
+        // terminate it manually using the watchdog
+        watchdog.destroyProcess();
+        // wait until the result of the process execution is propagated
+        handler.waitFor(WAITFOR_TIMEOUT);
+        assertTrue("Watchdog should have killed the process", watchdog.killedProcess());
+        assertFalse("Watchdog is no longer watching the process", watchdog.isWatching());
+        assertTrue("ResultHandler received a result", handler.hasResult());
+        assertNotNull("ResultHandler received an exception as result", handler.getException());
+    }
+
+    /**
+     * Start a asynchronous process and try to terminate it manually but
+     * the process was already terminated by the watchdog. This is
+     * basically a race condition between infrastructure and user
+     * code.
+     *
+     * @throws Exception the test failed
+     */
+    public void testExecuteAsyncWithTooLateUserTermination() throws Exception {
+        CommandLine cl = new CommandLine(foreverTestScript);
+        DefaultExecuteResultHandler handler = new DefaultExecuteResultHandler();
+        ExecuteWatchdog watchdog = new ExecuteWatchdog(3000);
+        exec.setWatchdog(watchdog);
+        exec.execute(cl, handler);
+        // wait for script to be terminated by the watchdog
+        Thread.sleep(6000);
+        // try to terminate the already terminated process
+        watchdog.destroyProcess();
+        // wait until the result of the process execution is propagated
+        handler.waitFor(WAITFOR_TIMEOUT);
+        assertTrue("Watchdog should have killed the process already", watchdog.killedProcess());
+        assertFalse("Watchdog is no longer watching the process", watchdog.isWatching());
+        assertTrue("ResultHandler received a result", handler.hasResult());
+        assertNotNull("ResultHandler received an exception as result", handler.getException());
+    }
+
+    /**
+     * Start a script looping forever (synchronously) and check if the ExecuteWatchdog
+     * kicks in killing the run away process. To make killing a process
+     * more testable the "forever" scripts write each second a '.'
+     * into "./target/forever.txt" (a marker file). After a test run
+     * we should have a few dots in there.
+     *
+     * @throws Exception the test failed
+     */
+    public void testExecuteWatchdogSync() throws Exception {
+
+        if (OS.isFamilyOpenVms()){
+            System.out.println("The test 'testExecuteWatchdogSync' currently hangs on the following OS : "
+                    + System.getProperty("os.name"));
+            return;
+        }
+
+        long timeout = 10000;
+
+        CommandLine cl = new CommandLine(foreverTestScript);
+        DefaultExecutor executor = new DefaultExecutor();
+        executor.setWorkingDirectory(new File("."));
+        ExecuteWatchdog watchdog = new ExecuteWatchdog(timeout);
+        executor.setWatchdog(watchdog);
+
+        try {
+            executor.execute(cl);
+        }
+        catch(ExecuteException e) {
+            Thread.sleep(timeout);
+            int nrOfInvocations = getOccurrences(readFile(this.foreverOutputFile), '.');
+            assertTrue( executor.getWatchdog().killedProcess() );
+            assertTrue("killing the subprocess did not work : " + nrOfInvocations, nrOfInvocations > 5 && nrOfInvocations <= 11);
+            return;
+        }
+        catch(Throwable t) {
+            fail(t.getMessage());    
+        }
+
+        assertTrue("Killed process should be true", executor.getWatchdog().killedProcess() );
+        fail("Process did not create ExecuteException when killed");
+    }
+
+    /**
+     * Start a script looping forever (asynchronously) and check if the
+     * ExecuteWatchdog kicks in killing the run away process. To make killing
+     * a process more testable the "forever" scripts write each second a '.'
+     * into "./target/forever.txt" (a marker file). After a test run
+     * we should have a few dots in there.
+     *
+     * @throws Exception the test failed
+     */
+    public void testExecuteWatchdogAsync() throws Exception {
+
+        long timeout = 10000;
+
+        CommandLine cl = new CommandLine(foreverTestScript);
+        DefaultExecuteResultHandler handler = new DefaultExecuteResultHandler();
+        DefaultExecutor executor = new DefaultExecutor();
+        executor.setWorkingDirectory(new File("."));
+        executor.setWatchdog(new ExecuteWatchdog(timeout));
+
+        executor.execute(cl, handler);
+        handler.waitFor(WAITFOR_TIMEOUT);
+
+        assertTrue("Killed process should be true", executor.getWatchdog().killedProcess() );
+        assertTrue("ResultHandler received a result", handler.hasResult());
+        assertNotNull("ResultHandler received an exception as result", handler.getException());
+
+        int nrOfInvocations = getOccurrences(readFile(this.foreverOutputFile), '.');
+        assertTrue("Killing the process did not work : " + nrOfInvocations, nrOfInvocations > 5 && nrOfInvocations <= 11);
+    }
+
+    /**
+     * Try to start an non-existing application which should result
+     * in an exception.
+     *
+     * @throws Exception the test failed
+     */
+    public void testExecuteNonExistingApplication() throws Exception {
+        CommandLine cl = new CommandLine(nonExistingTestScript);
+        DefaultExecutor executor = new DefaultExecutor();
+        try {
+            executor.execute(cl);
+        }
+        catch( IOException e) {
+            // expected
+            return;
+        }
+        fail("Got no exception when executing an non-existing application");
+    }
+
+    /**
+     * Try to start an non-existing application asynchronously which should result
+     * in an exception.
+     *
+     * @throws Exception the test failed
+     */
+    public void testExecuteAsyncWithNonExistingApplication() throws Exception {
+        CommandLine cl = new CommandLine(nonExistingTestScript);
+        DefaultExecuteResultHandler handler = new DefaultExecuteResultHandler();
+        exec.execute(cl, handler);
+        Thread.sleep(2000);
+        assertNotNull(handler.getException());
+        assertTrue(exec.isFailure(handler.getExitValue()));
+    }
+
+    /**
+     * Invoke the error script but define that the ERROR_STATUS is a good
+     * exit value and therefore no exception should be thrown.
+     *
+     * @throws Exception the test failed
+     */
+    public void testExecuteWithCustomExitValue1() throws Exception {
+        exec.setExitValue(ERROR_STATUS);
+        CommandLine cl = new CommandLine(errorTestScript);
+        exec.execute(cl);
+    }
+
+    /**
+     * Invoke the error script but define that SUCCESS_STATUS is a bad
+     * exit value and therefore an exception should be thrown.
+     *
+     * @throws Exception the test failed
+     */
+    public void testExecuteWithCustomExitValue2() throws Exception {
+        CommandLine cl = new CommandLine(errorTestScript);
+        exec.setExitValue(SUCCESS_STATUS);
+        try{
+            exec.execute(cl);
+            fail("Must throw ExecuteException");
+        } catch(ExecuteException e) {
+            assertTrue(exec.isFailure(e.getExitValue()));
+            return;
+        }
+    }
+
+    /**
+     * Test the proper handling of ProcessDestroyer for an synchronous process.
+     *
+     * @throws Exception the test failed
+     */
+    public void testExecuteWithProcessDestroyer() throws Exception {
+
+      CommandLine cl = new CommandLine(testScript);
+      ShutdownHookProcessDestroyer processDestroyer = new ShutdownHookProcessDestroyer();
+      exec.setProcessDestroyer(processDestroyer);
+      
+      assertTrue(processDestroyer.size() == 0);
+      assertTrue(processDestroyer.isAddedAsShutdownHook() == false);
+      
+      int exitValue = exec.execute(cl);
+
+      assertEquals("FOO..", baos.toString().trim());
+      assertFalse(exec.isFailure(exitValue));
+      assertTrue(processDestroyer.size() == 0);
+      assertTrue(processDestroyer.isAddedAsShutdownHook() == false);
+    }
+  
+    /**
+     * Test the proper handling of ProcessDestroyer for an asynchronous process.
+     * Since we do not terminate the process it will be terminated in the
+     * ShutdownHookProcessDestroyer implementation.
+     *
+     * @throws Exception the test failed
+     */
+    public void testExecuteAsyncWithProcessDestroyer() throws Exception {
+
+      CommandLine cl = new CommandLine(foreverTestScript);
+      DefaultExecuteResultHandler handler = new DefaultExecuteResultHandler();
+      ShutdownHookProcessDestroyer processDestroyer = new ShutdownHookProcessDestroyer();
+      ExecuteWatchdog watchdog = new ExecuteWatchdog(Integer.MAX_VALUE);
+
+      assertTrue(exec.getProcessDestroyer() == null);
+      assertTrue(processDestroyer.size() == 0);
+      assertTrue(processDestroyer.isAddedAsShutdownHook() == false);
+
+      exec.setWatchdog(watchdog);
+      exec.setProcessDestroyer(processDestroyer);
+      exec.execute(cl, handler);
+
+      // wait for script to start
+      Thread.sleep(2000);
+
+      // our process destroyer should be initialized now
+      assertNotNull("Process destroyer should exist", exec.getProcessDestroyer());
+      assertEquals("Process destroyer size should be 1", 1, processDestroyer.size());
+      assertTrue("Process destroyer should exist as shutdown hook", processDestroyer.isAddedAsShutdownHook());
+
+      // terminate it and the process destroyer is detached
+      watchdog.destroyProcess();
+      assertTrue(watchdog.killedProcess());
+      handler.waitFor(WAITFOR_TIMEOUT);
+      assertTrue("ResultHandler received a result", handler.hasResult());
+      assertNotNull(handler.getException());
+      assertEquals("Processor Destroyer size should be 0", 0, processDestroyer.size());
+      assertFalse("Process destroyer should not exist as shutdown hook", processDestroyer.isAddedAsShutdownHook());
+    }
+
+    /**
+     * Invoke the test using some fancy arguments.
+     *
+     * @throws Exception the test failed
+     */
+    public void testExecuteWithFancyArg() throws Exception {
+        CommandLine cl = new CommandLine(testScript);
+        cl.addArgument("test $;`(0)[1]{2}");
+        int exitValue = exec.execute(cl);
+        assertTrue(baos.toString().trim().indexOf("test $;`(0)[1]{2}") > 0);
+        assertFalse(exec.isFailure(exitValue));
+    }
+
+    /**
+     * Start a process with redirected streams - stdin of the newly
+     * created process is connected to a FileInputStream whereas
+     * the "redirect" script reads all lines from stdin and prints
+     * them on stdout. Furthermore the script prints a status
+     * message on stderr.
+     *
+     * @throws Exception the test failed
+     */
+    public void testExecuteWithRedirectedStreams() throws Exception
+    {
+        if(OS.isFamilyUnix())
+        {
+            FileInputStream fis = new FileInputStream("./NOTICE.txt");
+            CommandLine cl = new CommandLine(redirectScript);
+            PumpStreamHandler pumpStreamHandler = new PumpStreamHandler( baos, baos, fis );
+            DefaultExecutor executor = new DefaultExecutor();
+            executor.setWorkingDirectory(new File("."));
+            executor.setStreamHandler( pumpStreamHandler );
+            int exitValue = executor.execute(cl);
+            fis.close();
+            String result = baos.toString().trim();
+            System.out.println(result);
+            assertTrue(result.indexOf("Finished reading from stdin") > 0);
+            assertFalse(exec.isFailure(exitValue));
+        }
+        else if(OS.isFamilyWindows()) {
+            System.err.println("The code samples to do that in windows look like a joke ... :-( .., no way I'm doing that");
+            System.err.println("The test 'testExecuteWithRedirectedStreams' does not support the following OS : " + System.getProperty("os.name"));
+            return;
+        }
+        else {
+            System.err.println("The test 'testExecuteWithRedirectedStreams' does not support the following OS : " + System.getProperty("os.name"));
+            return;
+        }
+    }
+
+     /**
+      * Start a process and connect stdout and stderr.
+      *
+      * @throws Exception the test failed
+      */
+     public void testExecuteWithStdOutErr() throws Exception
+     {
+         CommandLine cl = new CommandLine(testScript);
+         PumpStreamHandler pumpStreamHandler = new PumpStreamHandler( System.out, System.err );
+         DefaultExecutor executor = new DefaultExecutor();
+         executor.setStreamHandler( pumpStreamHandler );
+         int exitValue = executor.execute(cl);
+         assertFalse(exec.isFailure(exitValue));
+     }
+
+     /**
+      * Start a process and connect it to no stream.
+      *
+      * @throws Exception the test failed
+      */
+     public void testExecuteWithNullOutErr() throws Exception
+     {
+         CommandLine cl = new CommandLine(testScript);
+         PumpStreamHandler pumpStreamHandler = new PumpStreamHandler( null, null );
+         DefaultExecutor executor = new DefaultExecutor();
+         executor.setStreamHandler( pumpStreamHandler );
+         int exitValue = executor.execute(cl);
+         assertFalse(exec.isFailure(exitValue));
+     }
+
+     /**
+      * Start a process and connect out and err to a file.
+      *
+      * @throws Exception the test failed
+      */
+     public void testExecuteWithRedirectOutErr() throws Exception
+     {
+         File outfile = File.createTempFile("EXEC", ".test");
+         outfile.deleteOnExit();
+         CommandLine cl = new CommandLine(testScript);
+         PumpStreamHandler pumpStreamHandler = new PumpStreamHandler( new FileOutputStream(outfile) );
+         DefaultExecutor executor = new DefaultExecutor();
+         executor.setStreamHandler( pumpStreamHandler );
+         int exitValue = executor.execute(cl);
+         assertFalse(exec.isFailure(exitValue));
+         assertTrue(outfile.exists());
+     }
+
+    /**
+     * A generic test case to print the command line arguments to 'printargs' script to solve
+     * even more command line puzzles.
+     *
+     * @throws Exception the test failed
+     */
+    public void testExecuteWithComplexArguments() throws Exception {
+        CommandLine cl = new CommandLine(printArgsScript);
+        cl.addArgument("gdal_translate");
+        cl.addArgument("HDF5:\"/home/kk/grass/data/4404.he5\"://HDFEOS/GRIDS/OMI_Column_Amount_O3/Data_Fields/ColumnAmountO3/home/kk/4.tif", false);
+        DefaultExecutor executor = new DefaultExecutor();
+        int exitValue = executor.execute(cl);
+        assertFalse(exec.isFailure(exitValue));
+     }
+
+
+    /**
+     * The test script reads an argument from <code>stdin<code> and prints
+     * the result to stdout. To make things slightly more interesting
+     * we are using an asynchronous process.
+     *
+     * @throws Exception the test failed
+     */
+    public void testStdInHandling() throws Exception {
+
+        ByteArrayInputStream bais = new ByteArrayInputStream("Foo".getBytes()); // newline not needed; causes problems for VMS
+        CommandLine cl = new CommandLine(this.stdinSript);
+        PumpStreamHandler pumpStreamHandler = new PumpStreamHandler( this.baos, System.err, bais);
+        DefaultExecuteResultHandler resultHandler = new DefaultExecuteResultHandler();
+        Executor executor = new DefaultExecutor();
+        executor.setStreamHandler(pumpStreamHandler);
+        executor.execute(cl, resultHandler);
+
+        resultHandler.waitFor(WAITFOR_TIMEOUT);
+        assertTrue("ResultHandler received a result", resultHandler.hasResult());
+
+        assertFalse(exec.isFailure(resultHandler.getExitValue()));
+        String result = baos.toString();
+        assertTrue("Result '"+result+"' should contain 'Hello Foo!'", result.indexOf("Hello Foo!") >= 0);
+    }
+
+    /**
+     * Call a script to dump the environment variables of the subprocess. 
+     *
+     * @throws Exception the test failed
+     */
+    public void testEnvironmentVariables() throws Exception {
+        exec.execute(new CommandLine(environmentSript));
+        String environment = baos.toString().trim();
+        assertTrue("Found no environment variables", environment.length() > 0);
+        assertFalse(environment.indexOf("NEW_VAR") >= 0);
+        System.out.println(environment);
+    }
+
+    /**
+     * Call a script to dump the environment variables of the subprocess
+     * after adding a custom environment variable.
+     *
+     * @throws Exception the test failed
+     */
+    public void testAddEnvironmentVariables() throws Exception {
+        Map myEnvVars = new HashMap();
+        myEnvVars.putAll(EnvironmentUtils.getProcEnvironment());
+        myEnvVars.put("NEW_VAR","NEW_VAL");
+        exec.execute(new CommandLine(environmentSript), myEnvVars);
+        String environment = baos.toString().trim();
+        assertTrue("Expecting NEW_VAR in "+environment,environment.indexOf("NEW_VAR") >= 0);
+        assertTrue("Expecting NEW_VAL in "+environment,environment.indexOf("NEW_VAL") >= 0);
+    }
+
+    public void testAddEnvironmentVariableEmbeddedQuote() throws Exception {
+        Map myEnvVars = new HashMap();
+        myEnvVars.putAll(EnvironmentUtils.getProcEnvironment());
+        String name = "NEW_VAR";
+        String value = "NEW_\"_VAL";
+        myEnvVars.put(name,value);
+        exec.execute(new CommandLine(environmentSript), myEnvVars);
+        String environment = baos.toString().trim();
+        assertTrue("Expecting "+name+" in "+environment,environment.indexOf(name) >= 0);
+        assertTrue("Expecting "+value+" in "+environment,environment.indexOf(value) >= 0);
+    }
+
+    // ======================================================================
+    // === Testing bug fixes
+    // ======================================================================
+
+    /**
+     * Test the patch for EXEC-33 (https://issues.apache.org/jira/browse/EXEC-33)
+     *
+     * PumpStreamHandler hangs if System.in is redirect to process input stream .
+     *
+     * @throws Exception the test failed
+     */
+    public void testExec33() throws Exception
+    {
+        CommandLine cl = new CommandLine(testScript);
+        PumpStreamHandler pumpStreamHandler = new PumpStreamHandler( System.out, System.err, System.in );
+        DefaultExecutor executor = new DefaultExecutor();
+        executor.setStreamHandler( pumpStreamHandler );
+        int exitValue = executor.execute(cl);
+        assertFalse(exec.isFailure(exitValue));
+    }
+
+    /**
+     * EXEC-34 https://issues.apache.org/jira/browse/EXEC-34
+     *
+     * Race condition prevent watchdog working using ExecuteStreamHandler.
+     * The test fails because when watchdog.destroyProcess() is invoked the
+     * external process is not bound to the watchdog yet
+     *
+     * @throws Exception the test failed
+     */
+    public void testExec34() throws Exception {
+
+        CommandLine cmdLine = new CommandLine(pingScript);
+        cmdLine.addArgument("10"); // sleep 10 secs
+
+        ExecuteWatchdog watchdog = new ExecuteWatchdog(Integer.MAX_VALUE);
+        DefaultExecuteResultHandler handler = new DefaultExecuteResultHandler();
+        exec.setWatchdog(watchdog);
+        exec.execute(cmdLine, handler);
+        // if you comment out the next line the test will fail
+        Thread.sleep(2000);
+        // terminate it
+        assertTrue(watchdog.isWatching());
+        watchdog.destroyProcess();
+        assertTrue("Watchdog should have killed the process",watchdog.killedProcess());
+        assertFalse("Watchdog is no longer watching the process",watchdog.isWatching());
+    }
+
+    /**
+     * Test EXEC-36 see https://issues.apache.org/jira/browse/EXEC-36
+     *
+     * Original example from Kai Hu which only can be tested on Unix
+     *
+     * @throws Exception the test failed
+     */
+    public void testExec36_1() throws Exception {
+
+        if(OS.isFamilyUnix()) {
+
+            CommandLine cmdl;
+
+            /**
+             * ./script/jrake cruise:publish_installers INSTALLER_VERSION=unstable_2_1 \
+             *     INSTALLER_PATH="/var/lib/ cruise-agent/installers" INSTALLER_DOWNLOAD_SERVER='something' WITHOUT_HELP_DOC=true
+             */
+
+            String expected = "./script/jrake\n" +
+                    "cruise:publish_installers\n" +
+                    "INSTALLER_VERSION=unstable_2_1\n" +
+                    "INSTALLER_PATH=\"/var/lib/ cruise-agent/installers\"\n" +
+                    "INSTALLER_DOWNLOAD_SERVER='something'\n" +
+                    "WITHOUT_HELP_DOC=true";
+
+            cmdl = new CommandLine(printArgsScript);
+            cmdl.addArgument("./script/jrake", false);
+            cmdl.addArgument("cruise:publish_installers", false);
+            cmdl.addArgument("INSTALLER_VERSION=unstable_2_1", false);
+            cmdl.addArgument("INSTALLER_PATH=\"/var/lib/ cruise-agent/installers\"", false);
+            cmdl.addArgument("INSTALLER_DOWNLOAD_SERVER='something'", false);
+            cmdl.addArgument("WITHOUT_HELP_DOC=true", false);
+
+            int exitValue = exec.execute(cmdl);
+            String result = baos.toString().trim();
+            System.out.println("=== Expected ===");
+            System.out.println(expected);
+            System.out.println("=== Result ===");
+            System.out.println(result);
+            assertFalse(exec.isFailure(exitValue));
+            assertEquals(expected, result);
+        }
+        else {
+            System.err.println("The test 'testExec36_1' does not support the following OS : " + System.getProperty("os.name"));
+            return;
+        }
+    }
+
+    /**
+     * Test EXEC-36 see https://issues.apache.org/jira/browse/EXEC-36
+     *
+     * Test a complex real example found at
+     * http://blogs.msdn.com/b/astebner/archive/2005/12/13/503471.aspx
+     *
+     * The command line is so weird that it even falls apart under Windows
+     *
+     * @throws Exception the test failed
+     */
+    public void testExec36_2() throws Exception {
+
+        String expected;
+
+        // the original command line
+        // dotnetfx.exe /q:a /c:"install.exe /l ""\Documents and Settings\myusername\Local Settings\Temp\netfx.log"" /q"
+
+        if(OS.isFamilyWindows()) {
+            expected = "dotnetfx.exe\n" +
+                "/q:a\n" +
+                "/c:\"install.exe /l \"\"\\Documents and Settings\\myusername\\Local Settings\\Temp\\netfx.log\"\" /q\"";
+        }
+        else if(OS.isFamilyUnix()) {
+            expected = "dotnetfx.exe\n" +
+                "/q:a\n" +
+                "/c:\"install.exe /l \"\"/Documents and Settings/myusername/Local Settings/Temp/netfx.log\"\" /q\"";
+        }
+        else {
+            System.err.println("The test 'testExec36_3' does not support the following OS : " + System.getProperty("os.name"));
+            return;
+        }
+        
+        CommandLine cmdl;
+        File file = new File("/Documents and Settings/myusername/Local Settings/Temp/netfx.log");
+        Map map = new HashMap();
+        map.put("FILE", file);
+
+        cmdl = new CommandLine(printArgsScript);
+        cmdl.setSubstitutionMap(map);
+        cmdl.addArgument("dotnetfx.exe", false);
+        cmdl.addArgument("/q:a", false);
+        cmdl.addArgument("/c:\"install.exe /l \"\"${FILE}\"\" /q\"", false);
+
+        int exitValue = exec.execute(cmdl);
+        String result = baos.toString().trim();
+        System.out.println("=== Expected ===");
+        System.out.println(expected);
+        System.out.println("=== Result ===");
+        System.out.println(result);
+        assertFalse(exec.isFailure(exitValue));
+
+        if(OS.isFamilyUnix()) {
+        	// the parameters fall literally apart under Windows - need to disable the check for Win32
+        	assertEquals(expected, result);
+        }
+    }
+
+    /**
+     * Test the patch for EXEC-41 (https://issues.apache.org/jira/browse/EXEC-41).
+     *
+     * When a process runs longer than allowed by a configured watchdog's
+     * timeout, the watchdog tries to destroy it and then DefaultExecutor
+     * tries to clean up by joining with all installed pump stream threads.
+     * Problem is, that sometimes the native process doesn't die and thus
+     * streams aren't closed and the stream threads do not complete.
+     *
+     * @throws Exception the test failed
+     */
+    public void testExec41WithStreams() throws Exception {
+
+    	CommandLine cmdLine;
+
+    	if(OS.isFamilyWindows()) {
+    		cmdLine = CommandLine.parse("ping.exe -n 10 -w 1000 127.0.0.1");
+    	}
+    	else if(OS.isFamilyUnix()) {
+    		cmdLine = CommandLine.parse("ping -c 10 127.0.0.1");
+    	}
+    	else {
+    		System.err.println("The test 'testExec41WithStreams' does not support the following OS : " + System.getProperty("os.name"));
+    		return;
+    	}
+
+		DefaultExecutor executor = new DefaultExecutor();
+		ExecuteWatchdog watchdog = new ExecuteWatchdog(2*1000); // allow process no more than 2 secs
+        PumpStreamHandler pumpStreamHandler = new PumpStreamHandler( System.out, System.err);
+        // this method was part of the patch I reverted
+        // pumpStreamHandler.setAlwaysWaitForStreamThreads(false);
+
+		executor.setWatchdog(watchdog);
+        executor.setStreamHandler(pumpStreamHandler);
+
+		long startTime = System.currentTimeMillis();
+
+		try {
+			executor.execute(cmdLine);
+		} catch (ExecuteException e) {
+			// nothing to do
+		}
+
+        long duration = System.currentTimeMillis() - startTime;
+
+		System.out.println("Process completed in " + duration +" millis; below is its output");
+
+		if (watchdog.killedProcess()) {
+			System.out.println("Process timed out and was killed by watchdog.");
+		}
+
+        assertTrue("The process was killed by the watchdog", watchdog.killedProcess());
+        assertTrue("Skipping the Thread.join() did not work", duration < 9000);
+    }
+
+    /**
+     * Test EXEC-41 with a disabled PumpStreamHandler to check if we could return
+     * immediately after killing the process (no streams implies no blocking
+     * stream pumper threads). But you have to be 100% sure that the subprocess
+     * is not writing to 'stdout' and 'stderr'.
+     *
+     * For this test we are using the batch file - under Windows the 'ping'
+     * process can't be killed (not supported by Win32) and will happily
+     * run the given time (e.g. 10 seconds) even hwen the batch file is already
+     * killed. 
+     *
+     * @throws Exception the test failed
+     */
+    public void testExec41WithoutStreams() throws Exception {
+
+		CommandLine cmdLine = new CommandLine(pingScript);
+		cmdLine.addArgument("10"); // sleep 10 secs
+		DefaultExecutor executor = new DefaultExecutor();
+		ExecuteWatchdog watchdog = new ExecuteWatchdog(2*1000); // allow process no more than 2 secs
+
+        // create a custom "PumpStreamHandler" doing no pumping at all
+        PumpStreamHandler pumpStreamHandler = new PumpStreamHandler(null, null, null);
+        
+		executor.setWatchdog(watchdog);
+        executor.setStreamHandler(pumpStreamHandler);
+
+		long startTime = System.currentTimeMillis();
+
+		try {
+			executor.execute(cmdLine);
+		} catch (ExecuteException e) {
+			System.out.println(e);
+		}
+
+        long duration = System.currentTimeMillis() - startTime;
+
+		System.out.println("Process completed in " + duration +" millis; below is its output");
+
+		if (watchdog.killedProcess()) {
+			System.out.println("Process timed out and was killed.");
+		}
+
+        assertTrue("The process was killed by the watchdog", watchdog.killedProcess());
+        assertTrue("Skipping the Thread.join() did not work, duration="+duration, duration < 9000);
+    }
+
+    /**
+     * Test EXEC-44 (https://issues.apache.org/jira/browse/EXEC-44).
+     *
+     * Because the ExecuteWatchdog is the only way to destroy asynchronous
+     * processes, it should be possible to set it to an infinite timeout,
+     * for processes which should not timeout, but manually destroyed
+     * under some circumstances.
+     *
+     * @throws Exception the test failed
+     */
+    public void testExec44() throws Exception {
+
+        CommandLine cl = new CommandLine(foreverTestScript);
+        DefaultExecuteResultHandler resultHandler = new DefaultExecuteResultHandler();
+        ExecuteWatchdog watchdog = new ExecuteWatchdog(ExecuteWatchdog.INFINITE_TIMEOUT);
+
+        exec.setWatchdog(watchdog);
+        exec.execute(cl, resultHandler);
+
+        // wait for script to run
+        Thread.sleep(5000);
+        assertTrue("The watchdog is watching the process", watchdog.isWatching());
+
+        // terminate it
+        watchdog.destroyProcess();
+        assertTrue("The watchdog has killed the process", watchdog.killedProcess());
+        assertFalse("The watchdog is no longer watching any process", watchdog.isWatching());
+    }
+
+    // ======================================================================
+    // === Long running tests
+    // ======================================================================
+
+    /**
+     * Start any processes in a loop to make sure that we do
+     * not leave any handles/resources open.
+     *
+     * @throws Exception the test failed
+     */
+    public void _testExecuteStability() throws Exception {
+
+        // make a plain-vanilla test
+        for(int i=0; i<100; i++) {
+            Map env = new HashMap();
+            env.put("TEST_ENV_VAR", new Integer(i));
+            CommandLine cl = new CommandLine(testScript);
+            int exitValue = exec.execute(cl,env);
+            assertFalse(exec.isFailure(exitValue));
+            assertEquals("FOO." + i + ".", baos.toString().trim());
+            baos.reset();
+        }
+
+        // now be nasty and use the watchdog to kill out sub-processes
+        for(int i=0; i<100; i++) {
+            Map env = new HashMap();
+            env.put("TEST_ENV_VAR", new Integer(i));
+            DefaultExecuteResultHandler resultHandler = new DefaultExecuteResultHandler();
+            CommandLine cl = new CommandLine(foreverTestScript);
+            ExecuteWatchdog watchdog = new ExecuteWatchdog(500);
+            exec.setWatchdog(watchdog);
+            exec.execute(cl, env, resultHandler);
+            resultHandler.waitFor(WAITFOR_TIMEOUT);
+            assertTrue("ResultHandler received a result", resultHandler.hasResult());
+            assertNotNull(resultHandler.getException());
+            baos.reset();
+        }
+    }
+
+
+    // ======================================================================
+    // === Helper methods
+    // ======================================================================
+
+    private String readFile(File file) throws Exception {
+
+        String text;
+        StringBuffer contents = new StringBuffer();
+        BufferedReader reader = new BufferedReader(new FileReader(file));        
+
+        while ((text = reader.readLine()) != null)
+        {
+            contents.append(text)
+                .append(System.getProperty(
+                    "line.separator"));
+        }
+        reader.close();
+        return contents.toString();
+    }
+
+    private int getOccurrences(String data, char c) {
+
+        int result = 0;
+
+        for(int i=0; i<data.length(); i++) {
+            if(data.charAt(i) == c) {
+                result++;
+            }
+        }
+
+        return result;
+    }
+}
diff --git a/src/test/java/org/apache/commons/exec/LogOutputStreamTest.java b/src/test/java/org/apache/commons/exec/LogOutputStreamTest.java
new file mode 100644
index 0000000..caebb34
--- /dev/null
+++ b/src/test/java/org/apache/commons/exec/LogOutputStreamTest.java
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.commons.exec;
+
+import junit.framework.TestCase;
+
+import java.io.File;
+import java.io.OutputStream;
+
+/**
+ * Test the LogOutputStream.
+ */
+public class LogOutputStreamTest extends TestCase
+{
+
+    private Executor exec = new DefaultExecutor();
+    private File testDir = new File("src/test/scripts");
+    private OutputStream systemOut;
+    private File environmentScript = TestUtil.resolveScriptForOS(testDir + "/environment");
+
+    static{
+        // turn on debug mode and throw an exception for each encountered problem
+        System.setProperty("org.apache.commons.exec.lenient", "false");
+        System.setProperty("org.apache.commons.exec.debug", "true");
+    }
+
+
+    protected void setUp() throws Exception {
+        System.out.println(">>> Executing " + getName() + " ...");
+        this.systemOut = new SystemLogOutputStream(1);
+        this.exec.setStreamHandler(new PumpStreamHandler(systemOut, systemOut));
+    }
+
+    protected void tearDown() throws Exception {
+        this.systemOut.close();
+    }
+
+    // ======================================================================
+    // Start of regression tests
+    // ======================================================================
+
+    public void testStdout() throws Exception {
+        CommandLine cl = new CommandLine(environmentScript);
+        int exitValue = exec.execute(cl);
+        assertFalse(exec.isFailure(exitValue));
+    }
+
+    // ======================================================================
+    // Helper classes
+    // ======================================================================
+
+    private class SystemLogOutputStream extends LogOutputStream {
+
+        private SystemLogOutputStream(int level) {
+            super(level);
+        }
+
+        protected void processLine(String line, int level) {
+            System.out.println(line);
+        }
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/exec/TestRunner.java b/src/test/java/org/apache/commons/exec/TestRunner.java
new file mode 100644
index 0000000..a24feda
--- /dev/null
+++ b/src/test/java/org/apache/commons/exec/TestRunner.java
@@ -0,0 +1,58 @@
+/* 
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.apache.commons.exec;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestResult;
+import junit.framework.TestSuite;
+import org.apache.commons.exec.environment.EnvironmentUtilTest;
+import org.apache.commons.exec.util.MapUtilTest;
+
+/**
+ * A stand-alone JUnit invocation to allow running JUnit tests without
+ * having ANT or M2 installed.
+ */
+public class TestRunner extends TestCase {
+
+    public static Test suite() {
+        TestSuite suite = new TestSuite("TestRunner");
+        suite.addTestSuite(CommandLineTest.class);
+        suite.addTestSuite(DefaultExecutorTest.class);
+        suite.addTestSuite(EnvironmentUtilTest.class);
+        suite.addTestSuite(MapUtilTest.class);
+        suite.addTestSuite(TestUtilTest.class);
+        return suite;
+    }
+
+    public static void main(String[] args) {
+
+        Test test = TestRunner.suite();
+        junit.textui.TestRunner testRunner = new junit.textui.TestRunner(System.out);
+        TestResult testResult = testRunner.doRun(test);
+
+        if(!testResult.wasSuccessful()) {
+            System.exit(1);
+        }
+
+        // not calling System.exit(0) here to ensure that the application
+        // properly terminates (e.g. not waiting for any background threads
+        // indicating serious problems
+        return;
+    }
+}
diff --git a/src/test/java/org/apache/commons/exec/TestUtil.java b/src/test/java/org/apache/commons/exec/TestUtil.java
new file mode 100644
index 0000000..52abd3b
--- /dev/null
+++ b/src/test/java/org/apache/commons/exec/TestUtil.java
@@ -0,0 +1,84 @@
+/* 
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+package org.apache.commons.exec;
+
+import java.io.File;
+import java.util.Arrays;
+
+import junit.framework.AssertionFailedError;
+import junit.framework.TestCase;
+
+public final class TestUtil {
+
+    private TestUtil() {
+    }
+
+    public static File resolveScriptForOS(String script) {
+        if (OS.isFamilyWindows()) {
+            return new File(script + ".bat");
+        } else if (OS.isFamilyUnix()) {
+            return new File(script + ".sh");
+        } else if (OS.isFamilyOpenVms()) {
+            return new File(script + ".dcl");
+        } else {
+            throw new AssertionFailedError("Test not supported for this OS");
+        }
+    }
+    
+    /**
+     * Get success and fail return codes used by the test scripts
+     * @return int array[2] = {ok, success}
+     */
+    public static int[] getTestScriptCodesForOS() {
+        if (OS.isFamilyWindows()) {
+            return new int[]{0,1};
+        } else if (OS.isFamilyUnix()) {
+            return new int[]{0,1};
+        } else if (OS.isFamilyOpenVms()) {
+            return new int[]{1,2};
+        } else {
+            throw new AssertionFailedError("Test not supported for this OS");
+        }
+    }
+    
+    
+    public static void assertEquals(Object[] expected, Object[] actual, boolean orderSignificant) {
+    	
+    	if(expected == null && actual == null) {
+    		// all good
+    	} else if (actual == null) {
+    		throw new AssertionFailedError("Expected non null array");
+    	} else if (expected == null) {
+    		throw new AssertionFailedError("Expected null array");
+    	} else {
+    		if(expected.length != actual.length) {
+    			throw new AssertionFailedError("Arrays not of same length");
+    		}
+    		
+    		if(!orderSignificant) {
+    			Arrays.sort(expected);
+    			Arrays.sort(actual);
+    		}
+    		
+    		for (int i = 0; i < actual.length; i++) {
+				TestCase.assertEquals("Array element at " + i, expected[i], actual[i]);
+			}
+    	}
+    }
+}
diff --git a/src/test/java/org/apache/commons/exec/TestUtilTest.java b/src/test/java/org/apache/commons/exec/TestUtilTest.java
new file mode 100644
index 0000000..ff08151
--- /dev/null
+++ b/src/test/java/org/apache/commons/exec/TestUtilTest.java
@@ -0,0 +1,94 @@
+/* 
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+package org.apache.commons.exec;
+
+import junit.framework.AssertionFailedError;
+import junit.framework.TestCase;
+
+public class TestUtilTest extends TestCase {
+
+	public void testAssertArrayEquals() {
+		String[] expected = new String[]{"aaa", "bbb", "ccc"};
+		String[] actual = new String[]{"aaa", "bbb", "ccc"};
+		
+		TestUtil.assertEquals(expected, actual, true);
+	}
+
+	public void testAssertArrayNotEquals() {
+		String[] expected = new String[]{"aaa", "bbb", "ccc"};
+		String[] actual = new String[]{"aaa", "ddd", "ccc"};
+		
+		try{
+			TestUtil.assertEquals(expected, actual, true);
+			fail("Must throw AssertionFailedError");
+		} catch(AssertionFailedError e) {
+			// OK
+		}
+	}
+
+	public void testAssertArrayNotOrderEquals() {
+		String[] expected = new String[]{"aaa", "ccc", "bbb"};
+		String[] actual = new String[]{"aaa", "ddd", "ccc"};
+		
+		try{
+			TestUtil.assertEquals(expected, actual, true);
+			fail("Must throw AssertionFailedError");
+		} catch(AssertionFailedError e) {
+			// OK
+		}
+	}
+	
+	public void testAssertArrayEqualsOrderNotSignificant() {
+		String[] expected = new String[]{"aaa", "ccc", "bbb"};
+		String[] actual = new String[]{"aaa", "bbb", "ccc"};
+		
+		TestUtil.assertEquals(expected, actual, false);
+	}
+	
+	public void testAssertArrayEqualsNullNull() {
+		String[] expected = null;
+		String[] actual = null;
+		
+		TestUtil.assertEquals(expected, actual, false);
+	}
+
+	public void testAssertArrayEqualsActualNull() {
+		String[] expected = new String[]{"aaa", "ccc", "bbb"};
+		String[] actual = null;
+		
+		try{
+			TestUtil.assertEquals(expected, actual, true);
+			fail("Must throw AssertionFailedError");
+		} catch(AssertionFailedError e) {
+			// OK
+		}
+	}
+	
+	public void testAssertArrayEqualsExpectedNull() {
+		String[] expected = null;
+		String[] actual = new String[]{"aaa", "ddd", "ccc"};
+		
+		try{
+			TestUtil.assertEquals(expected, actual, true);
+			fail("Must throw AssertionFailedError");
+		} catch(AssertionFailedError e) {
+			// OK
+		}
+	}
+}
diff --git a/src/test/java/org/apache/commons/exec/TutorialTest.java b/src/test/java/org/apache/commons/exec/TutorialTest.java
new file mode 100644
index 0000000..b14663b
--- /dev/null
+++ b/src/test/java/org/apache/commons/exec/TutorialTest.java
@@ -0,0 +1,147 @@
+/* 
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+package org.apache.commons.exec;
+
+import junit.framework.TestCase;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.HashMap;
+
+/**
+ * An example based on the tutorial where the user can can safely play with
+ * <ul>
+ *  <li>blocking or non-blocking print jobs
+ *  <li>with print job timeouts to trigger the <code>ExecuteWatchdog</code>
+ *  <li>with the <code>exitValue</code> returned from the print script
+ * </ul>
+ */
+public class TutorialTest extends TestCase {
+
+    /** the directory to pick up the test scripts */
+    private File testDir = new File("src/test/scripts");
+
+    /** simulates a PDF print job */
+    private File acroRd32Script = TestUtil.resolveScriptForOS(testDir + "/acrord32");
+
+    public void testTutorialExample() throws Exception {
+
+        long printJobTimeout = 15000;
+        boolean printInBackground = false;
+        File pdfFile = new File("/Documents and Settings/foo.pdf");
+
+        PrintResultHandler printResult;
+
+        try {
+            // printing takes around 10 seconds            
+            System.out.println("[main] Preparing print job ...");
+            printResult = print(pdfFile, printJobTimeout, printInBackground);
+            System.out.println("[main] Successfully sent the print job ...");
+        }
+        catch(Exception e) {
+            e.printStackTrace();
+            fail("[main] Printing of the following document failed : " + pdfFile.getAbsolutePath());
+            throw e;
+        }
+
+        // come back to check the print result
+        System.out.println("[main] Test is exiting but waiting for the print job to finish...");
+        printResult.waitFor();
+        System.out.println("[main] The print job has finished ...");
+    }
+
+    /**
+     * Simulate printing a PDF document.
+     *
+     * @param file the file to print
+     * @param printJobTimeout the printJobTimeout (ms) before the watchdog terminates the print process
+     * @param printInBackground printing done in the background or blocking
+     * @return a print result handler (implementing a future)
+     * @throws IOException the test failed
+     */
+    public PrintResultHandler print(File file, long printJobTimeout, boolean printInBackground)
+            throws IOException {
+
+        int exitValue;
+        ExecuteWatchdog watchdog = null;
+        PrintResultHandler resultHandler;
+
+        // build up the command line to using a 'java.io.File'
+        HashMap map = new HashMap();
+        map.put("file", file);
+        CommandLine commandLine = new CommandLine(acroRd32Script);
+        commandLine.addArgument("/p");
+        commandLine.addArgument("/h");
+        commandLine.addArgument("${file}");
+        commandLine.setSubstitutionMap(map);
+
+        // create the executor and consider the exitValue '1' as success
+        Executor executor = new DefaultExecutor();
+        executor.setExitValue(1);
+        
+        // create a watchdog if requested
+        if(printJobTimeout > 0) {
+            watchdog = new ExecuteWatchdog(printJobTimeout);
+            executor.setWatchdog(watchdog);
+        }
+
+        // pass a "ExecuteResultHandler" when doing background printing
+        if(printInBackground) {
+            System.out.println("[print] Executing non-blocking print job  ...");
+            resultHandler = new PrintResultHandler(watchdog);
+            executor.execute(commandLine, resultHandler);
+        }
+        else {
+            System.out.println("[print] Executing blocking print job  ...");
+            exitValue = executor.execute(commandLine);
+            resultHandler = new PrintResultHandler(exitValue);
+        }
+
+        return resultHandler;
+    }
+
+    private class PrintResultHandler extends DefaultExecuteResultHandler {
+
+        private ExecuteWatchdog watchdog;
+
+        public PrintResultHandler(ExecuteWatchdog watchdog)
+        {
+            this.watchdog = watchdog;
+        }
+
+        public PrintResultHandler(int exitValue) {
+            super.onProcessComplete(exitValue);
+        }
+        
+        public void onProcessComplete(int exitValue) {
+            super.onProcessComplete(exitValue);
+            System.out.println("[resultHandler] The document was successfully printed ...");
+        }
+
+        public void onProcessFailed(ExecuteException e){
+            super.onProcessFailed(e);
+            if(watchdog != null && watchdog.killedProcess()) {
+                System.err.println("[resultHandler] The print process timed out");
+            }
+            else {
+                System.err.println("[resultHandler] The print process failed to do : " + e.getMessage());
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/test/java/org/apache/commons/exec/environment/EnvironmentUtilTest.java b/src/test/java/org/apache/commons/exec/environment/EnvironmentUtilTest.java
new file mode 100644
index 0000000..4607e17
--- /dev/null
+++ b/src/test/java/org/apache/commons/exec/environment/EnvironmentUtilTest.java
@@ -0,0 +1,116 @@
+/* 
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+package org.apache.commons.exec.environment;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.Map;
+
+import junit.framework.TestCase;
+
+import org.apache.commons.exec.OS;
+import org.apache.commons.exec.TestUtil;
+
+public class EnvironmentUtilTest extends TestCase {
+
+    /**
+     * Tests the behaviour of the EnvironmentUtils.toStrings()
+     * when using a <code>null</code> environment.
+     */
+    public void testToStrings() {
+        // check for a non-existing environment when passing null
+        TestUtil.assertEquals(null, EnvironmentUtils.toStrings(null), false);
+        // check for an environment when filling in two variables
+        Map env = new HashMap();
+        TestUtil.assertEquals(new String[0], EnvironmentUtils.toStrings(env), false);
+        env.put("foo2", "bar2");
+        env.put("foo", "bar");
+        String[] envStrings = EnvironmentUtils.toStrings(env);
+        String[] expected = new String[]{"foo=bar", "foo2=bar2"};
+        TestUtil.assertEquals(expected, envStrings, false);
+    }
+
+    /**
+     * Test to access the environment variables of the current
+     * process. Please note that this test does not run on
+     * java-gjc.
+     *
+     * @throws IOException the test failed
+     */
+    public void testGetProcEnvironment() throws IOException {
+        Map procEnvironment = EnvironmentUtils.getProcEnvironment();
+        // we assume that there is at least one environment variable
+        // for this process, i.e. $JAVA_HOME
+        assertTrue("Expecting non-zero environment size", procEnvironment.size() > 0);
+        String[] envArgs = EnvironmentUtils.toStrings(procEnvironment);
+        for(int i=0; i<envArgs.length; i++) {
+            assertNotNull("Entry "+i+" should not be null",envArgs[i]);
+            assertTrue("Entry "+i+" should not be empty",envArgs[i].length() > 0);
+            System.out.println(envArgs[i]);
+        }
+    }
+
+    /**
+     * On Windows platforms test that accessing environment variables
+     * can be done in a case-insensitive way, e.g. "PATH", "Path" and
+     * "path" would reference the same environment variable.
+     *
+     * @throws IOException the test failed
+     */
+    public void testGetProcEnvironmentCaseInsensitiveLookup() throws IOException {
+        // run tests only on windows platforms
+        if (!OS.isFamilyWindows()) {
+            return;
+        }
+
+        // ensure that we have the same value for upper and lowercase keys
+        Map procEnvironment = EnvironmentUtils.getProcEnvironment();
+        for (Iterator it = procEnvironment.entrySet().iterator(); it.hasNext();) {
+            Map.Entry entry = (Map.Entry) it.next();
+            String key = (String) entry.getKey();
+            String value = (String) entry.getValue();
+            assertEquals(value, procEnvironment.get(key.toLowerCase(Locale.ENGLISH)));
+            assertEquals(value, procEnvironment.get(key.toUpperCase(Locale.ENGLISH)));
+        }
+
+        // add an environment variable and check access
+        EnvironmentUtils.addVariableToEnvironment( procEnvironment, "foo=bar" );
+        assertEquals("bar", procEnvironment.get("FOO"));
+        assertEquals("bar", procEnvironment.get("Foo"));
+        assertEquals("bar", procEnvironment.get("foo"));
+    }
+
+    /**
+     * Accessing environment variables is case-sensitive or not depending
+     * on the operating system but the values of the environment variable
+     * are always case-sensitive. So make sure that this assumption holds
+     * on all operating systems.
+     *
+     * @throws Exception the test failed
+     */
+    public void testCaseInsensitiveVariableLookup() throws Exception {
+        Map procEnvironment = EnvironmentUtils.getProcEnvironment();
+        // Check that case is preserved for values
+        EnvironmentUtils.addVariableToEnvironment( procEnvironment, "foo=bAr" );
+        assertEquals("bAr", procEnvironment.get("foo"));
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/exec/util/MapUtilTest.java b/src/test/java/org/apache/commons/exec/util/MapUtilTest.java
new file mode 100644
index 0000000..f34c1d3
--- /dev/null
+++ b/src/test/java/org/apache/commons/exec/util/MapUtilTest.java
@@ -0,0 +1,74 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+package org.apache.commons.exec.util;
+
+import org.apache.commons.exec.environment.EnvironmentUtils;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import junit.framework.TestCase;
+
+public class MapUtilTest extends TestCase
+{
+    /**
+     * Test copying of map
+     */
+    public void testCopyMap() throws Exception {
+
+        HashMap procEnvironment = new HashMap();
+        procEnvironment.put("JAVA_HOME", "/usr/opt/java");
+
+        Map result = MapUtils.copy(procEnvironment);
+        assertTrue(result.size() == 1);
+        assertTrue(procEnvironment.size() == 1);
+        assertEquals("/usr/opt/java", result.get("JAVA_HOME"));
+
+        result.remove("JAVA_HOME");
+        assertTrue(result.size() == 0);
+        assertTrue(procEnvironment.size() == 1);
+    }
+
+    /**
+     * Test merging of maps
+     */
+    public void testMergeMap() throws Exception {
+
+        Map procEnvironment = EnvironmentUtils.getProcEnvironment();
+        HashMap applicationEnvironment = new HashMap();
+
+        applicationEnvironment.put("appMainClass", "foo.bar.Main");
+        Map result = MapUtils.merge(procEnvironment, applicationEnvironment);
+        assertTrue((procEnvironment.size() + applicationEnvironment.size()) == result.size());
+        assertEquals("foo.bar.Main", result.get("appMainClass"));
+    }
+
+    /**
+     * Test prefixing of map
+     */
+    public void testPrefixMap() throws Exception {
+
+        HashMap procEnvironment = new HashMap();
+        procEnvironment.put("JAVA_HOME", "/usr/opt/java");
+
+        Map result = MapUtils.prefix(procEnvironment, "env");
+        assertTrue(procEnvironment.size() == result.size());
+        assertEquals("/usr/opt/java", result.get("env.JAVA_HOME"));
+    }
+}
\ No newline at end of file
diff --git a/src/test/java/org/apache/commons/exec/util/StringUtilTest.java b/src/test/java/org/apache/commons/exec/util/StringUtilTest.java
new file mode 100644
index 0000000..5965892
--- /dev/null
+++ b/src/test/java/org/apache/commons/exec/util/StringUtilTest.java
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+package org.apache.commons.exec.util;
+
+import junit.framework.TestCase;
+import org.apache.commons.exec.environment.EnvironmentUtils;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class StringUtilTest extends TestCase
+{
+    /**
+     * Test no string substitution
+     */
+    public void testNoStringSubstitution() throws Exception
+    {
+        Map vars = new HashMap();
+        vars.put("foo", "FOO");
+        vars.put("bar", "BAR");
+
+        assertEquals("This is a FOO & BAR test", StringUtils.stringSubstitution("This is a FOO & BAR test", vars, true).toString());
+    }
+
+    /**
+     * Test a default string substitution, e.g. all placeholders
+     * are expanded.
+     */
+    public void testDefaultStringSubstitution() throws Exception 
+    {
+        Map vars = new HashMap();
+        vars.put("foo", "FOO");
+        vars.put("bar", "BAR");
+
+        assertEquals("This is a FOO & BAR test", StringUtils.stringSubstitution("This is a ${foo} & ${bar} test", vars, true).toString());
+        assertEquals("This is a FOO & BAR test", StringUtils.stringSubstitution("This is a ${foo} & ${bar} test", vars, false).toString());
+    }
+
+    /**
+     * Test an incomplete string substitution where not all placeholders
+     * are expanded.
+     */
+    public void testIncompleteSubstitution() throws Exception {
+
+        Map vars = new HashMap();
+        vars.put("foo", "FOO");
+
+        assertEquals("This is a FOO & ${bar} test",  StringUtils.stringSubstitution("This is a ${foo} & ${bar} test", vars, true).toString());
+
+        try
+        {
+            StringUtils.stringSubstitution("This is a ${foo} & ${bar} test", vars, false).toString();
+            fail();
+        }
+        catch(RuntimeException e)
+        {
+            // nothing to do
+        }
+    }
+
+    /**
+     * Test a erroneous template.
+     */
+    public void testErroneousTemplate() throws Exception
+    {
+        Map vars = new HashMap();
+        vars.put("foo", "FOO");
+
+        assertEquals("This is a FOO & ${}} test",  StringUtils.stringSubstitution("This is a ${foo} & ${}} test", vars, true).toString());
+    }
+}
\ No newline at end of file
diff --git a/src/test/scripts/acrord32.bat b/src/test/scripts/acrord32.bat
new file mode 100644
index 0000000..7971e14
--- /dev/null
+++ b/src/test/scripts/acrord32.bat
@@ -0,0 +1,21 @@
+ at ECHO OFF
+
+REM Licensed to the Apache Software Foundation (ASF) under one or more
+REM contributor license agreements.  See the NOTICE file distributed with
+REM this work for additional information regarding copyright ownership.
+REM The ASF licenses this file to You under the Apache License, Version 2.0
+REM (the "License"); you may not use this file except in compliance with
+REM the License.  You may obtain a copy of the License at
+REM
+REM      http://www.apache.org/licenses/LICENSE-2.0
+REM
+REM Unless required by applicable law or agreed to in writing, software
+REM distributed under the License is distributed on an "AS IS" BASIS,
+REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+REM See the License for the specific language governing permissions and
+REM limitations under the License.
+
+echo "[acrord32] Printing the following document : '%3'"
+ping -n 10 -w 1000 127.0.0.1 > nul
+echo "[acrord32] Finished printing"
+exit 1
diff --git a/src/test/scripts/acrord32.sh b/src/test/scripts/acrord32.sh
new file mode 100755
index 0000000..dbe185a
--- /dev/null
+++ b/src/test/scripts/acrord32.sh
@@ -0,0 +1,29 @@
+#!/bin/sh
+
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# Simulate printing a PDF document
+
+echo "[acrord32] Printing the following document : $3"
+for i in {1..10}
+do
+   sleep 1
+done
+echo "[acrord32] Finished printing"
+exit 1
+
diff --git a/src/test/scripts/environment.bat b/src/test/scripts/environment.bat
new file mode 100644
index 0000000..d462d42
--- /dev/null
+++ b/src/test/scripts/environment.bat
@@ -0,0 +1,22 @@
+ at ECHO OFF
+
+REM Little batch file to run nearly foerver
+REM see http://malektips.com/dos0017.html
+REM
+REM Licensed to the Apache Software Foundation (ASF) under one or more
+REM contributor license agreements.  See the NOTICE file distributed with
+REM this work for additional information regarding copyright ownership.
+REM The ASF licenses this file to You under the Apache License, Version 2.0
+REM (the "License"); you may not use this file except in compliance with
+REM the License.  You may obtain a copy of the License at
+REM
+REM      http://www.apache.org/licenses/LICENSE-2.0
+REM
+REM Unless required by applicable law or agreed to in writing, software
+REM distributed under the License is distributed on an "AS IS" BASIS,
+REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+REM See the License for the specific language governing permissions and
+REM limitations under the License.
+
+REM print the environment variables
+set
\ No newline at end of file
diff --git a/src/test/scripts/environment.dcl b/src/test/scripts/environment.dcl
new file mode 100644
index 0000000..8ee0a9f
--- /dev/null
+++ b/src/test/scripts/environment.dcl
@@ -0,0 +1,22 @@
+$!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+$!
+$! Licensed to the Apache Software Foundation (ASF) under one or more
+$! contributor license agreements.  See the NOTICE file distributed with
+$! this work for additional information regarding copyright ownership.
+$! The ASF licenses this file to You under the Apache License, Version 2.0
+$! (the "License"); you may not use this file except in compliance with
+$! the License.  You may obtain a copy of the License at
+$!
+$!      http://www.apache.org/licenses/LICENSE-2.0
+$!
+$! Unless required by applicable law or agreed to in writing, software
+$! distributed under the License is distributed on an "AS IS" BASIS,
+$! WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+$! See the License for the specific language governing permissions and
+$! limitations under the License.
+$! 
+$!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+$!
+$! print the "environment" variables
+$!
+$ show symbol *
\ No newline at end of file
diff --git a/src/test/scripts/environment.sh b/src/test/scripts/environment.sh
new file mode 100755
index 0000000..a875b5c
--- /dev/null
+++ b/src/test/scripts/environment.sh
@@ -0,0 +1,21 @@
+#!/bin/sh
+
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# print the environment variables
+env
\ No newline at end of file
diff --git a/src/test/scripts/error.bat b/src/test/scripts/error.bat
new file mode 100644
index 0000000..3902b20
--- /dev/null
+++ b/src/test/scripts/error.bat
@@ -0,0 +1,22 @@
+ at ECHO OFF
+
+REM Licensed to the Apache Software Foundation (ASF) under one or more
+REM  contributor license agreements.  See the NOTICE file distributed with
+REM  this work for additional information regarding copyright ownership.
+REM  The ASF licenses this file to You under the Apache License, Version 2.0
+REM  (the "License"); you may not use this file except in compliance with
+REM  the License.  You may obtain a copy of the License at
+REM
+REM      http://www.apache.org/licenses/LICENSE-2.0
+REM
+REM  Unless required by applicable law or agreed to in writing, software
+REM  distributed under the License is distributed on an "AS IS" BASIS,
+REM  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+REM  See the License for the specific language governing permissions and
+REM  limitations under the License.
+
+REM This test script will return an error exit code
+
+ at echo FOO.%TEST_ENV_VAR%.%1
+
+EXIT 1
diff --git a/src/test/scripts/error.dcl b/src/test/scripts/error.dcl
new file mode 100644
index 0000000..061ad00
--- /dev/null
+++ b/src/test/scripts/error.dcl
@@ -0,0 +1,25 @@
+$!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+$!
+$! Licensed to the Apache Software Foundation (ASF) under one or more
+$! contributor license agreements.  See the NOTICE file distributed with
+$! this work for additional information regarding copyright ownership.
+$! The ASF licenses this file to You under the Apache License, Version 2.0
+$! (the "License"); you may not use this file except in compliance with
+$! the License.  You may obtain a copy of the License at
+$!
+$!      http://www.apache.org/licenses/LICENSE-2.0
+$!
+$! Unless required by applicable law or agreed to in writing, software
+$! distributed under the License is distributed on an "AS IS" BASIS,
+$! WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+$! See the License for the specific language governing permissions and
+$! limitations under the License.
+$! 
+$!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+$!
+$! do something and return an error result code
+$!
+$ ENV_VAR=f$trnlnm("TEST_ENV_VAR")
+$ write sys$output "FOO.''ENV_VAR'.''P1'"
+$!
+$ exit %x10000002 ! this is an Error, but does not print a message
\ No newline at end of file
diff --git a/src/test/scripts/error.sh b/src/test/scripts/error.sh
new file mode 100755
index 0000000..8c498dd
--- /dev/null
+++ b/src/test/scripts/error.sh
@@ -0,0 +1,24 @@
+#!/bin/sh
+
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# 
+#
+
+# do something and return en error result code
+
+echo FOO.$TEST_ENV_VAR.$1
+exit 1
diff --git a/src/test/scripts/forever.bat b/src/test/scripts/forever.bat
new file mode 100644
index 0000000..7425f0b
--- /dev/null
+++ b/src/test/scripts/forever.bat
@@ -0,0 +1,27 @@
+ at ECHO OFF
+
+REM Little batch file to run nearly foerver
+REM see http://malektips.com/dos0017.html
+REM
+REM Licensed to the Apache Software Foundation (ASF) under one or more
+REM contributor license agreements.  See the NOTICE file distributed with
+REM this work for additional information regarding copyright ownership.
+REM The ASF licenses this file to You under the Apache License, Version 2.0
+REM (the "License"); you may not use this file except in compliance with
+REM the License.  You may obtain a copy of the License at
+REM
+REM      http://www.apache.org/licenses/LICENSE-2.0
+REM
+REM Unless required by applicable law or agreed to in writing, software
+REM distributed under the License is distributed on an "AS IS" BASIS,
+REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+REM See the License for the specific language governing permissions and
+REM limitations under the License.
+
+REM run an infinite loop so the script will never ever terminate on its behalf
+REM and append a '.' after each second
+
+:LOOP
+  ECHO . >> .\target\forever.txt
+  @ping 127.0.0.1 -n 2 -w 1000 > nul
+GOTO LOOP
\ No newline at end of file
diff --git a/src/test/scripts/forever.dcl b/src/test/scripts/forever.dcl
new file mode 100644
index 0000000..452681f
--- /dev/null
+++ b/src/test/scripts/forever.dcl
@@ -0,0 +1,38 @@
+$!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+$!
+$! Licensed to the Apache Software Foundation (ASF) under one or more
+$! contributor license agreements.  See the NOTICE file distributed with
+$! this work for additional information regarding copyright ownership.
+$! The ASF licenses this file to You under the Apache License, Version 2.0
+$! (the "License"); you may not use this file except in compliance with
+$! the License.  You may obtain a copy of the License at
+$!
+$!      http://www.apache.org/licenses/LICENSE-2.0
+$!
+$! Unless required by applicable law or agreed to in writing, software
+$! distributed under the License is distributed on an "AS IS" BASIS,
+$! WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+$! See the License for the specific language governing permissions and
+$! limitations under the License.
+$! 
+$!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+$!
+$! run an infinite loop so the script will never ever terminate
+$!
+$! Suppress timeout warning
+$ l_msg=f$environment("MESSAGE")
+$ SET MESSAGE  /NOFACILITY  /NOIDENTIFICATION       /NOSEVERITY  /NOTEXT
+$!
+$ SET NOON
+$ ON CONTROL_Y THEN GOTO DONE
+$ close/nolog OUT
+$ open/write OUT [.target]forever.txt ! create the output file
+$LOOP:
+$   write OUT "."
+$   read /prompt="."/time_out=1 sys$command dummy
+$ GOTO LOOP
+$!
+$DONE:
+$ close/nolog OUT
+$! Restore message settings
+$ SET MESSAGE  'l_msg'
\ No newline at end of file
diff --git a/src/test/scripts/forever.sh b/src/test/scripts/forever.sh
new file mode 100755
index 0000000..696d512
--- /dev/null
+++ b/src/test/scripts/forever.sh
@@ -0,0 +1,27 @@
+#!/bin/sh
+
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# run an infinite loop so the script will never ever terminate on its behalf
+# and append a '.' after each second
+
+while test "notempty"
+do
+  sleep 1
+  echo '.\c' >> ./target/forever.txt
+done
\ No newline at end of file
diff --git a/src/test/scripts/ping.bat b/src/test/scripts/ping.bat
new file mode 100644
index 0000000..16bf350
--- /dev/null
+++ b/src/test/scripts/ping.bat
@@ -0,0 +1,24 @@
+ at ECHO OFF
+
+REM Little batch file to run nearly foerver
+REM see http://malektips.com/dos0017.html
+REM
+REM Licensed to the Apache Software Foundation (ASF) under one or more
+REM contributor license agreements.  See the NOTICE file distributed with
+REM this work for additional information regarding copyright ownership.
+REM The ASF licenses this file to You under the Apache License, Version 2.0
+REM (the "License"); you may not use this file except in compliance with
+REM the License.  You may obtain a copy of the License at
+REM
+REM      http://www.apache.org/licenses/LICENSE-2.0
+REM
+REM Unless required by applicable law or agreed to in writing, software
+REM distributed under the License is distributed on an "AS IS" BASIS,
+REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+REM See the License for the specific language governing permissions and
+REM limitations under the License.
+
+REM ping is started as subprocess which runs '%1' seconds
+
+echo "[ping.bat] Blocking for %1 seconds ..."
+ping -n %1 -w 1000 127.0.0.1 > nul
\ No newline at end of file
diff --git a/src/test/scripts/ping.dcl b/src/test/scripts/ping.dcl
new file mode 100644
index 0000000..174024c
--- /dev/null
+++ b/src/test/scripts/ping.dcl
@@ -0,0 +1,23 @@
+$!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+$!
+$! Licensed to the Apache Software Foundation (ASF) under one or more
+$! contributor license agreements.  See the NOTICE file distributed with
+$! this work for additional information regarding copyright ownership.
+$! The ASF licenses this file to You under the Apache License, Version 2.0
+$! (the "License"); you may not use this file except in compliance with
+$! the License.  You may obtain a copy of the License at
+$!
+$!      http://www.apache.org/licenses/LICENSE-2.0
+$!
+$! Unless required by applicable law or agreed to in writing, software
+$! distributed under the License is distributed on an "AS IS" BASIS,
+$! WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+$! See the License for the specific language governing permissions and
+$! limitations under the License.
+$! 
+$!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+$!
+$! ping is started as subprocess which runs for 'P1' seconds
+$!
+$ write sys$output "[ping.dcl] Blocking for ''P1' seconds ..."
+$ tcpip ping 127.0.0.1 /number_packets='P1 /wait=1 
\ No newline at end of file
diff --git a/src/test/scripts/ping.sh b/src/test/scripts/ping.sh
new file mode 100755
index 0000000..cfa4383
--- /dev/null
+++ b/src/test/scripts/ping.sh
@@ -0,0 +1,23 @@
+#!/bin/sh
+
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# ping is started as subprocess which runs '$1' seconds
+
+echo "[ping.sh] Blocking for %1 seconds ..."
+ping -c $1 127.0.0.1
diff --git a/src/test/scripts/printargs.bat b/src/test/scripts/printargs.bat
new file mode 100644
index 0000000..721664d
--- /dev/null
+++ b/src/test/scripts/printargs.bat
@@ -0,0 +1,25 @@
+ at ECHO OFF
+
+REM Licensed to the Apache Software Foundation (ASF) under one or more
+REM contributor license agreements.  See the NOTICE file distributed with
+REM this work for additional information regarding copyright ownership.
+REM The ASF licenses this file to You under the Apache License, Version 2.0
+REM (the "License"); you may not use this file except in compliance with
+REM the License.  You may obtain a copy of the License at
+REM
+REM      http://www.apache.org/licenses/LICENSE-2.0
+REM
+REM Unless required by applicable law or agreed to in writing, software
+REM distributed under the License is distributed on an "AS IS" BASIS,
+REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+REM See the License for the specific language governing permissions and
+REM limitations under the License.
+
+REM checking for emptiness was tricky - see http://www.robvanderwoude.com/parameters.php
+
+:Loop
+IF [%1]==[] GOTO Continue
+    @ECHO "%1"
+SHIFT
+GOTO Loop
+:Continue
\ No newline at end of file
diff --git a/src/test/scripts/printargs.dcl b/src/test/scripts/printargs.dcl
new file mode 100644
index 0000000..9c94179
--- /dev/null
+++ b/src/test/scripts/printargs.dcl
@@ -0,0 +1,30 @@
+$!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+$!
+$! Licensed to the Apache Software Foundation (ASF) under one or more
+$! contributor license agreements.  See the NOTICE file distributed with
+$! this work for additional information regarding copyright ownership.
+$! The ASF licenses this file to You under the Apache License, Version 2.0
+$! (the "License"); you may not use this file except in compliance with
+$! the License.  You may obtain a copy of the License at
+$!
+$!      http://www.apache.org/licenses/LICENSE-2.0
+$!
+$! Unless required by applicable law or agreed to in writing, software
+$! distributed under the License is distributed on an "AS IS" BASIS,
+$! WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+$! See the License for the specific language governing permissions and
+$! limitations under the License.
+$! 
+$!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+$!
+$! Print arguments
+$!
+$! Crude, but effective. Works even if argument contains un-doubled double-quotes
+$ if f$length(P1) .gt. 0 then write sys$output P1
+$ if f$length(P2) .gt. 0 then write sys$output P2
+$ if f$length(P3) .gt. 0 then write sys$output P3
+$ if f$length(P4) .gt. 0 then write sys$output P4
+$ if f$length(P5) .gt. 0 then write sys$output P5
+$ if f$length(P6) .gt. 0 then write sys$output P6
+$ if f$length(P7) .gt. 0 then write sys$output P7
+$ if f$length(P8) .gt. 0 then write sys$output P8
\ No newline at end of file
diff --git a/src/test/scripts/printargs.sh b/src/test/scripts/printargs.sh
new file mode 100755
index 0000000..047e50e
--- /dev/null
+++ b/src/test/scripts/printargs.sh
@@ -0,0 +1,27 @@
+#!/bin/sh
+
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# Helper script to print out the command line arguments
+
+while [ $# -gt 0 ]
+do
+    echo "$1"
+    shift
+done
+
diff --git a/src/test/scripts/redirect.sh b/src/test/scripts/redirect.sh
new file mode 100755
index 0000000..dce5c99
--- /dev/null
+++ b/src/test/scripts/redirect.sh
@@ -0,0 +1,30 @@
+#!/bin/sh
+
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# read from stdin and output to stdout
+
+while read myline
+do
+  echo "stdout: $myline"
+done
+
+echo 1>&2 "stderr: Finished reading from stdin"
+
+exit 0
+
diff --git a/src/test/scripts/stdin.bat b/src/test/scripts/stdin.bat
new file mode 100644
index 0000000..02518be
--- /dev/null
+++ b/src/test/scripts/stdin.bat
@@ -0,0 +1,19 @@
+ at ECHO OFF
+
+REM Licensed to the Apache Software Foundation (ASF) under one or more
+REM contributor license agreements.  See the NOTICE file distributed with
+REM this work for additional information regarding copyright ownership.
+REM The ASF licenses this file to You under the Apache License, Version 2.0
+REM (the "License"); you may not use this file except in compliance with
+REM the License.  You may obtain a copy of the License at
+REM
+REM      http://www.apache.org/licenses/LICENSE-2.0
+REM
+REM Unless required by applicable law or agreed to in writing, software
+REM distributed under the License is distributed on an "AS IS" BASIS,
+REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+REM See the License for the specific language governing permissions and
+REM limitations under the License.
+
+set /p answer=What's your name? : 
+echo Hello %answer%!
\ No newline at end of file
diff --git a/src/test/scripts/stdin.dcl b/src/test/scripts/stdin.dcl
new file mode 100644
index 0000000..14fb835
--- /dev/null
+++ b/src/test/scripts/stdin.dcl
@@ -0,0 +1,23 @@
+$!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+$!
+$! Licensed to the Apache Software Foundation (ASF) under one or more
+$! contributor license agreements.  See the NOTICE file distributed with
+$! this work for additional information regarding copyright ownership.
+$! The ASF licenses this file to You under the Apache License, Version 2.0
+$! (the "License"); you may not use this file except in compliance with
+$! the License.  You may obtain a copy of the License at
+$!
+$!      http://www.apache.org/licenses/LICENSE-2.0
+$!
+$! Unless required by applicable law or agreed to in writing, software
+$! distributed under the License is distributed on an "AS IS" BASIS,
+$! WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+$! See the License for the specific language governing permissions and
+$! limitations under the License.
+$! 
+$!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+$!
+$! Read input and display it
+$!
+$ read /prompt="What's your name? : " sys$command answer
+$ write sys$output "Hello ''answer'!"
\ No newline at end of file
diff --git a/src/test/scripts/stdin.sh b/src/test/scripts/stdin.sh
new file mode 100755
index 0000000..5572c78
--- /dev/null
+++ b/src/test/scripts/stdin.sh
@@ -0,0 +1,23 @@
+#!/bin/sh
+
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+echo "What's your name? : "
+read answer
+echo "Hello $answer!"
+
diff --git a/src/test/scripts/test.bat b/src/test/scripts/test.bat
new file mode 100644
index 0000000..ab67bc0
--- /dev/null
+++ b/src/test/scripts/test.bat
@@ -0,0 +1,24 @@
+ at ECHO OFF
+
+REM
+REM Licensed to the Apache Software Foundation (ASF) under one or more
+REM contributor license agreements.  See the NOTICE file distributed with
+REM this work for additional information regarding copyright ownership.
+REM The ASF licenses this file to You under the Apache License, Version 2.0
+REM (the "License"); you may not use this file except in compliance with
+REM the License.  You may obtain a copy of the License at
+REM
+REM      http://www.apache.org/licenses/LICENSE-2.0
+REM
+REM Unless required by applicable law or agreed to in writing, software
+REM distributed under the License is distributed on an "AS IS" BASIS,
+REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+REM See the License for the specific language governing permissions and
+REM limitations under the License.
+REM
+REM
+
+REM print the given environment variable and command line parameter
+REM since this is verified by the regression test
+
+ at ECHO FOO.%TEST_ENV_VAR%.%1
diff --git a/src/test/scripts/test.dcl b/src/test/scripts/test.dcl
new file mode 100644
index 0000000..2fe6d6a
--- /dev/null
+++ b/src/test/scripts/test.dcl
@@ -0,0 +1,25 @@
+$!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+$!
+$! Licensed to the Apache Software Foundation (ASF) under one or more
+$! contributor license agreements.  See the NOTICE file distributed with
+$! this work for additional information regarding copyright ownership.
+$! The ASF licenses this file to You under the Apache License, Version 2.0
+$! (the "License"); you may not use this file except in compliance with
+$! the License.  You may obtain a copy of the License at
+$!
+$!      http://www.apache.org/licenses/LICENSE-2.0
+$!
+$! Unless required by applicable law or agreed to in writing, software
+$! distributed under the License is distributed on an "AS IS" BASIS,
+$! WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+$! See the License for the specific language governing permissions and
+$! limitations under the License.
+$!
+$!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+$! 
+$! print the given environment variable and command line parameter
+$! since this is verified by the regression test
+$!
+$ write sys$output "FOO.''TEST_ENV_VAR'.''P1'"
+$!
+$ exit 1 ! normal exit
\ No newline at end of file
diff --git a/src/test/scripts/test.sh b/src/test/scripts/test.sh
new file mode 100755
index 0000000..83d9618
--- /dev/null
+++ b/src/test/scripts/test.sh
@@ -0,0 +1,23 @@
+#!/bin/sh
+
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# print the given environment variable and command line parameter
+# since this is verified by the regression test
+
+echo FOO.$TEST_ENV_VAR.$1

-- 
UNNAMED PROJECT



More information about the pkg-java-commits mailing list