- /*
- * @(#)ProcessBuilder.java 1.6 04/02/07
- *
- * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
- * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
- *
- * @author Martin Buchholz
- * @version 1.6, 04/02/07
- */
-
- 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("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();
-
- return ProcessImpl.start(cmdarray,
- environment,
- dir,
- redirectErrorStream);
- }
- }