org.netbeans.api.extexecution.base.ProcessBuilder Maven / Gradle / Ivy
/*
* 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.netbeans.api.extexecution.base;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.Callable;
import org.netbeans.api.annotations.common.NonNull;
import org.netbeans.api.annotations.common.NullAllowed;
import org.netbeans.modules.extexecution.base.ExternalProcessBuilder;
import org.netbeans.modules.extexecution.base.ProcessBuilderAccessor;
import org.netbeans.modules.extexecution.base.ProcessParametersAccessor;
import org.netbeans.spi.extexecution.base.EnvironmentFactory;
import org.netbeans.spi.extexecution.base.EnvironmentImplementation;
import org.netbeans.spi.extexecution.base.ProcessBuilderImplementation;
import org.netbeans.spi.extexecution.base.ProcessParameters;
import org.openide.util.Lookup;
import org.openide.util.NbBundle;
import org.openide.util.Parameters;
/**
* Abstraction of process builders. You can freely configure the parameters
* and then create a process by calling the {@link #call()} method. You can
* also (re)configure the builder and spawn a different process.
*
* Note the API does not prescribe the actual meaning of {@link Process}.
* It may be local process, remote process or some other implementation.
*
* You can use the default implementation returned by {@link #getLocal()}
* for creating the local machine OS processes.
*
* Thread safety of this class depends on thread safety of
* the implementation class.
*
* If the {@link ProcessBuilderImplementation} is used and it is thread
* safe (if possible the implementation should be even stateless) this class
* is thread safe as well.
*
* If the {@link ProcessBuilderImplementation} is used and it is (including
* {@link EnvironmentImplementation}) thread safe and does not have any mutable
* configuration accessible via {@link ProcessBuilderImplementation#getLookup()}
* it is thread safe as well. Otherwise it is not thread safe.
*
* The synchronization mechanism used in this object is the {@link ProcessBuilderImplementation}
* object monitor.
*
* @author Petr Hejl
*/
// TODO proxy autoconfiguration optional via lookup
public final class ProcessBuilder implements Callable, Lookup.Provider {
private final ProcessBuilderImplementation implementation;
private final Object lock;
private final String description;
/**GuardedBy("lock")*/
private String executable;
/**GuardedBy("lock")*/
private String workingDirectory;
/**GuardedBy("lock")*/
private final List arguments = new ArrayList();
/**GuardedBy("lock")*/
private boolean redirectErrorStream;
static {
ProcessBuilderAccessor.setDefault(new ProcessBuilderAccessor() {
@Override
public ProcessBuilder createProcessBuilder(ProcessBuilderImplementation impl, String description) {
return new ProcessBuilder(impl, description);
}
});
}
private ProcessBuilder(ProcessBuilderImplementation implementation2, String description) {
assert implementation2 != null;
this.implementation = implementation2;
this.description = description;
this.lock = implementation2;
}
/**
* Returns the {@link ProcessBuilder} creating the OS process on local
* machine. Returned implementation is thread safe
.
* The returned builder also attempts to properly configure HTTP proxy
* for the process.
*
* @return the {@link ProcessBuilder} creating the OS process on local
* machine
*/
public static ProcessBuilder getLocal() {
return new ProcessBuilder(new LocalProcessBuilder(),
NbBundle.getMessage(ProcessBuilder.class, "LocalProcessBuilder"));
}
/**
* Returns the human readable description of this builder.
*
* @return the human readable description of this builder
*/
@NonNull
public String getDescription() {
return description;
}
/**
* Sets the executable to run. There is no default value. The {@link #call()}
* methods throws {@link IllegalStateException} when there is no executable
* configured.
*
* @param executable the executable to run
*/
public void setExecutable(@NonNull String executable) {
Parameters.notNull("executable", executable);
synchronized (lock) {
this.executable = executable;
}
}
/**
* Sets the working directory for the process created by subsequent call
* of {@link #call()}. The default value is implementation specific.
*
* @param workingDirectory the working directory of the process
*/
public void setWorkingDirectory(@NullAllowed String workingDirectory) {
synchronized (lock) {
this.workingDirectory = workingDirectory;
}
}
/**
* Sets the arguments passed to the process created by subsequent call
* of {@link #call()}. By default there are no arguments.
*
* @param arguments the arguments passed to the process
*/
public void setArguments(@NonNull List arguments) {
Parameters.notNull("arguments", arguments);
synchronized (lock) {
this.arguments.clear();
this.arguments.addAll(arguments);
}
}
/**
* Configures the error stream redirection. If true
the error
* stream of process created by subsequent call of {@link #call()} method
* will be redirected to standard output stream.
*
* @param redirectErrorStream the error stream redirection
*/
public void setRedirectErrorStream(boolean redirectErrorStream) {
synchronized (lock) {
this.redirectErrorStream = redirectErrorStream;
}
}
/**
* Returns the object for environment variables manipulation.
*
* @return the object for environment variables manipulation
*/
@NonNull
public Environment getEnvironment() {
return implementation.getEnvironment();
}
/**
* Returns the associated {@link Lookup}. Extension point provided by
* {@link ProcessBuilderImplementation}.
*
* @return the associated {@link Lookup}.
* @see ProcessBuilderImplementation#getLookup()
*/
@Override
public Lookup getLookup() {
if (implementation != null) {
return implementation.getLookup();
}
return Lookup.EMPTY;
}
/**
* Creates the new {@link Process} based on the properties configured
* in this builder.
*
* Actual behavior depends on the builder implementation, but it should
* respect all the properties configured on this builder.
*
* @see ProcessBuilderImplementation
* @return the new {@link Process} based on the properties configured
* in this builder
* @throws IOException if the process could not be created
* @throws IllegalStateException if there is no executable configured
* by {@link #setExecutable(java.lang.String)}
*/
@NonNull
@Override
public Process call() throws IOException {
String currentExecutable;
String currentWorkingDirectory;
List currentArguments = new ArrayList();
Map currentVariables = new HashMap();
boolean currentRedirectErrorStream;
synchronized (lock) {
currentExecutable = executable;
currentWorkingDirectory = workingDirectory;
currentArguments.addAll(arguments);
currentRedirectErrorStream = redirectErrorStream;
currentVariables.putAll(getEnvironment().values());
}
if (currentExecutable == null) {
throw new IllegalStateException("The executable has not been configured");
}
ProcessParameters params = ProcessParametersAccessor.getDefault().createProcessParameters(
currentExecutable, currentWorkingDirectory, currentArguments,
currentRedirectErrorStream, currentVariables);
return implementation.createProcess(params);
}
// /**
// * Marks an object from which it is possible to get a {@link ProcessBuilder}.
// */
// public static interface Provider {
//
// /**
// * Returns the {@link ProcessBuilder} for the object.
// *
// * @return the {@link ProcessBuilder} for the object
// * @throws IOException if there was a problem with the provision
// */
// ProcessBuilder getProcessBuilder() throws IOException;
//
// }
private static class LocalProcessBuilder implements ProcessBuilderImplementation {
private final Environment environment = EnvironmentFactory.createEnvironment(
new LocalEnvironment(this, System.getenv()));
@Override
public Environment getEnvironment() {
return environment;
}
@Override
public Lookup getLookup() {
return Lookup.EMPTY;
}
@Override
public Process createProcess(ProcessParameters parameters) throws IOException {
ExternalProcessBuilder builder = new ExternalProcessBuilder(parameters.getExecutable());
String workingDir = parameters.getWorkingDirectory();
if (workingDir != null) {
builder = builder.workingDirectory(new File(workingDir));
}
for (String argument : parameters.getArguments()) {
builder = builder.addArgument(argument);
}
builder = builder.redirectErrorStream(parameters.isRedirectErrorStream());
for (Map.Entry entry : parameters.getEnvironmentVariables().entrySet()) {
builder = builder.addEnvironmentVariable(entry.getKey(), entry.getValue());
}
return builder.call();
}
}
private static class LocalEnvironment implements EnvironmentImplementation {
private final LocalProcessBuilder builder;
private final Map systemEnvironment;
private final String pathName;
public LocalEnvironment(LocalProcessBuilder builder, Map systemEnvironment) {
this.builder = builder;
this.systemEnvironment = new HashMap(systemEnvironment);
this.pathName = ExternalProcessBuilder.getPathName(systemEnvironment);
}
@Override
public String getVariable(String name) {
synchronized (builder) {
if ("PATH".equals(name.toUpperCase(Locale.ENGLISH))) { // NOI18N
return systemEnvironment.get(pathName);
} else {
return systemEnvironment.get(name);
}
}
}
@Override
public void appendPath(String name, String value) {
putPath(name, value, false);
}
@Override
public void prependPath(String name, String value) {
putPath(name, value, true);
}
@Override
public void setVariable(String name, String value) {
synchronized (builder) {
if ("PATH".equals(name.toUpperCase(Locale.ENGLISH))) { // NOI18N
systemEnvironment.put(pathName, value);
} else {
systemEnvironment.put(name, value);
}
}
}
@Override
public void removeVariable(String name) {
synchronized (builder) {
if ("PATH".equals(name.toUpperCase(Locale.ENGLISH))) { // NOI18N
systemEnvironment.remove(pathName);
} else {
systemEnvironment.remove(name);
}
}
}
@Override
public Map values() {
synchronized (builder) {
return new HashMap(systemEnvironment);
}
}
private void putPath(String name, String value, boolean prepend) {
synchronized (builder) {
if ("PATH".equals(name.toUpperCase(Locale.ENGLISH))) { // NOI18N
ExternalProcessBuilder.putPath(new File(value), pathName,
prepend, systemEnvironment);
} else {
ExternalProcessBuilder.putPath(new File(value), name,
prepend, systemEnvironment);
}
}
}
}
}