[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