/*
* @(#)ProcessBuilder.java 1.9 06/03/22
*
* Copyright 2006 Sun Microsystems, Inc. All rights reserved.
* SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*
* @author Martin Buchholz
* @version 1.9, 06/03/22
*/
package java.lang;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* This class is used to create operating system processes.
*
* <p>Each <code>ProcessBuilder</code> instance manages a collection
* of process attributes. The {@link #start()} method creates a new
* {@link Process} instance with those attributes. The {@link
* #start()} method can be invoked repeatedly from the same instance
* to create new subprocesses with identical or related attributes.
*
* <p>Each process builder manages these process attributes:
*
* <ul>
*
* <li>a <i>command</i>, a list of strings which signifies the
* external program file to be invoked and its arguments, if any.
* Which string lists represent a valid operating system command is
* system-dependent. For example, it is common for each conceptual
* argument to be an element in this list, but there are operating
* systems where programs are expected to tokenize command line
* strings themselves - on such a system a Java implementation might
* require commands to contain exactly two elements.
*
* <li>an <i>environment</i>, which is a system-dependent mapping from
* <i>variables</i> to <i>values</i>. The initial value is a copy of
* the environment of the current process (see {@link System#getenv()}).
*
* <li>a <i>working directory</i>. The default value is the current
* working directory of the current process, usually the directory
* named by the system property <code>user.dir</code>.
*
* <li>a <i>redirectErrorStream</i> property. Initially, this property
* is <code>false</code>, meaning that the standard output and error
* output of a subprocess are sent to two separate streams, which can
* be accessed using the {@link Process#getInputStream()} and {@link
* Process#getErrorStream()} methods. If the value is set to
* <code>true</code>, the standard error is merged with the standard
* output. This makes it easier to correlate error messages with the
* corresponding output. In this case, the merged data can be read
* from the stream returned by {@link Process#getInputStream()}, while
* reading from the stream returned by {@link
* Process#getErrorStream()} will get an immediate end of file.
*
* </ul>
*
* <p>Modifying a process builder's attributes will affect processes
* subsequently started by that object's {@link #start()} method, but
* will never affect previously started processes or the Java process
* itself.
*
* <p>Most error checking is performed by the {@link #start()} method.
* It is possible to modify the state of an object so that {@link
* #start()} will fail. For example, setting the command attribute to
* an empty list will not throw an exception unless {@link #start()}
* is invoked.
*
* <p><strong>Note that this class is not synchronized.</strong>
* If multiple threads access a <code>ProcessBuilder</code> instance
* concurrently, and at least one of the threads modifies one of the
* attributes structurally, it <i>must</i> be synchronized externally.
*
* <p>Starting a new process which uses the default working directory
* and environment is easy:
*
* <blockquote><pre>
* Process p = new ProcessBuilder("myCommand", "myArg").start();
* </pre></blockquote>
*
* <p>Here is an example that starts a process with a modified working
* directory and environment:
*
* <blockquote><pre>
* ProcessBuilder pb = new ProcessBuilder("myCommand", "myArg1", "myArg2");
* Map<String, String> env = pb.environment();
* env.put("VAR1", "myValue");
* env.remove("OTHERVAR");
* env.put("VAR2", env.get("VAR1") + "suffix");
* pb.directory(new File("myDir"));
* Process p = pb.start();
* </pre></blockquote>
*
* <p>To start a process with an explicit set of environment
* variables, first call {@link java.util.Map#clear() Map.clear()}
* before adding environment variables.
*
* @since 1.5
*/
public final class ProcessBuilder {
private List<String> command;
private File directory;
private Map<String,String> environment;
private boolean redirectErrorStream;
/**
* Constructs a process builder with the specified operating
* system program and arguments. This constructor does <i>not</i>
* make a copy of the <code>command</code> list. Subsequent
* updates to the list will be reflected in the state of the
* process builder. It is not checked whether
* <code>command</code> corresponds to a valid operating system
* command.</p>
*
* @param command The list containing the program and its arguments
*
* @throws NullPointerException
* If the argument is <code>null</code>
*/
public ProcessBuilder(List<String> command) {
if (command == null)
throw new NullPointerException();
this.command = command;
}
/**
* Constructs a process builder with the specified operating
* system program and arguments. This is a convenience
* constructor that sets the process builder's command to a string
* list containing the same strings as the <code>command</code>
* array, in the same order. It is not checked whether
* <code>command</code> corresponds to a valid operating system
* command.</p>
*
* @param command A string array containing the program and its arguments
*/
public ProcessBuilder(String... command) {
this.command = new ArrayList<String>(command.length);
for (String arg : command)
this.command.add(arg);
}
/**
* Sets this process builder's operating system program and
* arguments. This method does <i>not</i> make a copy of the
* <code>command</code> list. Subsequent updates to the list will
* be reflected in the state of the process builder. It is not
* checked whether <code>command</code> corresponds to a valid
* operating system command.</p>
*
* @param command The list containing the program and its arguments
* @return This process builder
*
* @throws NullPointerException
* If the argument is <code>null</code>
*/
public ProcessBuilder command(List<String> command) {
if (command == null)
throw new NullPointerException();
this.command = command;
return this;
}
/**
* Sets this process builder's operating system program and
* arguments. This is a convenience method that sets the command
* to a string list containing the same strings as the
* <code>command</code> array, in the same order. It is not
* checked whether <code>command</code> corresponds to a valid
* operating system command.</p>
*
* @param command A string array containing the program and its arguments
* @return This process builder
*/
public ProcessBuilder command(String... command) {
this.command = new ArrayList<String>(command.length);
for (String arg : command)
this.command.add(arg);
return this;
}
/**
* Returns this process builder's operating system program and
* arguments. The returned list is <i>not</i> a copy. Subsequent
* updates to the list will be reflected in the state of this
* process builder.</p>
*
* @return This process builder's program and its arguments
*/
public List<String> command() {
return command;
}
/**
* Returns a string map view of this process builder's environment.
*
* Whenever a process builder is created, the environment is
* initialized to a copy of the current process environment (see
* {@link System#getenv()}). Subprocesses subsequently started by
* this object's {@link #start()} method will use this map as
* their environment.
*
* <p>The returned object may be modified using ordinary {@link
* java.util.Map Map} operations. These modifications will be
* visible to subprocesses started via the {@link #start()}
* method. Two <code>ProcessBuilder</code> instances always
* contain independent process environments, so changes to the
* returned map will never be reflected in any other
* <code>ProcessBuilder</code> instance or the values returned by
* {@link System#getenv System.getenv}.
*
* <p>If the system does not support environment variables, an
* empty map is returned.
*
* <p>The returned map does not permit null keys or values.
* Attempting to insert or query the presence of a null key or
* value will throw a {@link NullPointerException}.
* Attempting to query the presence of a key or value which is not
* of type {@link String} will throw a {@link ClassCastException}.
*
* <p>The behavior of the returned map is system-dependent. A
* system may not allow modifications to environment variables or
* may forbid certain variable names or values. For this reason,
* attempts to modify the map may fail with
* {@link UnsupportedOperationException} or
* {@link IllegalArgumentException}
* if the modification is not permitted by the operating system.
*
* <p>Since the external format of environment variable names and
* values is system-dependent, there may not be a one-to-one
* mapping between them and Java's Unicode strings. Nevertheless,
* the map is implemented in such a way that environment variables
* which are not modified by Java code will have an unmodified
* native representation in the subprocess.
*
* <p>The returned map and its collection views may not obey the
* general contract of the {@link Object#equals} and
* {@link Object#hashCode} methods.
*
* <p>The returned map is typically case-sensitive on all platforms.
*
* <p>If a security manager exists, its
* {@link SecurityManager#checkPermission checkPermission}
* method is called with a
* <code>{@link RuntimePermission}("getenv.*")</code>
* permission. This may result in a {@link SecurityException} being
* thrown.
*
* <p>When passing information to a Java subprocess,
* <a href=System.html#EnvironmentVSSystemProperties>system properties</a>
* are generally preferred over environment variables.</p>
*
* @return This process builder's environment
*
* @throws SecurityException
* If a security manager exists and its
* {@link SecurityManager#checkPermission checkPermission}
* method doesn't allow access to the process environment
*
* @see Runtime#exec(String[],String[],java.io.File)
* @see System#getenv()
*/
public Map<String,String> environment() {
SecurityManager security = System.getSecurityManager();
if (security != null)
security.checkPermission(new RuntimePermission("getenv.*"));
if (environment == null)
environment = ProcessEnvironment.environment();
assert environment != null;
return environment;
}
// Only for use by Runtime.exec(...envp...)
ProcessBuilder environment(String[] envp) {
assert environment == null;
if (envp != null) {
environment = ProcessEnvironment.emptyEnvironment(envp.length);
assert environment != null;
for (String envstring : envp) {
// Before 1.5, we blindly passed invalid envstrings
// to the child process.
// We would like to throw an exception, but do not,
// for compatibility with old broken code.
// Silently discard any trailing junk.
if (envstring.indexOf((int) '\u0000') != -1)
envstring = envstring.replaceFirst("\u0000.*", "");
int eqlsign =
envstring.indexOf('=', ProcessEnvironment.MIN_NAME_LENGTH);
// Silently ignore envstrings lacking the required `='.
if (eqlsign != -1)
environment.put(envstring.substring(0,eqlsign),
envstring.substring(eqlsign+1));
}
}
return this;
}
/**
* Returns this process builder's working directory.
*
* Subprocesses subsequently started by this object's {@link
* #start()} method will use this as their working directory.
* The returned value may be <code>null</code> -- this means to use
* the working directory of the current Java process, usually the
* directory named by the system property <code>user.dir</code>,
* as the working directory of the child process.</p>
*
* @return This process builder's working directory
*/
public File directory() {
return directory;
}
/**
* Sets this process builder's working directory.
*
* Subprocesses subsequently started by this object's {@link
* #start()} method will use this as their working directory.
* The argument may be <code>null</code> -- this means to use the
* working directory of the current Java process, usually the
* directory named by the system property <code>user.dir</code>,
* as the working directory of the child process.</p>
*
* @param directory The new working directory
* @return This process builder
*/
public ProcessBuilder directory(File directory) {
this.directory = directory;
return this;
}
/**
* Tells whether this process builder merges standard error and
* standard output.
*
* <p>If this property is <code>true</code>, then any error output
* generated by subprocesses subsequently started by this object's
* {@link #start()} method will be merged with the standard
* output, so that both can be read using the
* {@link Process#getInputStream()} method. This makes it easier
* to correlate error messages with the corresponding output.
* The initial value is <code>false</code>.</p>
*
* @return This process builder's <code>redirectErrorStream</code> property
*/
public boolean redirectErrorStream() {
return redirectErrorStream;
}
/**
* Sets this process builder's <code>redirectErrorStream</code> property.
*
* <p>If this property is <code>true</code>, then any error output
* generated by subprocesses subsequently started by this object's
* {@link #start()} method will be merged with the standard
* output, so that both can be read using the
* {@link Process#getInputStream()} method. This makes it easier
* to correlate error messages with the corresponding output.
* The initial value is <code>false</code>.</p>
*
* @param redirectErrorStream The new property value
* @return This process builder
*/
public ProcessBuilder redirectErrorStream(boolean redirectErrorStream) {
this.redirectErrorStream = redirectErrorStream;
return this;
}
/**
* Starts a new process using the attributes of this process builder.
*
* <p>The new process will
* invoke the command and arguments given by {@link #command()},
* in a working directory as given by {@link #directory()},
* with a process environment as given by {@link #environment()}.
*
* <p>This method checks that the command is a valid operating
* system command. Which commands are valid is system-dependent,
* but at the very least the command must be a non-empty list of
* non-null strings.
*
* <p>If there is a security manager, its
* {@link SecurityManager#checkExec checkExec}
* method is called with the first component of this object's
* <code>command</code> array as its argument. This may result in
* a {@link SecurityException} being thrown.
*
* <p>Starting an operating system process is highly system-dependent.
* Among the many things that can go wrong are:
* <ul>
* <li>The operating system program file was not found.
* <li>Access to the program file was denied.
* <li>The working directory does not exist.
* </ul>
*
* <p>In such cases an exception will be thrown. The exact nature
* of the exception is system-dependent, but it will always be a
* subclass of {@link IOException}.
*
* <p>Subsequent modifications to this process builder will not
* affect the returned {@link Process}.</p>
*
* @return A new {@link Process} object for managing the subprocess
*
* @throws NullPointerException
* If an element of the command list is null
*
* @throws IndexOutOfBoundsException
* If the command is an empty list (has size <code>0</code>)
*
* @throws SecurityException
* If a security manager exists and its
* {@link SecurityManager#checkExec checkExec}
* method doesn't allow creation of the subprocess
*
* @throws IOException
* If an I/O error occurs
*
* @see Runtime#exec(String[], String[], java.io.File)
* @see SecurityManager#checkExec(String)
*/
public Process start() throws IOException {
// Must convert to array first -- a malicious user-supplied
// list might try to circumvent the security check.
String[] cmdarray = command.toArray(new String[command.size()]);
for (String arg : cmdarray)
if (arg == null)
throw new NullPointerException();
// Throws IndexOutOfBoundsException if command is empty
String prog = cmdarray[0];
SecurityManager security = System.getSecurityManager();
if (security != null)
security.checkExec(prog);
String dir = directory == null ? null : directory.toString();
try {
return ProcessImpl.start(cmdarray,
environment,
dir,
redirectErrorStream);
} catch (IOException e) {
// It's much easier for us to create a high-quality error
// message than the low-level C code which found the problem.
throw new IOException(
"Cannot run program \"" + prog + "\""
+ (dir == null ? "" : " (in directory \"" + dir + "\")")
+ ": " + e.getMessage(),
e);
}
}
}