This is an automated email from the ASF dual-hosted git repository.

sebb pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-exec.git


The following commit(s) were added to refs/heads/master by this push:
     new 6321022e Normalise EOL
6321022e is described below

commit 6321022e8108c24f9bf73b33b62e768e0a05e04d
Author: Sebb <s...@apache.org>
AuthorDate: Sat Jan 18 22:38:14 2025 +0000

    Normalise EOL
---
 .gitattributes                                     |  17 +
 .github/workflows/maven.yml                        | 102 +--
 src/changes/changes.xml                            | 630 +++++++--------
 src/conf/findbugs-exclude-filter.xml               |  88 +--
 .../java/org/apache/commons/exec/CommandLine.java  | 850 ++++++++++-----------
 .../java/org/apache/commons/exec/Executor.java     | 364 ++++-----
 .../org/apache/commons/exec/ProcessDestroyer.java  |  98 +--
 7 files changed, 1083 insertions(+), 1066 deletions(-)

diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 00000000..35a5c24e
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,17 @@
+#   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.
+
+* text=auto
+*.bat -text
diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml
index d422b9a6..d379da68 100644
--- a/.github/workflows/maven.yml
+++ b/.github/workflows/maven.yml
@@ -1,54 +1,54 @@
-# 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.
-
-name: Java CI
-
-on: [push, pull_request]
-
-permissions:
-  contents: read
-
-jobs:
-  build:
-
-    runs-on: ${{ matrix.os }}
-    continue-on-error: ${{ matrix.experimental }}
-    strategy:
-      matrix:
-        os: [ubuntu-latest, windows-latest, macos-13]
-        java: [ 8, 11, 17, 21, 23 ]
-        experimental: [false]
-        include:
-          - java: 24-ea
-            experimental: true
-            os: ubuntu-latest
-
-    steps:
+# 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.
+
+name: Java CI
+
+on: [push, pull_request]
+
+permissions:
+  contents: read
+
+jobs:
+  build:
+
+    runs-on: ${{ matrix.os }}
+    continue-on-error: ${{ matrix.experimental }}
+    strategy:
+      matrix:
+        os: [ubuntu-latest, windows-latest, macos-13]
+        java: [ 8, 11, 17, 21, 23 ]
+        experimental: [false]
+        include:
+          - java: 24-ea
+            experimental: true
+            os: ubuntu-latest
+
+    steps:
     - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
-      with:
-        persist-credentials: false
+      with:
+        persist-credentials: false
     - uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
-      with:
-        path: ~/.m2/repository
-        key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
-        restore-keys: |
-          ${{ runner.os }}-maven-
-    - name: Set up JDK ${{ matrix.java }}
+      with:
+        path: ~/.m2/repository
+        key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
+        restore-keys: |
+          ${{ runner.os }}-maven-
+    - name: Set up JDK ${{ matrix.java }}
       uses: actions/setup-java@7a6d8a8234af8eb26422e24e3006232cccaa061b # 
