/* * @(#)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. * *

Each ProcessBuilder 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. * *

Each process builder manages these process attributes: * *

* *

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. * *

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. * *

Note that this class is not synchronized. * If multiple threads access a ProcessBuilder instance * concurrently, and at least one of the threads modifies one of the * attributes structurally, it must be synchronized externally. * *

Starting a new process which uses the default working directory * and environment is easy: * *

 * Process p = new ProcessBuilder("myCommand", "myArg").start();
 * 
* *

Here is an example that starts a process with a modified working * directory and environment: * *

 * 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();
 * 
* *

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 command; private File directory; private Map environment; private boolean redirectErrorStream; /** * Constructs a process builder with the specified operating * system program and arguments. This constructor does not * make a copy of the command list. Subsequent * updates to the list will be reflected in the state of the * process builder. It is not checked whether * command corresponds to a valid operating system * command.

* * @param command The list containing the program and its arguments * * @throws NullPointerException * If the argument is null */ public ProcessBuilder(List 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 command * array, in the same order. It is not checked whether * command corresponds to a valid operating system * command.

* * @param command A string array containing the program and its arguments */ public ProcessBuilder(String... command) { this.command = new ArrayList(command.length); for (String arg : command) this.command.add(arg); } /** * Sets this process builder's operating system program and * arguments. This method does not make a copy of the * command list. Subsequent updates to the list will * be reflected in the state of the process builder. It is not * checked whether command corresponds to a valid * operating system command.

* * @param command The list containing the program and its arguments * @return This process builder * * @throws NullPointerException * If the argument is null */ public ProcessBuilder command(List 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 * command array, in the same order. It is not * checked whether command corresponds to a valid * operating system command.

* * @param command A string array containing the program and its arguments * @return This process builder */ public ProcessBuilder command(String... command) { this.command = new ArrayList(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 not a copy. Subsequent * updates to the list will be reflected in the state of this * process builder.

* * @return This process builder's program and its arguments */ public List 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. * *

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 ProcessBuilder instances always * contain independent process environments, so changes to the * returned map will never be reflected in any other * ProcessBuilder instance or the values returned by * {@link System#getenv System.getenv}. * *

If the system does not support environment variables, an * empty map is returned. * *

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}. * *

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. * *

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. * *

The returned map and its collection views may not obey the * general contract of the {@link Object#equals} and * {@link Object#hashCode} methods. * *

The returned map is typically case-sensitive on all platforms. * *

If a security manager exists, its * {@link SecurityManager#checkPermission checkPermission} * method is called with a * {@link RuntimePermission}("getenv.*") * permission. This may result in a {@link SecurityException} being * thrown. * *

When passing information to a Java subprocess, * system properties * are generally preferred over environment variables.

* * @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 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 null -- this means to use * the working directory of the current Java process, usually the * directory named by the system property user.dir, * as the working directory of the child process.

* * @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 null -- this means to use the * working directory of the current Java process, usually the * directory named by the system property user.dir, * as the working directory of the child process.

* * @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. * *

If this property is true, 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 false.

* * @return This process builder's redirectErrorStream property */ public boolean redirectErrorStream() { return redirectErrorStream; } /** * Sets this process builder's redirectErrorStream property. * *

If this property is true, 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 false.

* * @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. * *

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()}. * *

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. * *

If there is a security manager, its * {@link SecurityManager#checkExec checkExec} * method is called with the first component of this object's * command array as its argument. This may result in * a {@link SecurityException} being thrown. * *

Starting an operating system process is highly system-dependent. * Among the many things that can go wrong are: *

    *
  • The operating system program file was not found. *
  • Access to the program file was denied. *
  • The working directory does not exist. *
* *

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}. * *

Subsequent modifications to this process builder will not * affect the returned {@link Process}.

* * @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 0) * * @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); } }