[maven-release-plugin]  copy for tag EXEC_1_0_0

git-svn-id: https://svn.apache.org/repos/asf/commons/proper/exec/tags/EXEC_1_0_0@747204 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/NOTICE.txt b/NOTICE.txt
index c1c880c..4d95380 100644
--- a/NOTICE.txt
+++ b/NOTICE.txt
@@ -1,5 +1,5 @@
 Apache Commons Exec
-Copyright 2005-2008 The Apache Software Foundation
+Copyright 2005-2009 The Apache Software Foundation
 
 This product includes software developed by
 The Apache Software Foundation (http://www.apache.org/).
diff --git a/build.xml b/build.xml
index 54cf038..cfd11de 100644
--- a/build.xml
+++ b/build.xml
@@ -112,6 +112,9 @@
     </copy>
     <!-- make the shell script files readable/executable -->
     <chmod dir="${maven.build.directory}/dist" perm="ugo+rx" includes="**/*.sh"/>
+    <!-- copy the various legal files -->
+    <copy file="${basedir}/NOTICE.txt" tofile="${maven.build.directory}/dist/NOTICE.txt"/>    
+    <copy file="${basedir}/LICENSE.txt" tofile="${maven.build.directory}/dist/LICENSE.txt"/>        
   </target>
   
   <!-- Create a zip containing the test environment -->
diff --git a/findbugs-exclude-filter.xml b/findbugs-exclude-filter.xml
new file mode 100644
index 0000000..39990fe
--- /dev/null
+++ b/findbugs-exclude-filter.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0"?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+
+<!--
+  This file contains some false positive bugs detected by findbugs. Their
+  false positive nature has been analyzed individually and they have been
+  put here to instruct findbugs it must ignore them.
+-->
+<FindBugsFilter>
+
+  <!-- The following cases are intentional hard-coded paths for different operating systems -->
+  <Match>
+    <Class name="org.apache.commons.exec.environment.DefaultProcessingEnvironment" />
+    <Method name="getProcEnvCommand" params="" returns="org.apache.commons.exec.CommandLine" />
+    <Bug pattern="DMI_HARDCODED_ABSOLUTE_FILENAME" />
+  </Match>
+
+</FindBugsFilter>
diff --git a/pom.xml b/pom.xml
index 8312531..f41620a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -31,6 +31,11 @@
   <description>A library to reliably execute external processes from within the JVM</description>
   <url>http://commons.apache.org/exec/</url>  
   
+  <issueManagement>
+      <system>jira</system>
+      <url>http://issues.apache.org/jira/browse/EXEC</url>
+  </issueManagement>
+    
   <dependencies>
    <dependency>
       <groupId>junit</groupId>
@@ -128,6 +133,16 @@
           </reportSet>
         </reportSets>
       </plugin>           
+      <plugin>
+        <groupId>org.codehaus.mojo</groupId>
+        <artifactId>findbugs-maven-plugin</artifactId>
+        <version>1.2</version>
+        <configuration>
+          <threshold>Normal</threshold>
+          <effort>Default</effort>
+          <excludeFilterFile>${basedir}/findbugs-exclude-filter.xml</excludeFilterFile>
+       </configuration>
+      </plugin>
     </plugins>
   </reporting>
 
@@ -171,7 +186,7 @@
     <commons.jira.pid>12310814</commons.jira.pid>
     <commons.release.version>1.0.0</commons.release.version>
     <!-- The RC version used in the staging repository URL. -->
-    <commons.rc.version>RC3</commons.rc.version>
+    <commons.rc.version>RC4</commons.rc.version>
   </properties>
 
-</project>
\ No newline at end of file
+</project>
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 9f23bc8..2005940 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -23,7 +23,22 @@
     <author email="sgoeschl@apache.org">Siegfried Goeschl</author>
   </properties>
   <body>
-    <release version="1.0.0" date="2009-01-05" description="Sandbox release">
+    <release version="1.0.0" date="2009-02-24" 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".
diff --git a/src/main/java/org/apache/commons/exec/CommandLine.java b/src/main/java/org/apache/commons/exec/CommandLine.java
index ef72f42..1adf3fb 100644
--- a/src/main/java/org/apache/commons/exec/CommandLine.java
+++ b/src/main/java/org/apache/commons/exec/CommandLine.java
@@ -34,12 +34,12 @@
     /**
      * The arguments of the command.
      */
-    private Vector arguments = new Vector();
+    private final Vector arguments = new Vector();
 
     /**
      * The program to execute.
      */
-    private String executable;
+    private final String executable;
 
     /**
      * A map of name value pairs used to expand command line arguments
@@ -54,9 +54,7 @@
     /**
      * Create a command line from a string.
      * 
-     * @param line
-     *            the line: the first element becomes the executable, the rest
-     *            the arguments
+     * @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
      */
@@ -67,9 +65,7 @@
     /**
      * Create a command line from a string.
      *
-     * @param line
-     *            the line: the first element becomes the executable, the rest
-     *            the arguments
+     * @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
@@ -100,7 +96,7 @@
      */
     public CommandLine(String executable) {
         this.isFile=false;
-        setExecutable(executable);
+        this.executable=getExecutable(executable);
     }
 
     /**
@@ -110,7 +106,7 @@
      */
     public CommandLine(File executable) {
         this.isFile=true;
-        setExecutable(executable.getAbsolutePath());
+        this.executable=getExecutable(executable.getAbsolutePath());
     }
 
     /**
@@ -240,7 +236,8 @@
 
     /**
      * Set the substitutionMap to expand variables in the
-     * command line
+     * command line.
+     * 
      * @param substitutionMap the map
      */
     public void setSubstitutionMap(Map substitutionMap) {
@@ -394,18 +391,19 @@
     }
 
     /**
-     * Set the executable - the argument is trimmed and '/' and '\\' are
+     * Get the executable - the argument is trimmed and '/' and '\\' are
      * replaced with the platform specific file seperator char
      *
      * @param executable the executable
+     * @return the platform-specific executable string
      */
-    private void setExecutable(final String executable) {
+    private String getExecutable(final String executable) {
         if (executable == null) {
             throw new IllegalArgumentException("Executable can not be null");
         } else if(executable.trim().length() == 0) {
             throw new IllegalArgumentException("Executable can not be empty");
         } else {
-            this.executable = StringUtils.fixFileSeparatorChar(executable);
+            return StringUtils.fixFileSeparatorChar(executable);
         }
     }
 }
diff --git a/src/main/java/org/apache/commons/exec/DefaultExecutor.java b/src/main/java/org/apache/commons/exec/DefaultExecutor.java
index 7cb5201..19955bc 100644
--- a/src/main/java/org/apache/commons/exec/DefaultExecutor.java
+++ b/src/main/java/org/apache/commons/exec/DefaultExecutor.java
@@ -42,8 +42,6 @@
  * CommandLine cl = new CommandLine("ls -l");
  * int exitvalue = exec.execute(cl);
  * </pre>
- *
- *
  */
 public class DefaultExecutor implements Executor {
 
@@ -60,7 +58,7 @@
     private int[] exitValues;
 
     /** launches the command in a new process */
-    private CommandLauncher launcher;
+    private final CommandLauncher launcher;
 
     /** optional cleanup of started processes */ 
     private ProcessDestroyer processDestroyer;
@@ -200,7 +198,7 @@
 
     /** @see org.apache.commons.exec.Executor#setExitValues(int[]) */
     public void setExitValues(final int[] values) {
-        this.exitValues = values;
+        this.exitValues = (int[]) values.clone();
     }
 
     /** @see org.apache.commons.exec.Executor#isFailure(int) */
diff --git a/src/main/java/org/apache/commons/exec/ExecuteException.java b/src/main/java/org/apache/commons/exec/ExecuteException.java
index 1cf8aa0..fdb8b0a 100644
--- a/src/main/java/org/apache/commons/exec/ExecuteException.java
+++ b/src/main/java/org/apache/commons/exec/ExecuteException.java
@@ -33,12 +33,12 @@
 	/**
 	 * The underlying cause of this exception.
 	 */
-	private Throwable cause;
+	private final Throwable cause;
 
 	/**
 	 * The exit value returned by the failed process
 	 */
-	private int exitValue;
+	private final int exitValue;
     
     /**
      * Construct a new exception with the specified detail message.
@@ -49,6 +49,7 @@
      */
     public ExecuteException(final String message, int exitValue) {
         super(message + "(Exit value: " + exitValue + ")");
+        this.cause = null;
         this.exitValue = exitValue;
     }
 
diff --git a/src/main/java/org/apache/commons/exec/ExecuteWatchdog.java b/src/main/java/org/apache/commons/exec/ExecuteWatchdog.java
index a386ddc..8eb00a9 100644
--- a/src/main/java/org/apache/commons/exec/ExecuteWatchdog.java
+++ b/src/main/java/org/apache/commons/exec/ExecuteWatchdog.java
@@ -49,7 +49,7 @@
     private boolean killedProcess;
 
     /** Will tell us whether timeout has occurred. */
-    private Watchdog watchdog;
+    private final Watchdog watchdog;
 
     /**
      * Creates a new watchdog with a given timeout.
@@ -172,7 +172,7 @@
     /**
      * reset the monitor flag and the process.
      */
-    protected void cleanUp() {
+    protected synchronized void cleanUp() {
         watch = false;
         process = null;
     }    
diff --git a/src/main/java/org/apache/commons/exec/Executor.java b/src/main/java/org/apache/commons/exec/Executor.java
index 96aa554..37a7786 100644
--- a/src/main/java/org/apache/commons/exec/Executor.java
+++ b/src/main/java/org/apache/commons/exec/Executor.java
@@ -22,66 +22,130 @@
 import java.io.IOException;
 import java.util.Map;
 
+/**
+ * The main abstraction to start an external process.
+ *
+ * The interface allows to
+ * <ul>
+ *  <li>set a current working directory for the subprocess</li>
+ *  <li>provide a set of environment variables passed to the subprocess</li>
+ *  <li>capture the subprocess output of stdout and stderr using an ExecuteStreamHandler</li>
+ *  <li>kill long-running processes using an ExecuteWatchdog</li>
+ *  <li>define a set of expected exit values</li>
+ *  <li>terminate any started processes when the main process is terminating using a ProcessDestroyer</li>
+ * </ul>
+ *
+ * The following example shows the basic usage:
+ *
+ * <pre>
+ * Executor exec = new DefaultExecutor();
+ * CommandLine cl = new CommandLine("ls -l");
+ * int exitvalue = exec.execute(cl);
+ * </pre>
+ */
+
 public interface Executor {
 
-    /** Invalid exit code. * */
+    /** Invalid exit code. */
     int INVALID_EXITVALUE = 0xdeadbeef;
 
-    /*
+    /**
      * Define the exit code of the process to considered
      * successful.
+     *
+     * @param value the exit code representing successful execution
      */
     void setExitValue(final int value);
 
-    /*
+    /**
      * Define the exit code of the process to considered
-     * successful using one of the following values
+     * 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</li>
      *  <li>null to indicate to skip checking of exit codes</li>
      * </ul>
+     *
+     * @param values a list of the exit codes
      */
     void setExitValues(final int[] values);
 
     /**
      * Checks whether <code>exitValue</code> signals a failure. If no
      * exit values are set than the default conventions of the OS is
-     * used. 
+     * used. e.g. most OS regard an exit code of '0' as successful
+     * execution and everything else as failure.
      *
      * @param exitValue the exit value (return code) to be checked
      * @return <code>true</code> if <code>exitValue</code> signals a failure
      */
     boolean isFailure(final int exitValue);
 
-    /*
-     * StreamHandlers are used for providing input, 
-     * retriving the output. Also used for logging.  
+    /**
+     * Get the StreamHandler used for providing input and
+     * retriving the output.
+     * 
+     * @return the StreamHandler 
      */
     ExecuteStreamHandler getStreamHandler();
+
+    /**
+     * Set the StreamHandler used for providing input and
+     * retriving the output.
+     *
+     * @param streamHandler the StreamHandler
+     */
+
     void setStreamHandler(ExecuteStreamHandler streamHandler);
 
-    /*
-     * Watchdog is used to kill of processes running, 
-     * typically, too long time. 
+    /**
+     * Get the watchdog used to kill of processes running,
+     * typically, too long time.
+     *
+     * @return the watchdog
      */
     ExecuteWatchdog getWatchdog();
+
+    /**
+     * Set the watchdog used to kill of processes running, 
+     * typically, too long time.
+     *
+     * @param watchDog the watchdog
+     */
     void setWatchdog(ExecuteWatchdog watchDog);
 
     /**
-     * Optinal cleanup of started processes if the main process
+     * Set the handler for cleanup of started processes if the main process
      * is going to terminate.
+     *
+     * @return the ProcessDestroyer
      */
     ProcessDestroyer getProcessDestroyer();
+
+    /**
+     * Get the handler for cleanup of started processes if the main process
+     * is going to terminate.
+     *
+     * @param processDestroyer the ProcessDestroyer
+     */
     void setProcessDestroyer(ProcessDestroyer processDestroyer);
 
-    /*
-     * Set the working directory of the created process.
+    /**
+     * Get the working directory of the created process.
+     *
+     * @return the working directory
      */
     File getWorkingDirectory();
+
+    /**
+     * Set the working directory of the created process. The
+     * working directory must exist when you start the process.
+     *
+     * @param dir the working directory
+     */
     void setWorkingDirectory(File dir);
 
-    /*
+    /**
      * Methods for starting synchronous execution. The child process inherits
      * all environment variables of the parent process.
      *
@@ -89,44 +153,44 @@
      * @return process exit value
      * @throws ExecuteException execution of subprocess failed
      */
-    int execute(CommandLine command) throws ExecuteException, IOException;
+    int execute(CommandLine command)
+        throws ExecuteException, IOException;
 
-    /*
-     * Methods for starting synchronous execution. If
+    /**
+     * Methods for starting synchronous execution.
      *
      * @param command the command to execute
-     * @param environment The environment for the new process. If null, the environment
-     *          of the current process is used.
+     * @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
      */
-    int execute(CommandLine command, Map environment) throws ExecuteException, IOException;
+    int execute(CommandLine command, Map environment)
+        throws ExecuteException, IOException;
     
-    /*
-     * Methods for starting asynchronous execution. Result provided to callback handler
-     */
-
-    /*
-     * Methods for starting synchronous execution. The child process inherits
+    /**
+     * Methods for starting asynchronous execution. The child process inherits
      * all environment variables of the parent process. Result provided to
      * callback handler.
      *
      * @param command the command to execute
-     * @return process exit value
+     * @param handler capture process termination and exit code
      * @throws ExecuteException execution of subprocess failed
      */
-    void execute(CommandLine command, ExecuteResultHandler handler) throws ExecuteException, IOException;
+    void execute(CommandLine command, ExecuteResultHandler handler)
+        throws ExecuteException, IOException;
 
-    /*
-     * Methods for starting synchronous execution. The child process inherits
+    /**
+     * Methods for starting asynchronous execution. The child process inherits
      * all environment variables of the parent process. Result provided to
      * callback handler.
      *
      * @param command the command to execute
-     * @param environment The environment for the new process. If null, the environment
-     *          of the current process is used.
-     * @return process exit value
+     * @param environment The environment for the new process. If null, the
+     *          environment of the current process is used.
+     * @param handler capture process termination and exit code 
      * @throws ExecuteException execution of subprocess failed     
      */
-    void execute(CommandLine command, Map environment, ExecuteResultHandler handler) throws ExecuteException, IOException;
+    void execute(CommandLine command, Map environment, ExecuteResultHandler handler)
+        throws ExecuteException, IOException;
 }
diff --git a/src/main/java/org/apache/commons/exec/LogOutputStream.java b/src/main/java/org/apache/commons/exec/LogOutputStream.java
index 559032a..bc79386 100644
--- a/src/main/java/org/apache/commons/exec/LogOutputStream.java
+++ b/src/main/java/org/apache/commons/exec/LogOutputStream.java
@@ -41,12 +41,20 @@
     private static final int LF = 0x0a;
 
     /** the internal buffer */
-    private ByteArrayOutputStream buffer = new ByteArrayOutputStream(
+    private final ByteArrayOutputStream buffer = new ByteArrayOutputStream(
       INTIAL_SIZE);
 
     private boolean skip = false;
 
-    private int level = 999;
+    private final int level;
+
+    /**
+     * Creates a new instance of this class.
+     * Uses the default level of 999.
+     */
+    public LogOutputStream() {
+        this(999);
+    }
 
     /**
      * Creates a new instance of this class.
diff --git a/src/main/java/org/apache/commons/exec/PumpStreamHandler.java b/src/main/java/org/apache/commons/exec/PumpStreamHandler.java
index b467882..6f1f4bd 100644
--- a/src/main/java/org/apache/commons/exec/PumpStreamHandler.java
+++ b/src/main/java/org/apache/commons/exec/PumpStreamHandler.java
@@ -26,7 +26,8 @@
 
 /**
  * Copies standard output and error of subprocesses to standard output and error
- * of the parent process.
+ * of the parent process. If output or error stream are set to null, any feedback
+ * from that stream will be lost. 
  */
 public class PumpStreamHandler implements ExecuteStreamHandler {
 
@@ -36,11 +37,11 @@
 
     private Thread inputThread;
 
-    private OutputStream out;
+    private final OutputStream out;
 
-    private OutputStream err;
+    private final OutputStream err;
 
-    private InputStream input;
+    private final InputStream input;
 
     /**
      * Construct a new <CODE>PumpStreamHandler</CODE>.
@@ -54,6 +55,13 @@
      */
     public PumpStreamHandler(final OutputStream out, final OutputStream err,
             final InputStream input) {
+
+        // see EXEC-33
+        if(input == System.in) {
+            String msg = "Using System.in is currently not supported since it would hang your application (see EXEC-33).";
+            throw new IllegalArgumentException(msg);
+        }
+
         this.out = out;
         this.err = err;
         this.input = input;
@@ -96,7 +104,9 @@
      *            the <CODE>InputStream</CODE>.
      */
     public void setProcessOutputStream(final InputStream is) {
-        createProcessOutputPump(is, out);
+        if (out != null) {
+            createProcessOutputPump(is, out);
+        }
     }
 
     /**
@@ -136,8 +146,12 @@
      * Start the <CODE>Thread</CODE>s.
      */
     public void start() {
-        outputThread.start();
-        errorThread.start();
+        if (outputThread != null) {
+            outputThread.start();
+        }
+        if (errorThread != null) {
+            errorThread.start();
+        }
         if (inputThread != null) {
             inputThread.start();
         }
@@ -147,37 +161,51 @@
      * Stop pumping the streams.
      */
     public void stop() {
-        try {
-            outputThread.join();
-        } catch (InterruptedException e) {
-            // ignore
-        }
-        try {
-            errorThread.join();
-        } catch (InterruptedException e) {
-            // ignore
-        }
 
-        if (inputThread != null) {
+        if (outputThread != null) {
             try {
-                inputThread.join();
+                outputThread.join();
+                outputThread = null;
             } catch (InterruptedException e) {
                 // ignore
             }
         }
 
-        try {
-            err.flush();
-        } catch (IOException e) {
-            String msg = "Got exception while flushing the error stream";
-            DebugUtils.handleException(msg ,e);
+        if (errorThread != null) {
+            try {
+                errorThread.join();
+                errorThread = null;
+            } catch (InterruptedException e) {
+                // ignore
+            }
         }
-        try {
-            out.flush();
-        } catch (IOException e) {
-            String msg = "Got exception while flushing the output stream";
-            DebugUtils.handleException(msg ,e);
+
+        if (inputThread != null) {
+            try {
+                inputThread.join();
+                inputThread = null;
+            } catch (InterruptedException e) {
+                // ignore
+            }
         }
+
+         if (err != null && err != out) {
+             try {
+                 err.flush();
+             } catch (IOException e) {
+                 String msg = "Got exception while flushing the error stream";
+                 DebugUtils.handleException(msg ,e);
+             }
+         }
+
+         if (out != null) {
+             try {
+                 out.flush();
+             } catch (IOException e) {
+                 String msg = "Got exception while flushing the output stream";
+                 DebugUtils.handleException(msg ,e);
+             }
+         }
     }
 
     /**
@@ -227,6 +255,10 @@
     /**
      * Creates a stream pumper to copy the given input stream to the given
      * output stream.
+     *
+     * @param is the input stream to copy from
+     * @param os the output stream to copy into
+     * @return the stream pumper thread
      */
     protected Thread createPump(final InputStream is, final OutputStream os) {
         return createPump(is, os, false);
@@ -235,6 +267,11 @@
     /**
      * Creates a stream pumper to copy the given input stream to the given
      * output stream.
+     *
+     * @param is the input stream to copy from
+     * @param os the output stream to copy into
+     * @param closeWhenExhausted close the output stream when the input stream is exhausted
+     * @return the stream pumper thread
      */
     protected Thread createPump(final InputStream is, final OutputStream os,
             final boolean closeWhenExhausted) {
diff --git a/src/main/java/org/apache/commons/exec/StreamPumper.java b/src/main/java/org/apache/commons/exec/StreamPumper.java
index 1306e26..1fd4697 100644
--- a/src/main/java/org/apache/commons/exec/StreamPumper.java
+++ b/src/main/java/org/apache/commons/exec/StreamPumper.java
@@ -33,19 +33,19 @@
     private static final int DEFAULT_SIZE = 1024;
 
     /** the input stream to pump from */
-    private InputStream is;
+    private final InputStream is;
 
     /** the output stream to pmp into */
-    private OutputStream os;
+    private final OutputStream os;
 
     /** the size of the internal buffer for copying the streams */ 
-    private int size;
+    private final int size;
 
     /** was the end of the stream reached */
     private boolean finished;
 
     /** close the output stream when exhausted */
-    private boolean closeWhenExhausted;
+    private final boolean closeWhenExhausted;
     
     /**
      * Create a new stream pumper.
diff --git a/src/main/java/org/apache/commons/exec/Watchdog.java b/src/main/java/org/apache/commons/exec/Watchdog.java
index 754d072..f9eecff 100644
--- a/src/main/java/org/apache/commons/exec/Watchdog.java
+++ b/src/main/java/org/apache/commons/exec/Watchdog.java
@@ -30,13 +30,13 @@
 
     private Vector observers = new Vector(1);
 
-    private long timeout = -1;
+    private final long timeout;
 
     private boolean stopped = false;
 
     public Watchdog(final long timeout) {
         if (timeout < 1) {
-            throw new IllegalArgumentException("timeout lesser than 1.");
+            throw new IllegalArgumentException("timeout must not be less than 1.");
         }
         this.timeout = timeout;
     }
diff --git a/src/main/java/org/apache/commons/exec/environment/EnvironmentUtils.java b/src/main/java/org/apache/commons/exec/environment/EnvironmentUtils.java
index 77da29f..c102cb3 100644
--- a/src/main/java/org/apache/commons/exec/environment/EnvironmentUtils.java
+++ b/src/main/java/org/apache/commons/exec/environment/EnvironmentUtils.java
@@ -30,7 +30,7 @@
 public class EnvironmentUtils
 {
 
-	private static DefaultProcessingEnvironment procEnvironment;
+	private static final DefaultProcessingEnvironment procEnvironment;
 	
 	static {
         if (OS.isFamilyOpenVms()) {
@@ -84,13 +84,14 @@
 
     /**
      * Add a key/value pair to the given environment.
+     * If the key matches an existing key, the previous setting is replaced.
      *
      * @param environment the current environment
      * @param keyAndValue the key/value pair 
      */
     public static void addVariableToEnvironment(Map environment, String keyAndValue) {
-		String[] parsedVarible = parseEnvironmentVariable(keyAndValue);		
-		environment.put(parsedVarible[0], parsedVarible[1]);
+		String[] parsedVariable = parseEnvironmentVariable(keyAndValue);		
+		environment.put(parsedVariable[0], parsedVariable[1]);
 	}
     
     /**
diff --git a/src/main/java/org/apache/commons/exec/environment/OpenVmsProcessingEnvironment.java b/src/main/java/org/apache/commons/exec/environment/OpenVmsProcessingEnvironment.java
index 5af1014..4bff50d 100644
--- a/src/main/java/org/apache/commons/exec/environment/OpenVmsProcessingEnvironment.java
+++ b/src/main/java/org/apache/commons/exec/environment/OpenVmsProcessingEnvironment.java
@@ -37,7 +37,7 @@
      * @return a amp containing the environment variables
      * @throws IOException the operation failed
      */    
-    protected synchronized Map createProcEnvironment() throws IOException {
+    protected Map createProcEnvironment() throws IOException {
         if (procEnvironment == null) {
             procEnvironment = new HashMap();
 
@@ -109,8 +109,8 @@
             logicals.put(logName, logValue);
         }
 
-        for (Iterator i = logicals.keySet().iterator(); i.hasNext();) {
-            String logical = (String) i.next();
+        for (Iterator i = logicals.entrySet().iterator(); i.hasNext();) {
+            String logical = (String) ((Map.Entry) i.next()).getKey();
             environment.put(logical, logicals.get(logical));
         }
         return environment;
diff --git a/src/main/java/org/apache/commons/exec/launcher/CommandLauncherProxy.java b/src/main/java/org/apache/commons/exec/launcher/CommandLauncherProxy.java
index 4ce8c09..64f997a 100644
--- a/src/main/java/org/apache/commons/exec/launcher/CommandLauncherProxy.java
+++ b/src/main/java/org/apache/commons/exec/launcher/CommandLauncherProxy.java
@@ -33,7 +33,7 @@
         myLauncher = launcher;
     }
 
-    private CommandLauncher myLauncher;
+    private final CommandLauncher myLauncher;
 
     /**
      * Launches the given command in a new process. Delegates this method to the
diff --git a/src/main/java/org/apache/commons/exec/util/MapUtils.java b/src/main/java/org/apache/commons/exec/util/MapUtils.java
index 3e1f973..5e69785 100644
--- a/src/main/java/org/apache/commons/exec/util/MapUtils.java
+++ b/src/main/java/org/apache/commons/exec/util/MapUtils.java
@@ -59,17 +59,18 @@
      */
     public static Map prefix(Map source, String prefix) {
 
-        Map result = new HashMap();
-
         if(source == null) {
             return null;
         }
 
-        Iterator iter = source.keySet().iterator();
+        Map result = new HashMap();
+
+        Iterator iter = source.entrySet().iterator();
 
         while(iter.hasNext()) {
-            Object key = iter.next();
-            Object value = source.get(key);
+            Map.Entry entry = (Map.Entry) iter.next();
+            Object key = entry.getKey();
+            Object value = entry.getValue();
             result.put(prefix + '.' + key.toString(), value);
         }
 
diff --git a/src/test/java/org/apache/commons/exec/CommandLineTest.java b/src/test/java/org/apache/commons/exec/CommandLineTest.java
index 05ee0c7..2e3d7fc 100644
--- a/src/test/java/org/apache/commons/exec/CommandLineTest.java
+++ b/src/test/java/org/apache/commons/exec/CommandLineTest.java
@@ -239,6 +239,28 @@
         assertEquals("cmd /C convert source.jpg -resize \"500x> \" target.jpg", cmdl.toString());        
     }
 
+    /**
+     * Another  command line parsing puzzle from Kai Hu - as
+     * far as I understand it there is no way to express that
+     * in a one-line command string.
+     */
+    public void testParseComplexCommandLine2() {
+
+        String commandline = "./script/jrake cruise:publish_installers "
+            + "INSTALLER_VERSION=unstable_2_1 "
+            + "INSTALLER_PATH=\"/var/lib/ cruise-agent/installers\" "
+            + "INSTALLER_DOWNLOAD_SERVER=\'something\' "
+            + "WITHOUT_HELP_DOC=true";
+
+        CommandLine cmdl = CommandLine.parse(commandline);
+        String[] args = cmdl.getArguments();
+        assertEquals(args[0], "cruise:publish_installers");
+        assertEquals(args[1], "INSTALLER_VERSION=unstable_2_1");
+        // assertEquals(args[2], "INSTALLER_PATH=\"/var/lib/ cruise-agent/installers\"");
+        // assertEquals(args[3], "INSTALLER_DOWNLOAD_SERVER='something'");
+        assertEquals(args[4], "WITHOUT_HELP_DOC=true");
+    }
+
    /**
     * Create a command line with pre-quoted strings to test SANDBOX-192,
     * e.g. "runMemorySud.cmd", "10", "30", "-XX:+UseParallelGC", "\"-XX:ParallelGCThreads=2\""
diff --git a/src/test/java/org/apache/commons/exec/DefaultExecutorTest.java b/src/test/java/org/apache/commons/exec/DefaultExecutorTest.java
index 8f4264f..7caed58 100644
--- a/src/test/java/org/apache/commons/exec/DefaultExecutorTest.java
+++ b/src/test/java/org/apache/commons/exec/DefaultExecutorTest.java
@@ -21,6 +21,8 @@
 import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.IOException;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -35,6 +37,7 @@
     private File errorTestScript = TestUtil.resolveScriptForOS(testDir + "/error");
     private File foreverTestScript = TestUtil.resolveScriptForOS(testDir + "/forever");
     private File nonExistingTestScript = TestUtil.resolveScriptForOS(testDir + "/grmpffffff");
+    private File redirectScript = TestUtil.resolveScriptForOS(testDir + "/redirect");
 
     // Get suitable exit codes for the OS
     private static final int SUCCESS_STATUS; // test script successful exit code
@@ -343,5 +346,90 @@
         int exitValue = exec.execute(cl);
         assertTrue(baos.toString().trim().indexOf("test $;`(0)[1]{2}") > 0);
         assertFalse(exec.isFailure(exitValue));
-    }    
+    }
+
+    /**
+     * Start a process with redirected streams - stdin of the newly
+     * created process is connected to a FileInputStream whereas
+     * the "redirect" script reads all lines from stdin and prints
+     * them on stdout. Furthermore the script prints a status
+     * message on stderr.
+     */
+    public void testExecuteWithRedirectedStreams() throws Exception
+    {
+        if(OS.isFamilyUnix())
+        {
+            FileInputStream fis = new FileInputStream("./NOTICE.txt");
+            CommandLine cl = new CommandLine(redirectScript);
+            PumpStreamHandler pumpStreamHandler = new PumpStreamHandler( System.out, System.out, fis );
+            DefaultExecutor executor = new DefaultExecutor();
+            executor.setWorkingDirectory(new File("."));
+            executor.setStreamHandler( pumpStreamHandler );
+            int exitValue = executor.execute(cl);
+            fis.close();
+            assertFalse(exec.isFailure(exitValue));
+        }
+    }
+
+    /**
+     * Start a process and connect stdin, stdout and stderr. This
+     * test currenty hang. Therefore we throw an IllegalArgument
+     * Exception to notify the user (see EXEC-33).
+     */
+    public void testExecuteWithStdin() throws Exception
+    {
+        try {
+            CommandLine cl = new CommandLine(testScript);
+            PumpStreamHandler pumpStreamHandler = new PumpStreamHandler( System.out, System.err, System.in );
+            DefaultExecutor executor = new DefaultExecutor();
+            executor.setStreamHandler( pumpStreamHandler );
+            int exitValue = executor.execute(cl);
+            assertFalse(exec.isFailure(exitValue));
+        }
+        catch(IllegalArgumentException e) {
+            assertTrue( e.getMessage().indexOf("EXEC-33") >= 0);
+        }
+    }
+
+     /**
+      * Start a process and connect stdout and stderr.
+      */
+     public void testExecuteWithStdOutErr() throws Exception
+     {
+         CommandLine cl = new CommandLine(testScript);
+         PumpStreamHandler pumpStreamHandler = new PumpStreamHandler( System.out, System.err );
+         DefaultExecutor executor = new DefaultExecutor();
+         executor.setStreamHandler( pumpStreamHandler );
+         int exitValue = executor.execute(cl);
+         assertFalse(exec.isFailure(exitValue));
+     }
+
+     /**
+      * Start a process and connect it to no stream.
+      */
+     public void testExecuteWithNullOutErr() throws Exception
+     {
+         CommandLine cl = new CommandLine(testScript);
+         PumpStreamHandler pumpStreamHandler = new PumpStreamHandler( null, null );
+         DefaultExecutor executor = new DefaultExecutor();
+         executor.setStreamHandler( pumpStreamHandler );
+         int exitValue = executor.execute(cl);
+         assertFalse(exec.isFailure(exitValue));
+     }
+
+     /**
+      * Start a process and connect out and err to a file.
+      */
+     public void testExecuteWithRedirectOutErr() throws Exception
+     {
+         File outfile = File.createTempFile("EXEC", ".test");
+         outfile.deleteOnExit();
+         CommandLine cl = new CommandLine(testScript);
+         PumpStreamHandler pumpStreamHandler = new PumpStreamHandler( new FileOutputStream(outfile) );
+         DefaultExecutor executor = new DefaultExecutor();
+         executor.setStreamHandler( pumpStreamHandler );
+         int exitValue = executor.execute(cl);
+         assertFalse(exec.isFailure(exitValue));
+         assertTrue(outfile.exists());
+     }
 }
diff --git a/src/test/java/org/apache/commons/exec/environment/EnvironmentUtilTest.java b/src/test/java/org/apache/commons/exec/environment/EnvironmentUtilTest.java
index 0d76884..e9a09da 100644
--- a/src/test/java/org/apache/commons/exec/environment/EnvironmentUtilTest.java
+++ b/src/test/java/org/apache/commons/exec/environment/EnvironmentUtilTest.java
@@ -31,21 +31,20 @@
 
 public class EnvironmentUtilTest extends TestCase {
 
-    public void testToStrings() throws IOException {
+    /**
+     * Tests the behaviour of the EnvironmentUtils.toStrings()
+     * when using a <code>null</code> environment.
+     */
+    public void testToStrings() {
+        // check for a non-existing environment when passing null
         TestUtil.assertEquals(null, EnvironmentUtils.toStrings(null), false);
-
+        // check for an environment when filling in two variables
         Map env = new HashMap();
-
         TestUtil.assertEquals(new String[0], EnvironmentUtils.toStrings(env), false);
-
         env.put("foo2", "bar2");
         env.put("foo", "bar");
-
         String[] envStrings = EnvironmentUtils.toStrings(env);
-
         String[] expected = new String[]{"foo=bar", "foo2=bar2"};
-
-
         TestUtil.assertEquals(expected, envStrings, false);
     }
 
@@ -57,7 +56,7 @@
     public void testGetProcEnvironment() throws IOException {
         Map procEnvironment = EnvironmentUtils.getProcEnvironment();
         // we assume that there is at least one environment variable
-        // for this process
+        // for this process, i.e. $JAVA_HOME
         assertTrue(procEnvironment.size() > 0);
         String[] envArgs = EnvironmentUtils.toStrings(procEnvironment);
         for(int i=0; i<envArgs.length; i++) {
@@ -80,11 +79,12 @@
 
         // ensure that we have the same value for upper and lowercase keys
         Map procEnvironment = EnvironmentUtils.getProcEnvironment();
-        for (Iterator it = procEnvironment.keySet().iterator(); it.hasNext();) {
-            String variable = (String) it.next();
-            String value = (String) procEnvironment.get(variable);
-            assertEquals(value, procEnvironment.get(variable.toLowerCase(Locale.ENGLISH)));
-            assertEquals(value, procEnvironment.get(variable.toUpperCase(Locale.ENGLISH)));
+        for (Iterator it = procEnvironment.entrySet().iterator(); it.hasNext();) {
+            Map.Entry entry = (Map.Entry) it.next();
+            String key = (String) entry.getKey();
+            String value = (String) entry.getValue();
+            assertEquals(value, procEnvironment.get(key.toLowerCase(Locale.ENGLISH)));
+            assertEquals(value, procEnvironment.get(key.toUpperCase(Locale.ENGLISH)));
         }
 
         // add an environment variable and check access
@@ -94,4 +94,17 @@
         assertEquals("bar", procEnvironment.get("foo"));
     }
 
+    /**
+     * Accessing environment variables is case-sensitive or not depending
+     * on the operating system but the values of the environment variable
+     * are always case-sensitive. So make sure that this assumption holds
+     * on all operating systems.
+     */
+    public void testCaseInsensitiveVariableLookup() throws Exception {
+        Map procEnvironment = EnvironmentUtils.getProcEnvironment();
+        // Check that case is preserved for values
+        EnvironmentUtils.addVariableToEnvironment( procEnvironment, "foo=bAr" );
+        assertEquals("bAr", procEnvironment.get("foo"));
+    }
+
 }
diff --git a/src/test/scripts/redirect.sh b/src/test/scripts/redirect.sh
new file mode 100755
index 0000000..dce5c99
--- /dev/null
+++ b/src/test/scripts/redirect.sh
@@ -0,0 +1,30 @@
+#!/bin/sh
+
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# read from stdin and output to stdout
+
+while read myline
+do
+  echo "stdout: $myline"
+done
+
+echo 1>&2 "stderr: Finished reading from stdin"
+
+exit 0
+