v4.6.0
-      with:
-        distribution: 'temurin'
-        java-version: ${{ matrix.java }}
-    - name: Build with Maven
-      run: mvn --errors --show-version --batch-mode --no-transfer-progress 
-Ddoclint=all
+      with:
+        distribution: 'temurin'
+        java-version: ${{ matrix.java }}
+    - name: Build with Maven
+      run: mvn --errors --show-version --batch-mode --no-transfer-progress 
-Ddoclint=all
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 4b23820e..7b079837 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -1,315 +1,315 @@
-<?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 xmlns="http://maven.apache.org/changes/2.0.0";
-          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
-          xsi:schemaLocation="http://maven.apache.org/changes/2.0.0 
https://maven.apache.org/xsd/changes-2.0.0.xsd";>
-    <properties>
-        <title>Apache Commons Exec Release Notes</title>
-    </properties>
-    <body>
-        <release version="1.4.1" date="YYYY-MM-DD" description="Maintenance 
and feature Release (Java 8 or above)">
-            <!-- ADD -->
-            <action type="add" dev="ggregory" due-to="Gary Gregory">Add Maven 
property project.build.outputTimestamp for build reproducibility.</action>
-            <!-- FIX -->
-            <action type="fix" dev="ggregory" issue="EXEC-122" 
due-to="Marcono1234">Document PumpStreamHandler stream thread-safety 
requirements.</action>
-            <action type="fix" dev="ggregory" due-to="Marcono1234">Fix CI only 
running on Ubuntu and improve OS-specific tests #143.</action>
-            <action type="fix" dev="ggregory" due-to="Gary Gregory">Fix PMD 
UnnecessaryFullyQualifiedName in DefaultExecutor.</action>
-            <action type="fix" dev="ggregory" due-to="Gary Gregory">Fix PMD 
EmptyCatchBlock by allowing commented blocks.</action>
-            <action type="fix" dev="ggregory" due-to="Gary Gregory">Fix PMD 
EmptyControlStatement by allowing commented blocks.</action>
-            <action type="fix" dev="ggregory" due-to="Gary Gregory">Replace 
OS.OS_* use of Locale.ENGLISH with Locale.ROOT.</action>
-            <!-- UPDATE -->
-            <action type="update" dev="ggregory" due-to="Dependabot, Gary 
Gregory">Bump org.apache.commons:commons-parent from 65 to 79 #174, #204, #212, 
#214, #219, #223, #226, #233.</action>
-        </release>
-        <release version="1.4.0" date="2024-01-01" description="Maintenance 
and feature Release (Java 8 or above)">
-            <!-- ADD -->
-            <action type="add" dev="ggregory" due-to="Gary Gregory">Add 
ShutdownHookProcessDestroyer.isEmpty().</action>
-            <action type="add" dev="ggregory" due-to="Gary Gregory">Add 
DefaultExecuteResultHandler.waitFor(Duration).</action>
-            <action type="add" dev="ggregory" due-to="Gary Gregory">Add 
Watchdog.Watchdog(Duration).</action>
-            <action type="add" dev="ggregory" due-to="Gary Gregory">Add 
ExecuteWatchdog.ExecuteWatchdog(Duration).</action>
-            <action type="add" dev="ggregory" due-to="Gary Gregory">Add 
PumpStreamHandler.setStopTimeout(Duration) and deprecate 
PumpStreamHandler.setStopTimeout(long).</action>
-            <action type="add" dev="ggregory" due-to="Gary Gregory">Add 
DefaultExecutor.Builder.</action>
-            <action type="add" dev="ggregory" due-to="Gary Gregory">Add 
DaemonExecutor.Builder.</action>
-            <action type="add" dev="ggregory" due-to="Gary Gregory">Add 
ExecuteWatchdog.Builder.</action>
-            <action type="add" dev="ggregory" due-to="Gary Gregory">Add 
Watchdog.Builder.</action>
-            <!-- FIX -->
-            <action issue="EXEC-105" type="fix" due-to="Dimitrios 
Efthymiou">Fix code snippet in tutorial page.</action>
-            <action issue="EXEC-100" dev="sgoeschl" type="fix" 
date="2016-01-11">Sync org.apache.commons.exec.OS with the newest Ant source 
file.</action>
-            <action issue="EXEC-64" dev="sebb" type="fix" due-to="Michael 
Vorburger">DefaultExecutor swallows IOException cause instead of propagating it 
(work-round for Java 1.5).</action>
-            <action type="fix" dev="ggregory" due-to="nullptr">Java-style 
Array declaration and remove empty finally block #26.</action>
-            <action type="fix" dev="ggregory" due-to="John Patrick">Use JUnit 
5 assertThrows() #72.</action>
-            <action type="fix" dev="ggregory" due-to="step-security-bot, Gary 
Gregory">[StepSecurity] ci: Harden GitHub Actions #107.</action>
-            <action type="fix" dev="ggregory" due-to="Gary Gregory">Port from 
JUnit 4 to 5.</action>
-            <action type="fix" dev="ggregory" due-to="ArdenL-Liu, Gary 
Gregory">[Javadoc] CommandLine.toCleanExecutable(final String dirtyExecutable) 
IllegalArgumentException #61.</action>
-            <action type="fix" dev="ggregory" due-to="Gary 
Gregory">ExecuteException propagates its cause to its IOException 
superclass.</action>
-            <action type="fix" dev="ggregory" due-to="Gary Gregory">Propagate 
exception in DebugUtils.handleException(String, Exception).</action>
-            <action type="fix" dev="ggregory" due-to="Gary Gregory">Deprecate 
StringUtils.toString(String[], String) in favor of String.join(CharSequence, 
CharSequence...).</action>
-            <action issue="EXEC-78" dev="ggregory" type="fix">No need to use 
System.class.getMethod("getenv",...) any more.</action>
-            <action issue="EXEC-70" dev="ggregory" type="fix">Delegate thread 
creation to java.util.concurrent.ThreadFactory.</action>
-            <action type="fix" dev="ggregory" due-to="Gary Gregory">Avoid 
NullPointerException in MapUtils.prefix(Map, String).</action>
-            <!-- REMOVE -->
-            <action type="remove" dev="ggregory" due-to="Gary 
Gregory">Deprecate DefaultExecuteResultHandler.waitFor(long).</action>
-            <action type="remove" dev="ggregory" due-to="Gary 
Gregory">Deprecate ExecuteWatchdog.ExecuteWatchdog(long).</action>
-            <action type="remove" dev="ggregory" due-to="Gary 
Gregory">Deprecate Watchdog.Watchdog(long).</action>
-            <action type="remove" dev="ggregory" due-to="Gary Gregory">Drop 
obsolete and unmaintained Ant build.</action>
-            <action type="remove" dev="ggregory" due-to="Gary Gregory">Drop 
CLIRR plugin, replaced by JApiCmp.</action>
-            <!-- UPDATE -->
-            <action type="update" dev="ggregory" due-to="Gary Gregory, 
Dependabot">Bump github actions #52.</action>
-            <action issue="EXEC-111" type="update" dev="ggregory" due-to="Gary 
Gregory">Update from Java 5 to 6.</action>
-            <action type="update" dev="ggregory" due-to="Gary Gregory">Update 
from Java 7 to 8.</action>
-            <action type="update" dev="ggregory" due-to="Gary Gregory">Bump 
actions/cache from 2 to 3.0.11 #48, #51, #55, #69.</action>
-            <action type="update" dev="ggregory" due-to="Dependabot, Gary 
Gregory">Bump actions/checkout from 2.3.2 to 3.1.0 #24, #46, #68.</action>
-            <action type="update" dev="ggregory" due-to="Dependabot, Gary 
Gregory">Bump actions/setup-java from 1.4.0 to 3.8.0 #21, #50, #70, 
#78.</action>
-            <action type="update" dev="ggregory" due-to="Dependabot">Bump 
junit from 4.13 to 5.9.1 Vintage #23, #33.</action>
-            <action type="update" dev="ggregory" due-to="Dependabot">Bump 
maven-pmd-plugin from 2.7.1 to 3.19.0 #45, #62.</action>
-            <action type="update" dev="ggregory" due-to="Dependabot">Bump 
maven-checkstyle-plugin from 2.13 to 3.2.0 #29, #60.</action>
-            <action type="update" dev="ggregory" due-to="Dependabot, Gary 
Gregory">Bump commons-parent from 52 to 65, #49, #65, #79, #88, #105, 
#137.</action>
-            <action type="update" dev="ggregory" due-to="Gary Gregory">Bump 
japicmp-maven-plugin from 0.15.6 to 0.16.0.</action>
-        </release>
-        <release version="1.3" date="2014-11-02" description="Maintenance and 
feature Release">
-            <action issue="EXEC-69" type="add" dev="ggregory" due-to="Richard 
Atkins, Michael Vorburger">
-                DefaultExecutor async execute prevents shutdown hooks running.
-            </action>
-            <action issue="EXEC-81" type="fix" dev="ggregory" 
date="2014-02-04" due-to="Stephen Compall">
-                Remove remaining raw types, unchecked conversions
-            </action>
-            <action issue="EXEC-80" type="fix" dev="ggregory" 
date="2014-02-04">
-                NullPointerException in EnvironmentUtils.toString(map)
-            </action>
-            <action issue="EXEC-78" dev="sebb" type="update" date="2014-01-11">
-                No need to use System.class.getMethod("getenv",...) any more
-            </action>
-            <action issue="EXEC-77" dev="britter" type="update" 
date="2014-01-10">
-                Update JUnit dependency to 4.11
-            </action>
-            <action issue="EXEC-76" dev="britter" type="update" 
date="2014-01-10">
-                Update to Java 5
-            </action>
-        </release>
-        <release version="1.2" date="2014-01-02" description="Maintenance and 
small feature Release">
-            <action issue="EXEC-68" type="fix" dev="ggregory" 
date="2012-10-22"  due-to="Joel McCance">
-                Watchdog kills process immediately if timeout is too large.
-            </action>
-            <action issue="EXEC-57" dev="sgoeschl" type="fix" 
date="2011-10-10"  due-to="Nickolay Martinov">
-                Applied the patch from Nickolay Martinov but the timeout 
disguises the fact
-                that the process might be still running. Therefore added a 
sanity check in
-                order to throw an exception if the timeout for join() was 
exceeded.
-            </action>
-            <action issue="EXEC-60" dev="sgoeschl" type="fix" 
date="2011-10-09"  due-to="Peter Kofler">
-                Fixed deadlock by calling the timeout observers outside of the 
synchronized block thereby
-                removing a prerequisite of a deadlock. Also added a test case 
to demonstrate that this
-                problem is fixed (which of course can not guarantee the 
absence of a dead lock).
-            </action>
-            <action issue="EXEC-55" dev="sgoeschl" type="add" 
date="2011-02-21"  due-to="Dominik Stadler">
-                Set names for started threads.
-            </action>
-            <action issue="EXEC-52" dev="sebb" type="fix" date="2011-02-26"  
due-to="Nickolay Martinov">
-                Tests fail on HP-UX, because it uses a different syntax for 
the ping command.
-            </action>
-            <action issue="EXEC-49" dev="sgoeschl" type="fix" 
date="2010-11-05" due-to="Kevin Telford">
-                "Write dead end" IOException when using Piped streams 
w/PumpStreamHandler.
-                When encountering a PipedOutputStream we will automatically 
close it to avoid
-                the exception.
-            </action>
-            <action issue="EXEC-34" dev="simonetripodi" type="fix" 
date="2011-11-30" due-to="Marco Ferrante">
-                Race condition prevent watchdog working using 
ExecuteStreamHandler.
-                Patch submittd by Kristian Rosenvold.
-            </action>
-        </release>
-        <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 NullPointerException 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>
+<?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 xmlns="http://maven.apache.org/changes/2.0.0";
+          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+          xsi:schemaLocation="http://maven.apache.org/changes/2.0.0 
https://maven.apache.org/xsd/changes-2.0.0.xsd";>
+    <properties>
+        <title>Apache Commons Exec Release Notes</title>
+    </properties>
+    <body>
+        <release version="1.4.1" date="YYYY-MM-DD" description="Maintenance 
and feature Release (Java 8 or above)">
+            <!-- ADD -->
+            <action type="add" dev="ggregory" due-to="Gary Gregory">Add Maven 
property project.build.outputTimestamp for build reproducibility.</action>
+            <!-- FIX -->
+            <action type="fix" dev="ggregory" issue="EXEC-122" 
due-to="Marcono1234">Document PumpStreamHandler stream thread-safety 
requirements.</action>
+            <action type="fix" dev="ggregory" due-to="Marcono1234">Fix CI only 
running on Ubuntu and improve OS-specific tests #143.</action>
+            <action type="fix" dev="ggregory" due-to="Gary Gregory">Fix PMD 
UnnecessaryFullyQualifiedName in DefaultExecutor.</action>
+            <action type="fix" dev="ggregory" due-to="Gary Gregory">Fix PMD 
EmptyCatchBlock by allowing commented blocks.</action>
+            <action type="fix" dev="ggregory" due-to="Gary Gregory">Fix PMD 
EmptyControlStatement by allowing commented blocks.</action>
+            <action type="fix" dev="ggregory" due-to="Gary Gregory">Replace 
OS.OS_* use of Locale.ENGLISH with Locale.ROOT.</action>
+            <!-- UPDATE -->
+            <action type="update" dev="ggregory" due-to="Dependabot, Gary 
Gregory">Bump org.apache.commons:commons-parent from 65 to 79 #174, #204, #212, 
#214, #219, #223, #226, #233.</action>
+        </release>
+        <release version="1.4.0" date="2024-01-01" description="Maintenance 
and feature Release (Java 8 or above)">
+            <!-- ADD -->
+            <action type="add" dev="ggregory" due-to="Gary Gregory">Add 
ShutdownHookProcessDestroyer.isEmpty().</action>
+            <action type="add" dev="ggregory" due-to="Gary Gregory">Add 
DefaultExecuteResultHandler.waitFor(Duration).</action>
+            <action type="add" dev="ggregory" due-to="Gary Gregory">Add 
Watchdog.Watchdog(Duration).</action>
+            <action type="add" dev="ggregory" due-to="Gary Gregory">Add 
ExecuteWatchdog.ExecuteWatchdog(Duration).</action>
+            <action type="add" dev="ggregory" due-to="Gary Gregory">Add 
PumpStreamHandler.setStopTimeout(Duration) and deprecate 
PumpStreamHandler.setStopTimeout(long).</action>
+            <action type="add" dev="ggregory" due-to="Gary Gregory">Add 
DefaultExecutor.Builder.</action>
+            <action type="add" dev="ggregory" due-to="Gary Gregory">Add 
DaemonExecutor.Builder.</action>
+            <action type="add" dev="ggregory" due-to="Gary Gregory">Add 
ExecuteWatchdog.Builder.</action>
+            <action type="add" dev="ggregory" due-to="Gary Gregory">Add 
Watchdog.Builder.</action>
+            <!-- FIX -->
+            <action issue="EXEC-105" type="fix" due-to="Dimitrios 
Efthymiou">Fix code snippet in tutorial page.</action>
+            <action issue="EXEC-100" dev="sgoeschl" type="fix" 
date="2016-01-11">Sync org.apache.commons.exec.OS with the newest Ant source 
file.</action>
+            <action issue="EXEC-64" dev="sebb" type="fix" due-to="Michael 
Vorburger">DefaultExecutor swallows IOException cause instead of propagating it 
(work-round for Java 1.5).</action>
+            <action type="fix" dev="ggregory" due-to="nullptr">Java-style 
Array declaration and remove empty finally block #26.</action>
+            <action type="fix" dev="ggregory" due-to="John Patrick">Use JUnit 
5 assertThrows() #72.</action>
+            <action type="fix" dev="ggregory" due-to="step-security-bot, Gary 
Gregory">[StepSecurity] ci: Harden GitHub Actions #107.</action>
+            <action type="fix" dev="ggregory" due-to="Gary Gregory">Port from 
JUnit 4 to 5.</action>
+            <action type="fix" dev="ggregory" due-to="ArdenL-Liu, Gary 
Gregory">[Javadoc] CommandLine.toCleanExecutable(final String dirtyExecutable) 
IllegalArgumentException #61.</action>
+            <action type="fix" dev="ggregory" due-to="Gary 
Gregory">ExecuteException propagates its cause to its IOException 
superclass.</action>
+            <action type="fix" dev="ggregory" due-to="Gary Gregory">Propagate 
exception in DebugUtils.handleException(String, Exception).</action>
+            <action type="fix" dev="ggregory" due-to="Gary Gregory">Deprecate 
StringUtils.toString(String[], String) in favor of String.join(CharSequence, 
CharSequence...).</action>
+            <action issue="EXEC-78" dev="ggregory" type="fix">No need to use 
System.class.getMethod("getenv",...) any more.</action>
+            <action issue="EXEC-70" dev="ggregory" type="fix">Delegate thread 
creation to java.util.concurrent.ThreadFactory.</action>
+            <action type="fix" dev="ggregory" due-to="Gary Gregory">Avoid 
NullPointerException in MapUtils.prefix(Map, String).</action>
+            <!-- REMOVE -->
+            <action type="remove" dev="ggregory" due-to="Gary 
Gregory">Deprecate DefaultExecuteResultHandler.waitFor(long).</action>
+            <action type="remove" dev="ggregory" due-to="Gary 
Gregory">Deprecate ExecuteWatchdog.ExecuteWatchdog(long).</action>
+            <action type="remove" dev="ggregory" due-to="Gary 
Gregory">Deprecate Watchdog.Watchdog(long).</action>
+            <action type="remove" dev="ggregory" due-to="Gary Gregory">Drop 
obsolete and unmaintained Ant build.</action>
+            <action type="remove" dev="ggregory" due-to="Gary Gregory">Drop 
CLIRR plugin, replaced by JApiCmp.</action>
+            <!-- UPDATE -->
+            <action type="update" dev="ggregory" due-to="Gary Gregory, 
Dependabot">Bump github actions #52.</action>
+            <action issue="EXEC-111" type="update" dev="ggregory" due-to="Gary 
Gregory">Update from Java 5 to 6.</action>
+            <action type="update" dev="ggregory" due-to="Gary Gregory">Update 
from Java 7 to 8.</action>
+            <action type="update" dev="ggregory" due-to="Gary Gregory">Bump 
actions/cache from 2 to 3.0.11 #48, #51, #55, #69.</action>
+            <action type="update" dev="ggregory" due-to="Dependabot, Gary 
Gregory">Bump actions/checkout from 2.3.2 to 3.1.0 #24, #46, #68.</action>
+            <action type="update" dev="ggregory" due-to="Dependabot, Gary 
Gregory">Bump actions/setup-java from 1.4.0 to 3.8.0 #21, #50, #70, 
#78.</action>
+            <action type="update" dev="ggregory" due-to="Dependabot">Bump 
junit from 4.13 to 5.9.1 Vintage #23, #33.</action>
+            <action type="update" dev="ggregory" due-to="Dependabot">Bump 
maven-pmd-plugin from 2.7.1 to 3.19.0 #45, #62.</action>
+            <action type="update" dev="ggregory" due-to="Dependabot">Bump 
maven-checkstyle-plugin from 2.13 to 3.2.0 #29, #60.</action>
+            <action type="update" dev="ggregory" due-to="Dependabot, Gary 
Gregory">Bump commons-parent from 52 to 65, #49, #65, #79, #88, #105, 
#137.</action>
+            <action type="update" dev="ggregory" due-to="Gary Gregory">Bump 
japicmp-maven-plugin from 0.15.6 to 0.16.0.</action>
+        </release>
+        <release version="1.3" date="2014-11-02" description="Maintenance and 
feature Release">
+            <action issue="EXEC-69" type="add" dev="ggregory" due-to="Richard 
Atkins, Michael Vorburger">
+                DefaultExecutor async execute prevents shutdown hooks running.
+            </action>
+            <action issue="EXEC-81" type="fix" dev="ggregory" 
date="2014-02-04" due-to="Stephen Compall">
+                Remove remaining raw types, unchecked conversions
+            </action>
+            <action issue="EXEC-80" type="fix" dev="ggregory" 
date="2014-02-04">
+                NullPointerException in EnvironmentUtils.toString(map)
+            </action>
+            <action issue="EXEC-78" dev="sebb" type="update" date="2014-01-11">
+                No need to use System.class.getMethod("getenv",...) any more
+            </action>
+            <action issue="EXEC-77" dev="britter" type="update" 
date="2014-01-10">
+                Update JUnit dependency to 4.11
+            </action>
+            <action issue="EXEC-76" dev="britter" type="update" 
date="2014-01-10">
+                Update to Java 5
+            </action>
+        </release>
+        <release version="1.2" date="2014-01-02" description="Maintenance and 
small feature Release">
+            <action issue="EXEC-68" type="fix" dev="ggregory" 
date="2012-10-22"  due-to="Joel McCance">
+                Watchdog kills process immediately if timeout is too large.
+            </action>
+            <action issue="EXEC-57" dev="sgoeschl" type="fix" 
date="2011-10-10"  due-to="Nickolay Martinov">
+                Applied the patch from Nickolay Martinov but the timeout 
disguises the fact
+                that the process might be still running. Therefore added a 
sanity check in
+                order to throw an exception if the timeout for join() was 
exceeded.
+            </action>
+            <action issue="EXEC-60" dev="sgoeschl" type="fix" 
date="2011-10-09"  due-to="Peter Kofler">
+                Fixed deadlock by calling the timeout observers outside of the 
synchronized block thereby
+                removing a prerequisite of a deadlock. Also added a test case 
to demonstrate that this
+                problem is fixed (which of course can not guarantee the 
absence of a dead lock).
+            </action>
+            <action issue="EXEC-55" dev="sgoeschl" type="add" 
date="2011-02-21"  due-to="Dominik Stadler">
+                Set names for started threads.
+            </action>
+            <action issue="EXEC-52" dev="sebb" type="fix" date="2011-02-26"  
due-to="Nickolay Martinov">
+                Tests fail on HP-UX, because it uses a different syntax for 
the ping command.
+            </action>
+            <action issue="EXEC-49" dev="sgoeschl" type="fix" 
date="2010-11-05" due-to="Kevin Telford">
+                "Write dead end" IOException when using Piped streams 
w/PumpStreamHandler.
+                When encountering a PipedOutputStream we will automatically 
close it to avoid
+                the exception.
+            </action>
+            <action issue="EXEC-34" dev="simonetripodi" type="fix" 
date="2011-11-30" due-to="Marco Ferrante">
+                Race condition prevent watchdog working using 
ExecuteStreamHandler.
+                Patch submittd by Kristian Rosenvold.
+            </action>
+        </release>
+        <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 NullPointerException 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/conf/findbugs-exclude-filter.xml 
b/src/conf/findbugs-exclude-filter.xml
index 48bf4ab7..8b2d2cac 100644
--- a/src/conf/findbugs-exclude-filter.xml
+++ b/src/conf/findbugs-exclude-filter.xml
@@ -1,44 +1,44 @@
-<?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
-    xmlns="https://github.com/spotbugs/filter/3.0.0";
-    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
-    xsi:schemaLocation="https://github.com/spotbugs/filter/3.0.0 
https://raw.githubusercontent.com/spotbugs/spotbugs/3.1.0/spotbugs/etc/findbugsfilter.xsd";>
-
-  <!-- TODO Can any of these be done without breaking binary compatibility? -->
-  <Match>
-    <Class name="~.*" />
-    <Or>
-      <Bug pattern="EI_EXPOSE_REP" />
-      <Bug pattern="EI_EXPOSE_REP2" />
-    </Or>
-  </Match>
-
-  <!-- https://github.com/spotbugs/spotbugs/issues/2710 -->
-  <Match>
-    <Class name="~.*" />
-    <Bug pattern="CT_CONSTRUCTOR_THROW" />
-  </Match>
-
-</FindBugsFilter>
+<?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
+    xmlns="https://github.com/spotbugs/filter/3.0.0";
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+    xsi:schemaLocation="https://github.com/spotbugs/filter/3.0.0 
https://raw.githubusercontent.com/spotbugs/spotbugs/3.1.0/spotbugs/etc/findbugsfilter.xsd";>
+
+  <!-- TODO Can any of these be done without breaking binary compatibility? -->
+  <Match>
+    <Class name="~.*" />
+    <Or>
+      <Bug pattern="EI_EXPOSE_REP" />
+      <Bug pattern="EI_EXPOSE_REP2" />
+    </Or>
+  </Match>
+
+  <!-- https://github.com/spotbugs/spotbugs/issues/2710 -->
+  <Match>
+    <Class name="~.*" />
+    <Bug pattern="CT_CONSTRUCTOR_THROW" />
+  </Match>
+
+</FindBugsFilter>
diff --git a/src/main/java/org/apache/commons/exec/CommandLine.java 
b/src/main/java/org/apache/commons/exec/CommandLine.java
index cd42f063..ab1ab264 100644
--- a/src/main/java/org/apache/commons/exec/CommandLine.java
+++ b/src/main/java/org/apache/commons/exec/CommandLine.java
@@ -1,425 +1,425 @@
-/*
- * 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.ArrayList;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Objects;
-import java.util.StringTokenizer;
-import java.util.Vector;
-
-import org.apache.commons.exec.util.StringUtils;
-
-/**
- * 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 {
-
-    /**
-     * Encapsulates a command line argument.
-     */
-    static final class Argument {
-
-        private final String value;
-        private final boolean handleQuoting;
-
-        private Argument(final String value, final boolean handleQuoting) {
-            this.value = value.trim();
-            this.handleQuoting = handleQuoting;
-        }
-
-        private String getValue() {
-            return value;
-        }
-
-        private boolean isHandleQuoting() {
-            return handleQuoting;
-        }
-    }
-
-    /**
-     * 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, final Map<String, ?> 
substitutionMap) {
-
-        if (line == null) {
-            throw new IllegalArgumentException("Command line cannot be null");
-        }
-        if (line.trim().isEmpty()) {
-            throw new IllegalArgumentException("Command line cannot be empty");
-        }
-        final String[] tmp = translateCommandline(line);
-
-        final CommandLine cl = new CommandLine(tmp[0]);
-        cl.setSubstitutionMap(substitutionMap);
-        for (int i = 1; i < tmp.length; i++) {
-            cl.addArgument(tmp[i]);
-        }
-
-        return cl;
-    }
-
-    /**
-     * 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.trim().isEmpty()) {
-            // 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;
-        final StringTokenizer tok = new StringTokenizer(toProcess, "\"\' ", 
true);
-        final ArrayList<String> list = new ArrayList<>();
-        StringBuilder current = new StringBuilder();
-        boolean lastTokenHasBeenQuoted = false;
-
-        while (tok.hasMoreTokens()) {
-            final 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:
-                switch (nextTok) {
-                case "\'":
-                    state = inQuote;
-                    break;
-                case "\"":
-                    state = inDoubleQuote;
-                    break;
-                case " ":
-                    if (lastTokenHasBeenQuoted || current.length() != 0) {
-                        list.add(current.toString());
-                        current = new StringBuilder();
-                    }
-                    break;
-                default:
-                    current.append(nextTok);
-                    break;
-                }
-                lastTokenHasBeenQuoted = false;
-                break;
-            }
-        }
-
-        if (lastTokenHasBeenQuoted || current.length() != 0) {
-            list.add(current.toString());
-        }
-
-        if (state == inQuote || state == inDoubleQuote) {
-            throw new IllegalArgumentException("Unbalanced quotes in " + 
toProcess);
-        }
-
-        final String[] args = new String[list.size()];
-        return list.toArray(args);
-    }
-
-    /**
-     * The arguments of the command.
-     */
-    private final Vector<Argument> 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<String, ?> substitutionMap; // N.B. This can contain values 
other than Strings.
-
-    /**
-     * Tests whether a file was used to set the executable.
-     */
-    private final boolean isFile;
-
-    /**
-     * Copy constructor.
-     *
-     * @param other the instance to copy.
-     */
-    public CommandLine(final CommandLine other) {
-        this.executable = other.getExecutable();
-        this.isFile = other.isFile();
-        this.arguments.addAll(other.arguments);
-
-        if (other.getSubstitutionMap() != null) {
-            this.substitutionMap = new HashMap<>(other.getSubstitutionMap());
-        }
-    }
-
-    /**
-     * Create a command line without any arguments.
-     *
-     * @param executable the executable file.
-     */
-    public CommandLine(final File executable) {
-        this.isFile = true;
-        this.executable = toCleanExecutable(executable.getAbsolutePath());
-    }
-
-    /**
-     * Create a command line without any arguments.
-     *
-     * @param executable the executable.
-     * @throws NullPointerException     on null input.
-     * @throws IllegalArgumentException on empty input.
-     */
-    public CommandLine(final String executable) {
-        this.isFile = false;
-        this.executable = toCleanExecutable(executable);
-    }
-
-    /**
-     * 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 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, final 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;
-    }
-
-    /**
-     * 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 addArguments An string containing multiple arguments.
-     * @return The command line itself.
-     */
-    public CommandLine addArguments(final String addArguments) {
-        return addArguments(addArguments, 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 addArguments  An string containing multiple arguments.
-     * @param handleQuoting Add the argument with/without handling quoting.
-     * @return The command line itself.
-     */
-    public CommandLine addArguments(final String addArguments, final boolean 
handleQuoting) {
-        if (addArguments != null) {
-            final String[] argumentsArray = translateCommandline(addArguments);
-            addArguments(argumentsArray, handleQuoting);
-        }
-
-        return this;
-    }
-
-    /**
-     * Add multiple arguments. Handles parsing of quotes and whitespace.
-     *
-     * @param addArguments An array of arguments.
-     * @return The command line itself.
-     */
-    public CommandLine addArguments(final String[] addArguments) {
-        return addArguments(addArguments, true);
-    }
-
-    /**
-     * Add multiple arguments.
-     *
-     * @param addArguments  An array of arguments.
-     * @param handleQuoting Add the argument with/without handling quoting.
-     * @return The command line itself.
-     */
-    public CommandLine addArguments(final String[] addArguments, final boolean 
handleQuoting) {
-        if (addArguments != null) {
-            for (final String addArgument : addArguments) {
-                addArgument(addArgument, handleQuoting);
-            }
-        }
-        return this;
-    }
-
-    /**
-     * Expand variables in a command line argument.
-     *
-     * @param argument the argument.
-     * @return the expanded string.
-     */
-    private String expandArgument(final String argument) {
-        final StringBuffer stringBuffer = 
StringUtils.stringSubstitution(argument, getSubstitutionMap(), true);
-        return stringBuffer.toString();
-    }
-
-    /**
-     * Gets the expanded and quoted command line arguments.
-     *
-     * @return The quoted arguments.
-     */
-    public String[] getArguments() {
-
-        Argument currArgument;
-        String expandedArgument;
-        final String[] result = new String[arguments.size()];
-
-        for (int i = 0; i < result.length; i++) {
-            currArgument = arguments.get(i);
-            expandedArgument = expandArgument(currArgument.getValue());
-            result[i] = currArgument.isHandleQuoting() ? 
StringUtils.quoteArgument(expandedArgument) : expandedArgument;
-        }
-
-        return result;
-    }
-
-    /**
-     * Gets 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));
-    }
-
-    /**
-     * Gets the substitution map.
-     *
-     * @return the substitution map.
-     */
-    public Map<String, ?> getSubstitutionMap() {
-        return substitutionMap;
-    }
-
-    /**
-     * Tests whether a file was used to set the executable.
-     *
-     * @return true whether a file was used for setting the executable.
-     */
-    public boolean isFile() {
-        return isFile;
-    }
-
-    /**
-     * Sets the substitutionMap to expand variables in the command line.
-     *
-     * @param substitutionMap the map
-     */
-    public void setSubstitutionMap(final Map<String, ?> substitutionMap) {
-        this.substitutionMap = substitutionMap;
-    }
-
-    /**
-     * Cleans the executable string. The argument is trimmed and '/' and '\\' 
are replaced with the platform specific file separator char
-     *
-     * @param dirtyExecutable the executable.
-     * @return the platform-specific executable string.
-     * @throws NullPointerException     on null input.
-     * @throws IllegalArgumentException on empty input.
-     */
-    private String toCleanExecutable(final String dirtyExecutable) {
-        Objects.requireNonNull(dirtyExecutable, "dirtyExecutable");
-        if (dirtyExecutable.trim().isEmpty()) {
-            throw new IllegalArgumentException("Executable cannot be empty");
-        }
-        return StringUtils.fixFileSeparatorChar(dirtyExecutable);
-    }
-
-    /**
-     * 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.
-     */
-    @Override
-    public String toString() {
-        return "[" + String.join(", ", toStrings()) + "]";
-    }
-
-    /**
-     * Converts 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] = getExecutable();
-        System.arraycopy(getArguments(), 0, result, 1, result.length - 1);
-        return result;
-    }
-}
+/*
+ * 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.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.StringTokenizer;
+import java.util.Vector;
+
+import org.apache.commons.exec.util.StringUtils;
+
+/**
+ * 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 {
+
+    /**
+     * Encapsulates a command line argument.
+     */
+    static final class Argument {
+
+        private final String value;
+        private final boolean handleQuoting;
+
+        private Argument(final String value, final boolean handleQuoting) {
+            this.value = value.trim();
+            this.handleQuoting = handleQuoting;
+        }
+
+        private String getValue() {
+            return value;
+        }
+
+        private boolean isHandleQuoting() {
+            return handleQuoting;
+        }
+    }
+
+    /**
+     * 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, final Map<String, ?> 
substitutionMap) {
+
+        if (line == null) {
+            throw new IllegalArgumentException("Command line cannot be null");
+        }
+        if (line.trim().isEmpty()) {
+            throw new IllegalArgumentException("Command line cannot be empty");
+        }
+        final String[] tmp = translateCommandline(line);
+
+        final CommandLine cl = new CommandLine(tmp[0]);
+        cl.setSubstitutionMap(substitutionMap);
+        for (int i = 1; i < tmp.length; i++) {
+            cl.addArgument(tmp[i]);
+        }
+
+        return cl;
+    }
+
+    /**
+     * 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.trim().isEmpty()) {
+            // 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;
+        final StringTokenizer tok = new StringTokenizer(toProcess, "\"\' ", 
true);
+        final ArrayList<String> list = new ArrayList<>();
+        StringBuilder current = new StringBuilder();
+        boolean lastTokenHasBeenQuoted = false;
+
+        while (tok.hasMoreTokens()) {
+            final 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:
+                switch (nextTok) {
+                case "\'":
+                    state = inQuote;
+                    break;
+                case "\"":
+                    state = inDoubleQuote;
+                    break;
+                case " ":
+                    if (lastTokenHasBeenQuoted || current.length() != 0) {
+                        list.add(current.toString());
+                        current = new StringBuilder();
+                    }
+                    break;
+                default:
+                    current.append(nextTok);
+                    break;
+                }
+                lastTokenHasBeenQuoted = false;
+                break;
+            }
+        }
+
+        if (lastTokenHasBeenQuoted || current.length() != 0) {
+            list.add(current.toString());
+        }
+
+        if (state == inQuote || state == inDoubleQuote) {
+            throw new IllegalArgumentException("Unbalanced quotes in " + 
toProcess);
+        }
+
+        final String[] args = new String[list.size()];
+        return list.toArray(args);
+    }
+
+    /**
+     * The arguments of the command.
+     */
+    private final Vector<Argument> 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<String, ?> substitutionMap; // N.B. This can contain values 
other than Strings.
+
+    /**
+     * Tests whether a file was used to set the executable.
+     */
+    private final boolean isFile;
+
+    /**
+     * Copy constructor.
+     *
+     * @param other the instance to copy.
+     */
+    public CommandLine(final CommandLine other) {
+        this.executable = other.getExecutable();
+        this.isFile = other.isFile();
+        this.arguments.addAll(other.arguments);
+
+        if (other.getSubstitutionMap() != null) {
+            this.substitutionMap = new HashMap<>(other.getSubstitutionMap());
+        }
+    }
+
+    /**
+     * Create a command line without any arguments.
+     *
+     * @param executable the executable file.
+     */
+    public CommandLine(final File executable) {
+        this.isFile = true;
+        this.executable = toCleanExecutable(executable.getAbsolutePath());
+    }
+
+    /**
+     * Create a command line without any arguments.
+     *
+     * @param executable the executable.
+     * @throws NullPointerException     on null input.
+     * @throws IllegalArgumentException on empty input.
+     */
+    public CommandLine(final String executable) {
+        this.isFile = false;
+        this.executable = toCleanExecutable(executable);
+    }
+
+    /**
+     * 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 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, final 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;
+    }
+
+    /**
+     * 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 addArguments An string containing multiple arguments.
+     * @return The command line itself.
+     */
+    public CommandLine addArguments(final String addArguments) {
+        return addArguments(addArguments, 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 addArguments  An string containing multiple arguments.
+     * @param handleQuoting Add the argument with/without handling quoting.
+     * @return The command line itself.
+     */
+    public CommandLine addArguments(final String addArguments, final boolean 
handleQuoting) {
+        if (addArguments != null) {
+            final String[] argumentsArray = translateCommandline(addArguments);
+            addArguments(argumentsArray, handleQuoting);
+        }
+
+        return this;
+    }
+
+    /**
+     * Add multiple arguments. Handles parsing of quotes and whitespace.
+     *
+     * @param addArguments An array of arguments.
+     * @return The command line itself.
+     */
+    public CommandLine addArguments(final String[] addArguments) {
+        return addArguments(addArguments, true);
+    }
+
+    /**
+     * Add multiple arguments.
+     *
+     * @param addArguments  An array of arguments.
+     * @param handleQuoting Add the argument with/without handling quoting.
+     * @return The command line itself.
+     */
+    public CommandLine addArguments(final String[] addArguments, final boolean 
handleQuoting) {
+        if (addArguments != null) {
+            for (final String addArgument : addArguments) {
+                addArgument(addArgument, handleQuoting);
+            }
+        }
+        return this;
+    }
+
+    /**
+     * Expand variables in a command line argument.
+     *
+     * @param argument the argument.
+     * @return the expanded string.
+     */
+    private String expandArgument(final String argument) {
+        final StringBuffer stringBuffer = 
StringUtils.stringSubstitution(argument, getSubstitutionMap(), true);
+        return stringBuffer.toString();
+    }
+
+    /**
+     * Gets the expanded and quoted command line arguments.
+     *
+     * @return The quoted arguments.
+     */
+    public String[] getArguments() {
+
+        Argument currArgument;
+        String expandedArgument;
+        final String[] result = new String[arguments.size()];
+
+        for (int i = 0; i < result.length; i++) {
+            currArgument = arguments.get(i);
+            expandedArgument = expandArgument(currArgument.getValue());
+            result[i] = currArgument.isHandleQuoting() ? 
StringUtils.quoteArgument(expandedArgument) : expandedArgument;
+        }
+
+        return result;
+    }
+
+    /**
+     * Gets 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));
+    }
+
+    /**
+     * Gets the substitution map.
+     *
+     * @return the substitution map.
+     */
+    public Map<String, ?> getSubstitutionMap() {
+        return substitutionMap;
+    }
+
+    /**
+     * Tests whether a file was used to set the executable.
+     *
+     * @return true whether a file was used for setting the executable.
+     */
+    public boolean isFile() {
+        return isFile;
+    }
+
+    /**
+     * Sets the substitutionMap to expand variables in the command line.
+     *
+     * @param substitutionMap the map
+     */
+    public void setSubstitutionMap(final Map<String, ?> substitutionMap) {
+        this.substitutionMap = substitutionMap;
+    }
+
+    /**
+     * Cleans the executable string. The argument is trimmed and '/' and '\\' 
are replaced with the platform specific file separator char
+     *
+     * @param dirtyExecutable the executable.
+     * @return the platform-specific executable string.
+     * @throws NullPointerException     on null input.
+     * @throws IllegalArgumentException on empty input.
+     */
+    private String toCleanExecutable(final String dirtyExecutable) {
+        Objects.requireNonNull(dirtyExecutable, "dirtyExecutable");
+        if (dirtyExecutable.trim().isEmpty()) {
+            throw new IllegalArgumentException("Executable cannot be empty");
+        }
+        return StringUtils.fixFileSeparatorChar(dirtyExecutable);
+    }
+
+    /**
+     * 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.
+     */
+    @Override
+    public String toString() {
+        return "[" + String.join(", ", toStrings()) + "]";
+    }
+
+    /**
+     * Converts 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] = getExecutable();
+        System.arraycopy(getArguments(), 0, result, 1, result.length - 1);
+        return result;
+    }
+}
diff --git a/src/main/java/org/apache/commons/exec/Executor.java 
b/src/main/java/org/apache/commons/exec/Executor.java
index 0eb0e3cb..1ac6a01d 100644
--- a/src/main/java/org/apache/commons/exec/Executor.java
+++ b/src/main/java/org/apache/commons/exec/Executor.java
@@ -1,182 +1,182 @@
-/*
- * 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>
- * <p>
- * The following example shows the basic usage:
- * </p>
- *
- * <pre>
- * Executor exec = DefaultExecutor.builder().get();
- * CommandLine cl = new CommandLine("ls -l");
- * int exitvalue = exec.execute(cl);
- * </pre>
- */
-
-public interface Executor {
-
-    /** Invalid exit code. */
-    int INVALID_EXITVALUE = 0xdeadbeef;
-
-    /**
-     * Executes a command synchronously. 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)}.
-     * @throws IOException      If an I/O error occurs.
-     */
-    int execute(CommandLine command) throws ExecuteException, IOException;
-
-    /**
-     * Executes a command asynchronously. 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.
-     * @throws IOException      If an I/O error occurs.
-     */
-    void execute(CommandLine command, ExecuteResultHandler handler) throws 
ExecuteException, IOException;
-
-    /**
-     * Executes a command synchronously.
-     *
-     * @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)}.
-     * @throws IOException      If an I/O error occurs.
-     */
-    int execute(CommandLine command, Map<String, String> environment) throws 
ExecuteException, IOException;
-
-    /**
-     * Executes a command asynchronously. 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.
-     * @throws IOException      If an I/O error occurs.
-     */
-    void execute(CommandLine command, Map<String, String> environment, 
ExecuteResultHandler handler) throws ExecuteException, IOException;
-
-    /**
-     * Sets the handler for cleanup of started processes if the main process 
is going to terminate.
-     *
-     * @return the ProcessDestroyer.
-     */
-    ProcessDestroyer getProcessDestroyer();
-
-    /**
-     * Gets the StreamHandler used for providing input and retrieving the 
output.
-     *
-     * @return the StreamHandler.
-     */
-    ExecuteStreamHandler getStreamHandler();
-
-    /**
-     * Gets the watchdog used to kill of processes running, typically, too 
long time.
-     *
-     * @return the watchdog.
-     */
-    ExecuteWatchdog getWatchdog();
-
-    /**
-     * Gets the working directory of the created process.
-     *
-     * @return the working directory.
-     */
-    File getWorkingDirectory();
-
-    /**
-     * Tests whether {@code exitValue} 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} if {@code exitValue} signals a failure.
-     */
-    boolean isFailure(final int exitValue);
-
-    /**
-     * Sets the {@code exitValue} 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);
-
-    /**
-     * Sets a list of {@code exitValue} 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);
-
-    /**
-     * Sets the handler for cleanup of started processes if the main process 
is going to terminate.
-     *
-     * @param processDestroyer the ProcessDestroyer.
-     */
-    void setProcessDestroyer(ProcessDestroyer processDestroyer);
-
-    /**
-     * Sets 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 Process 
Process}).
-     *
-     * @param streamHandler the stream handler.
-     */
-    void setStreamHandler(ExecuteStreamHandler streamHandler);
-
-    /**
-     * Sets the watchdog used to kill of processes running, typically, too 
long time.
-     *
-     * @param watchDog the watchdog.
-     */
-    void setWatchdog(ExecuteWatchdog watchDog);
-
-    /**
-     * Sets 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);
-}
+/*
+ * 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>
+ * <p>
+ * The following example shows the basic usage:
+ * </p>
+ *
+ * <pre>
+ * Executor exec = DefaultExecutor.builder().get();
+ * CommandLine cl = new CommandLine("ls -l");
+ * int exitvalue = exec.execute(cl);
+ * </pre>
+ */
+
+public interface Executor {
+
+    /** Invalid exit code. */
+    int INVALID_EXITVALUE = 0xdeadbeef;
+
+    /**
+     * Executes a command synchronously. 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)}.
+     * @throws IOException      If an I/O error occurs.
+     */
+    int execute(CommandLine command) throws ExecuteException, IOException;
+
+    /**
+     * Executes a command asynchronously. 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.
+     * @throws IOException      If an I/O error occurs.
+     */
+    void execute(CommandLine command, ExecuteResultHandler handler) throws 
ExecuteException, IOException;
+
+    /**
+     * Executes a command synchronously.
+     *
+     * @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)}.
+     * @throws IOException      If an I/O error occurs.
+     */
+    int execute(CommandLine command, Map<String, String> environment) throws 
ExecuteException, IOException;
+
+    /**
+     * Executes a command asynchronously. 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.
+     * @throws IOException      If an I/O error occurs.
+     */
+    void execute(CommandLine command, Map<String, String> environment, 
ExecuteResultHandler handler) throws ExecuteException, IOException;
+
+    /**
+     * Sets the handler for cleanup of started processes if the main process 
is going to terminate.
+     *
+     * @return the ProcessDestroyer.
+     */
+    ProcessDestroyer getProcessDestroyer();
+
+    /**
+     * Gets the StreamHandler used for providing input and retrieving the 
output.
+     *
+     * @return the StreamHandler.
+     */
+    ExecuteStreamHandler getStreamHandler();
+
+    /**
+     * Gets the watchdog used to kill of processes running, typically, too 
long time.
+     *
+     * @return the watchdog.
+     */
+    ExecuteWatchdog getWatchdog();
+
+    /**
+     * Gets the working directory of the created process.
+     *
+     * @return the working directory.
+     */
+    File getWorkingDirectory();
+
+    /**
+     * Tests whether {@code exitValue} 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} if {@code exitValue} signals a failure.
+     */
+    boolean isFailure(final int exitValue);
+
+    /**
+     * Sets the {@code exitValue} 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);
+
+    /**
+     * Sets a list of {@code exitValue} 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);
+
+    /**
+     * Sets the handler for cleanup of started processes if the main process 
is going to terminate.
+     *
+     * @param processDestroyer the ProcessDestroyer.
+     */
+    void setProcessDestroyer(ProcessDestroyer processDestroyer);
+
+    /**
+     * Sets 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 Process 
Process}).
+     *
+     * @param streamHandler the stream handler.
+     */
+    void setStreamHandler(ExecuteStreamHandler streamHandler);
+
+    /**
+     * Sets the watchdog used to kill of processes running, typically, too 
long time.
+     *
+     * @param watchDog the watchdog.
+     */
+    void setWatchdog(ExecuteWatchdog watchDog);
+
+    /**
+     * Sets 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);
+}
diff --git a/src/main/java/org/apache/commons/exec/ProcessDestroyer.java 
b/src/main/java/org/apache/commons/exec/ProcessDestroyer.java
index 0b00022c..0ad10add 100644
--- a/src/main/java/org/apache/commons/exec/ProcessDestroyer.java
+++ b/src/main/java/org/apache/commons/exec/ProcessDestroyer.java
@@ -1,49 +1,49 @@
-/*
- * 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 Process} after a certain event, typically 
when the VM exits.
- *
- * @see ShutdownHookProcessDestroyer
- */
-public interface ProcessDestroyer {
-
-    /**
-     * Returns {@code true} if the specified {@link Process} was successfully 
added to the list of processes to be destroy.
-     *
-     * @param process the process to add.
-     * @return {@code true} if the specified {@link Process} was successfully 
added.
-     */
-    boolean add(Process process);
-
-    /**
-     * Returns {@code true} if the specified {@link Process} was successfully 
removed from the list of processes to be destroy.
-     *
-     * @param process the process to remove.
-     * @return {@code true} if the specified {@link Process} was successfully 
removed.
-     */
-    boolean remove(Process process);
-
-    /**
-     * Returns the number of registered processes.
-     *
-     * @return the number of register process.
-     */
-    int size();
-}
+/*
+ * 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 Process} after a certain event, typically 
when the VM exits.
+ *
+ * @see ShutdownHookProcessDestroyer
+ */
+public interface ProcessDestroyer {
+
+    /**
+     * Returns {@code true} if the specified {@link Process} was successfully 
added to the list of processes to be destroy.
+     *
+     * @param process the process to add.
+     * @return {@code true} if the specified {@link Process} was successfully 
added.
+     */
+    boolean add(Process process);
+
+    /**
+     * Returns {@code true} if the specified {@link Process} was successfully 
removed from the list of processes to be destroy.
+     *
+     * @param process the process to remove.
+     * @return {@code true} if the specified {@link Process} was successfully 
removed.
+     */
+    boolean remove(Process process);
+
+    /**
+     * Returns the number of registered processes.
+     *
+     * @return the number of register process.
+     */
+    int size();
+}

Reply via email to