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(); +}