diff --git a/extexecution/apichanges.xml b/extexecution/apichanges.xml --- a/extexecution/apichanges.xml +++ b/extexecution/apichanges.xml @@ -104,6 +104,7 @@ External Execution API External Execution Input API External Execution Input Printing API + External Execution Process API External Execution Startup Arguments API External Execution SPI External Execution Process Destroy SPI @@ -115,7 +116,50 @@ - + + Enhance API and SPI to make it usable as a proxy to native execution + + + + + +

+ There is new SPI for ProcessBuilder named + ProcessBuilderImplementation2 which now implements + Lookup.Provider to be extensible and provides + a separate Environment object represented by + the EnvironmentImplementation in the SPI. + The ProcessBuilder itself allows access to those + new features via getLookup() and + getEnvironment() methods and it now also + implements Lookup.Provider. The utility class + ProcessParameters wraps up the parameters passed + to the SPI for process creation. Instances of + Environment API are created via + EnvironmentFactory support class. +

+

+ There are also new classes extending a Process + functionality named ProcessCharset, + ProcessId and ProcessSignal allowing + client to get the charset, to get ID and to send signal to + a process. This might not be always supported and the support + may be pre-checked. +

+
+ + + + + + + + + + + + +
Advice to throw UserQuestionException diff --git a/extexecution/arch.xml b/extexecution/arch.xml --- a/extexecution/arch.xml +++ b/extexecution/arch.xml @@ -67,6 +67,13 @@ org.openide.windows.OutputWriter. API provides common implementations too.

+ The support API/SPI + + allows enhancement of process with additional data such as ID, character set + and ability to send signals. Client can later easily check the feature is + supported and perform the query or action. +

+

The SPI allows different implementations of process builder. @@ -170,8 +177,17 @@

The creation of the external process is supported by ExternalProcessBuilder + and ProcessBuilder to make things easier.

+

+ The process returned by default or custom builders may support additional + features. To do so the process must be a Lookup.Provider and put + the implementation of the feature to lookup. The possible extensions are + located in org.netbeans.api.extexecution.process + package. The client then use static methods on respective classes in order + to use the extended functionality. +

@@ -232,8 +248,8 @@

In order to do so it will implement - - ProcessBuilderImplementation and pass + + ProcessBuilderImplementation2 and pass ProcessBuilder to its clients. The API instances are created with help of @@ -510,9 +526,8 @@ -->

- No known platform dependencies. In future there could be need for native code - in order to terminate the whole process tree created by execution of external - process. + No known platform dependencies. The platform dependent way of process + termination is in a separate implementation module.

diff --git a/extexecution/nbproject/project.xml b/extexecution/nbproject/project.xml --- a/extexecution/nbproject/project.xml +++ b/extexecution/nbproject/project.xml @@ -88,6 +88,7 @@ org.netbeans.api.extexecution org.netbeans.api.extexecution.print + org.netbeans.api.extexecution.process org.netbeans.api.extexecution.input org.netbeans.api.extexecution.startup org.netbeans.spi.extexecution diff --git a/extexecution/src/org/netbeans/api/extexecution/Environment.java b/extexecution/src/org/netbeans/api/extexecution/Environment.java new file mode 100644 --- /dev/null +++ b/extexecution/src/org/netbeans/api/extexecution/Environment.java @@ -0,0 +1,156 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2012 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2012 Sun Microsystems, Inc. + */ +package org.netbeans.api.extexecution; + +import java.util.Map; +import org.netbeans.api.annotations.common.CheckForNull; +import org.netbeans.api.annotations.common.NonNull; +import org.netbeans.modules.extexecution.EnvironmentAccessor; +import org.netbeans.spi.extexecution.EnvironmentImplementation; +import org.openide.util.Parameters; + +/** + * The class that provides an access to environment variables. + * + * @see ProcessBuilder#getEnvironment() + * @see EnvironmentImplementation + * @author Petr Hejl + * @since 1.37 + */ +public final class Environment { + + private final EnvironmentImplementation implementation; + + static { + EnvironmentAccessor.setDefault(new EnvironmentAccessor() { + + @Override + public Environment createEnvironment(EnvironmentImplementation impl) { + return new Environment(impl); + } + }); + } + + private Environment(EnvironmentImplementation implementation) { + this.implementation = implementation; + } + + /** + * Returns the value of the variable or null. + * + * @param name the name of the variable + * @return the value of the variable or null + */ + @CheckForNull + public String getVariable(@NonNull String name) { + Parameters.notNull("name", name); + + return implementation.getVariable(name); + } + + /** + * Appends a path to a path-like variable. The proper path separator is used + * to separate the new value. + * + * @param name the name of the variable such as for example + * PATH or LD_LIBRARY_PATH + * @param value the value (path to append) + */ + public void appendPath(@NonNull String name, @NonNull String value) { + Parameters.notNull("name", name); + Parameters.notNull("value", value); + + implementation.appendPath(name, value); + } + + /** + * Prepends a path to a path-like variable. The proper path separator is used + * to separate the new value. + * + * @param name the name of the variable such as for example + * PATH or LD_LIBRARY_PATH + * @param value the value (path to prepend) + */ + public void prependPath(@NonNull String name, @NonNull String value) { + Parameters.notNull("name", name); + Parameters.notNull("value", value); + + implementation.prependPath(name, value); + } + + /** + * Sets a value for a variable with the given name. + * + * @param name the name of the variable + * @param value the value + */ + public void setVariable(@NonNull String name, @NonNull String value) { + Parameters.notNull("name", name); + Parameters.notNull("value", value); + + implementation.setVariable(name, value); + } + + /** + * Removes a variable with the given name. The subsequent call to + * {@link #getVariable(java.lang.String)} with the same argument will return + * null. + * + * @param name the name of the variable + */ + public void removeVariable(@NonNull String name) { + Parameters.notNull("name", name); + + implementation.removeVariable(name); + } + + /** + * Returns all variable names and associated values as a {@link Map}. + * Changes to the map are not propagated back to the {@link Environment}. + * + * @return all variable names and associated values + */ + @NonNull + public Map values() { + return implementation.values(); + } +} diff --git a/extexecution/src/org/netbeans/api/extexecution/ExecutionService.java b/extexecution/src/org/netbeans/api/extexecution/ExecutionService.java --- a/extexecution/src/org/netbeans/api/extexecution/ExecutionService.java +++ b/extexecution/src/org/netbeans/api/extexecution/ExecutionService.java @@ -52,6 +52,7 @@ import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.Reader; +import java.io.Writer; import java.nio.charset.Charset; import java.security.AccessController; import java.security.PrivilegedAction; @@ -66,10 +67,12 @@ import java.util.concurrent.Future; import java.util.concurrent.FutureTask; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.AbstractAction; import org.netbeans.api.annotations.common.NonNull; +import org.netbeans.api.annotations.common.NullAllowed; import org.netbeans.api.progress.ProgressHandle; import org.netbeans.api.progress.ProgressHandleFactory; import org.netbeans.modules.extexecution.ProcessInputStream; @@ -79,7 +82,9 @@ import org.netbeans.api.extexecution.input.InputProcessors; import org.netbeans.api.extexecution.input.InputReaderTask; import org.netbeans.api.extexecution.input.InputReaders; +import org.netbeans.api.extexecution.input.LineProcessor; import org.netbeans.api.extexecution.input.LineProcessors; +import org.netbeans.api.extexecution.process.ProcessCharset; import org.openide.util.Cancellable; import org.openide.util.Mutex; import org.openide.util.NbBundle; @@ -91,20 +96,20 @@ * Execution service provides the facility to execute the process while * displaying the output and handling the input. *

- * It will execute the program with an associated I/O window, with stop and - * restart buttons. It will also obey various descriptor properties such as - * whether or not to show a progress bar. + * It will execute the program with an associated I/O window, possibly with + * stop and restart buttons. It will also obey various descriptor properties + * such as whether or not to show a progress bar. *

* All processes launched by this class are terminated on VM exit (if * these are not finished or terminated earlier). *

- * Note that once service is run for the first time, subsequents runs can be + * Note that once service is run for the first time, subsequent runs can be * invoked by the user (rerun button) if it is allowed to do so * ({@link ExecutionDescriptor#isControllable()}). * *

*

- * Sample usage - executing ls command: + * Sample usage (ls command): *

  *     ExecutionDescriptor descriptor = new ExecutionDescriptor()
  *          .frontWindow(true).controllable(true);
@@ -114,6 +119,14 @@
  *     ExecutionService service = ExecutionService.newService(processBuilder, descriptor, "ls command");
  *     Future<Integer> task = service.run();
  * 
+ *

+ * Even simpler usage but without displaying output (ls command): + *

+ *     ExternalProcessBuilder processBuilder = new ExternalProcessBuilder("ls");
+ *
+ *     ExecutionService service = ExecutionService.newService(processBuilder, null, null);
+ *     Future<Integer> task = service.run();
+ * 
*
* * @author Petr Hejl @@ -130,6 +143,21 @@ private static final ExecutorService EXECUTOR_SERVICE = new RequestProcessor(ExecutionService.class.getName(), Integer.MAX_VALUE); + private static final InputProcessor NULL_PROCESSOR = new InputProcessor() { + + @Override + public void processInput(char[] chars) throws IOException { + } + + @Override + public void reset() throws IOException { + } + + @Override + public void close() throws IOException { + } + }; + static { // rerun accessor RerunAction.Accessor.setDefault(new RerunAction.Accessor() { @@ -156,16 +184,25 @@ }); } - private final Callable processCreator; + private final Callable processCreator; private final ExecutionDescriptor descriptor; private final String originalDisplayName; - private ExecutionService(Callable processCreator, String displayName, ExecutionDescriptor descriptor) { + private final Reader processInput; + + private final boolean rerunAllowed; + + private final AtomicInteger runCount = new AtomicInteger(); + + private ExecutionService(Callable processCreator, String displayName, + ExecutionDescriptor descriptor, Reader processInput, boolean rerunAllowed) { this.processCreator = processCreator; this.originalDisplayName = displayName; this.descriptor = descriptor; + this.processInput = processInput; + this.rerunAllowed = rerunAllowed; } /** @@ -178,24 +215,130 @@ * @return new execution service */ @NonNull - public static ExecutionService newService(@NonNull Callable processCreator, + public static ExecutionService newService(@NonNull Callable processCreator, @NonNull ExecutionDescriptor descriptor, @NonNull String displayName) { - return new ExecutionService(processCreator, displayName, descriptor); + return new ExecutionService(processCreator, displayName, descriptor, null, true); + } + + /** + * Creates new execution service. Service will wrap up the processes + * created by processCreator and will manage them. The service + * may not be run more than once otherwise {@link IllegalStateException} + * is thrown on {@link #run()}. No UI will be displayed. + *

+ * Passed processInput will be implicitly closed when the execution (invoked by + * {@link #run()}) is finished. The client should not use the reader passed + * as argument anymore or the reader should be designed thread safe. + * + * @param processCreator callable returning the process to wrap up + * @param outputProcessor processor processing lines of standard output; + * may be null + * @param errorProcessor processor processing lines of standard error + * output; may be null + * @param processorInput reader providing input for standard input of + * the created process. Once passed it either should not be + * used anymore or it should be designed to be thread safe. + * May be null. + * @return new execution service + * @since 1.37 + */ + @NonNull + public static ExecutionService newService(@NonNull Callable processCreator, + @NullAllowed final LineProcessor outputProcessor, @NullAllowed final LineProcessor errorProcessor, + @NullAllowed final Reader processorInput) { + ExecutionDescriptor descriptor = new ExecutionDescriptor() + .inputOutput(InputOutput.NULL); + + if (outputProcessor != null) { + descriptor = descriptor.outProcessorFactory(new InputProcessorFactory() { + @Override + public InputProcessor newInputProcessor(InputProcessor defaultProcessor) { + return InputProcessors.bridge(outputProcessor); + } + }); + } + if (errorProcessor != null) { + descriptor = descriptor.errProcessorFactory(new InputProcessorFactory() { + @Override + public InputProcessor newInputProcessor(InputProcessor defaultProcessor) { + return InputProcessors.bridge(errorProcessor); + } + }); + } + + return new ExecutionService(processCreator, null, descriptor, processorInput, false); + } + + /** + * Creates new execution service. Service will wrap up the processes + * created by processCreator and will manage them. The service + * may not be run more than once otherwise {@link IllegalStateException} + * is thrown on {@link #run()}. No UI will be displayed. + *

+ * Passed inputWriter and errorWriter will be implicitly closed when the + * execution (invoked by {@link #run()}) is finished. The client either + * should not use the passed writers anymore or these should be designed + * to be thread safe. + * + * @param processCreator callable returning the process to wrap up + * @param outputWriter where to write standard output of the process. Once + * passed it either should not be used anymore or it should + * be designed to be thread safe. May be null. + * @param errorWriter where to write standard error output of the process. + * Once passed it either should not be used anymore or it should + * be designed to be thread safe. May be null. + * @return new execution service + * @since 1.37 + */ + public static ExecutionService newService(@NonNull Callable processCreator, + @NullAllowed final Writer outputWriter, @NullAllowed final Writer errorWriter) { + ExecutionDescriptor descriptor = new ExecutionDescriptor() + .inputOutput(InputOutput.NULL); + + if (outputWriter != null) { + descriptor = descriptor.outProcessorFactory(new InputProcessorFactory() { + @Override + public InputProcessor newInputProcessor(InputProcessor defaultProcessor) { + return InputProcessors.copying(outputWriter); + } + }); + } + if (errorWriter != null) { + descriptor = descriptor.errProcessorFactory(new InputProcessorFactory() { + @Override + public InputProcessor newInputProcessor(InputProcessor defaultProcessor) { + return InputProcessors.copying(errorWriter); + } + }); + } + + return new ExecutionService(processCreator, null, descriptor, null, false); } /** * Runs the process described by this service. The call does not block * and the task is represented by the returned value. Integer returned * as a result of the {@link Future} is exit code of the process. + * The ability to call this method multiple times depends on a way + * in which the service has been created. Only service created by + * {@link #newService(java.util.concurrent.Callable, org.netbeans.api.extexecution.ExecutionDescriptor, java.lang.String)} + * may be run more than once. *

* The output tabs are reused (if caller does not use the custom one, - * see {@link ExecutionDescriptor#getInputOutput()}) - the tab to reuse - * (if any) is selected by having the same name and same buttons - * (control and option). If there is no output tab to reuse new one - * is opened. + * see {@link ExecutionDescriptor#getInputOutput()} or if it is not being + * run without output tab, see {@link #newService(java.util.concurrent.Callable, org.netbeans.api.extexecution.input.LineProcessor, org.netbeans.api.extexecution.input.LineProcessor, java.io.Reader)}) + * - the tab to reuse (if any) is selected by having the same name + * and same buttons (control and option). If there is no output tab to + * reuse new one is opened. *

- * This method can be invoked multiple times returning the different and - * unrelated {@link Future}s. On each call Callable<Process> + * If the service has been created by {@link #newService(java.util.concurrent.Callable, org.netbeans.api.extexecution.input.LineProcessor, org.netbeans.api.extexecution.input.LineProcessor, java.io.Reader)} + * this method may be called only once. Subsequent calls will cause an + * {@link IllegalStateException} to be thrown. + * If the service has been created by {@link #newService(java.util.concurrent.Callable, org.netbeans.api.extexecution.ExecutionDescriptor, java.lang.String)} + * this method can be invoked multiple times returning the different and + * unrelated {@link Future}s. + *

+ * On each call Callable<Process> * passed to {@link #newService(java.util.concurrent.Callable, org.netbeans.api.extexecution.ExecutionDescriptor, java.lang.String)} * is invoked in order to create the process. If the process creation fails * (throwing an exception) returned Future will throw @@ -213,6 +356,10 @@ } private Future run(InputOutput required) { + if (!rerunAllowed && runCount.incrementAndGet() > 1) { + throw new IllegalStateException("Run invoked multimple times"); + } + final InputOutputManager.InputOutputData ioData = getInputOutput(required); final String displayName = ioData.getDisplayName(); @@ -220,9 +367,16 @@ final ProgressHandle handle = createProgressHandle(ioData.getInputOutput(), displayName, cancellable); final InputOutput io = ioData.getInputOutput(); - final OutputWriter out = io.getOut(); - final OutputWriter err = io.getErr(); - final Reader in = io.getIn(); + assert processInput == null || io == InputOutput.NULL; + + final Reader in; + if (processInput != null) { + in = processInput; + } else if (descriptor.isInputVisible() && io != InputOutput.NULL) { + in = io.getIn(); + } else { + in = null; + } final CountDownLatch finishedLatch = new CountDownLatch(1); @@ -267,23 +421,37 @@ outStream = new ProcessInputStream(process, process.getInputStream()); errStream = new ProcessInputStream(process, process.getErrorStream()); - executor = Executors.newFixedThreadPool(descriptor.isInputVisible() ? 3 : 2); + executor = Executors.newFixedThreadPool(in != null ? 3 : 2); Charset charset = descriptor.getCharset(); + Charset inputCharset = charset; + Charset outputCharset = charset; + Charset errorCharset = charset; if (charset == null) { - charset = Charset.defaultCharset(); + inputCharset = ProcessCharset.getInputCharset(process); + outputCharset = ProcessCharset.getOutputCharset(process); + errorCharset = ProcessCharset.getErrorCharset(process); + } + if (inputCharset == null) { + inputCharset = Charset.defaultCharset(); + } + if (outputCharset == null) { + outputCharset = Charset.defaultCharset(); + } + if (errorCharset == null) { + errorCharset = Charset.defaultCharset(); } tasks.add(InputReaderTask.newDrainingTask( - InputReaders.forStream(new BufferedInputStream(outStream), charset), - createOutProcessor(out))); + InputReaders.forStream(new BufferedInputStream(outStream), outputCharset), + createOutProcessor(io))); tasks.add(InputReaderTask.newDrainingTask( - InputReaders.forStream(new BufferedInputStream(errStream), charset), - createErrProcessor(err))); - if (descriptor.isInputVisible()) { + InputReaders.forStream(new BufferedInputStream(errStream), errorCharset), + createErrProcessor(io))); + if (in != null) { tasks.add(InputReaderTask.newTask( InputReaders.forReader(in), - createInProcessor(process.getOutputStream()))); + createInProcessor(process.getOutputStream(), inputCharset))); } for (InputReaderTask task : tasks) { executor.submit(task); @@ -568,15 +736,20 @@ Mutex.EVENT.readAccess(ui); } - private InputProcessor createOutProcessor(OutputWriter writer) { - LineConvertorFactory convertorFactory = descriptor.getOutConvertorFactory(); + private InputProcessor createOutProcessor(InputOutput io) { InputProcessor outProcessor = null; - if (descriptor.isOutLineBased()) { - outProcessor = InputProcessors.bridge(LineProcessors.printing(writer, - convertorFactory != null ? convertorFactory.newLineConvertor() : null, true)); + if (io != InputOutput.NULL) { + LineConvertorFactory convertorFactory = descriptor.getOutConvertorFactory(); + OutputWriter writer = io.getOut(); + if (descriptor.isOutLineBased()) { + outProcessor = InputProcessors.bridge(LineProcessors.printing(writer, + convertorFactory != null ? convertorFactory.newLineConvertor() : null, true)); + } else { + outProcessor = InputProcessors.printing(writer, + convertorFactory != null ? convertorFactory.newLineConvertor() : null, true); + } } else { - outProcessor = InputProcessors.printing(writer, - convertorFactory != null ? convertorFactory.newLineConvertor() : null, true); + outProcessor = NULL_PROCESSOR; } InputProcessorFactory descriptorOutFactory = descriptor.getOutProcessorFactory(); @@ -587,15 +760,20 @@ return outProcessor; } - private InputProcessor createErrProcessor(OutputWriter writer) { - LineConvertorFactory convertorFactory = descriptor.getErrConvertorFactory(); + private InputProcessor createErrProcessor(InputOutput io) { InputProcessor errProcessor = null; - if (descriptor.isErrLineBased()) { - errProcessor = InputProcessors.bridge(LineProcessors.printing(writer, - convertorFactory != null ? convertorFactory.newLineConvertor() : null, false)); + if (io != InputOutput.NULL) { + LineConvertorFactory convertorFactory = descriptor.getErrConvertorFactory(); + OutputWriter writer = io.getErr(); + if (descriptor.isErrLineBased()) { + errProcessor = InputProcessors.bridge(LineProcessors.printing(writer, + convertorFactory != null ? convertorFactory.newLineConvertor() : null, false)); + } else { + errProcessor = InputProcessors.printing(writer, + convertorFactory != null ? convertorFactory.newLineConvertor() : null, false); + } } else { - errProcessor = InputProcessors.printing(writer, - convertorFactory != null ? convertorFactory.newLineConvertor() : null, false); + errProcessor = NULL_PROCESSOR; } InputProcessorFactory descriptorErrFactory = descriptor.getErrProcessorFactory(); @@ -606,8 +784,8 @@ return errProcessor; } - private InputProcessor createInProcessor(OutputStream os) { - return InputProcessors.copying(new OutputStreamWriter(os)); + private InputProcessor createInProcessor(OutputStream os, Charset charset) { + return InputProcessors.copying(new OutputStreamWriter(os, charset)); } private static class ProgressCancellable implements Cancellable { diff --git a/extexecution/src/org/netbeans/api/extexecution/ExternalProcessBuilder.java b/extexecution/src/org/netbeans/api/extexecution/ExternalProcessBuilder.java --- a/extexecution/src/org/netbeans/api/extexecution/ExternalProcessBuilder.java +++ b/extexecution/src/org/netbeans/api/extexecution/ExternalProcessBuilder.java @@ -100,6 +100,8 @@ private final Map envVariables = new HashMap(); + private final boolean emptySystemVariables; + /** * Creates the new builder that will create the process by running * given executable. Arguments must not be part of the string. @@ -117,6 +119,7 @@ this.arguments.addAll(builder.arguments); this.paths.addAll(builder.paths); this.envVariables.putAll(builder.envVariables); + this.emptySystemVariables = builder.emptySystemVariables; } /** @@ -286,7 +289,13 @@ } Map pbEnv = pb.environment(); - Map env = buildEnvironment(pbEnv); + Map env; + if (emptySystemVariables) { + pbEnv.clear(); + env = new HashMap(); + } else { + env = buildEnvironment(pbEnv); + } pbEnv.putAll(env); String uuid = UUID.randomUUID().toString(); pbEnv.put(WrapperProcess.KEY_UUID, uuid); @@ -297,6 +306,11 @@ return wp; } + ExternalProcessBuilder emptySystemVariables(boolean emptySystemVariables) { + BuilderData builder = new BuilderData(this); + return new ExternalProcessBuilder(builder.emptySystemVariables(emptySystemVariables)); + } + /** * Logs the given pb using the given level. * @@ -331,18 +345,7 @@ // Find PATH environment variable - on Windows it can be some other // case and we should use whatever it has. - String pathName = "PATH"; // NOI18N - - if (Utilities.isWindows()) { - pathName = "Path"; // NOI18N - - for (String key : ret.keySet()) { - if ("PATH".equals(key.toUpperCase(Locale.ENGLISH))) { // NOI18N - pathName = key; - break; - } - } - } + String pathName = getPathName(original); // TODO use StringBuilder String currentPath = ret.get(pathName); @@ -362,6 +365,7 @@ return ret; } + // package level for unit testing List buildArguments() { if (!Utilities.isWindows()) { @@ -378,6 +382,44 @@ return result; } + static void putPath(File path, String pathName, boolean prepend, Map current) { + String currentPath = current.get(pathName); + + if (currentPath == null) { + currentPath = ""; + } + + if (prepend) { + currentPath = path.getAbsolutePath().replace(" ", "\\ ") //NOI18N + + File.pathSeparator + currentPath; + } else { + currentPath = currentPath + File.pathSeparator + + path.getAbsolutePath().replace(" ", "\\ "); //NOI18N + } + + if (!"".equals(currentPath.trim())) { + current.put(pathName, currentPath); + } + } + + static String getPathName(Map systemEnv) { + // Find PATH environment variable - on Windows it can be some other + // case and we should use whatever it has. + String pathName = "PATH"; // NOI18N + + if (Utilities.isWindows()) { + pathName = "Path"; // NOI18N + + for (String keySystem : systemEnv.keySet()) { + if ("PATH".equals(keySystem.toUpperCase(Locale.ENGLISH))) { // NOI18N + pathName = keySystem; + break; + } + } + } + return pathName; + } + private static String escapeString(String s) { if (s.length() == 0) { return "\"\""; // NOI18N @@ -475,6 +517,8 @@ private Map envVariables = new HashMap(); + private boolean emptySystemVariables; + public BuilderData(String executable) { this.executable = executable; } @@ -486,6 +530,7 @@ this.arguments.addAll(builder.arguments); this.paths.addAll(builder.paths); this.envVariables.putAll(builder.envVariables); + this.emptySystemVariables = builder.emptySystemVariables; } public BuilderData workingDirectory(File workingDirectory) { @@ -521,6 +566,11 @@ envVariables.put(name, value); return this; } + + public BuilderData emptySystemVariables(boolean emptySystemVariables) { + this.emptySystemVariables = emptySystemVariables; + return this; + } } diff --git a/extexecution/src/org/netbeans/api/extexecution/ProcessBuilder.java b/extexecution/src/org/netbeans/api/extexecution/ProcessBuilder.java --- a/extexecution/src/org/netbeans/api/extexecution/ProcessBuilder.java +++ b/extexecution/src/org/netbeans/api/extexecution/ProcessBuilder.java @@ -46,12 +46,19 @@ 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.ProcessBuilderAccessor; +import org.netbeans.modules.extexecution.ProcessParametersAccessor; +import org.netbeans.spi.extexecution.EnvironmentFactory; +import org.netbeans.spi.extexecution.EnvironmentImplementation; import org.netbeans.spi.extexecution.ProcessBuilderImplementation; +import org.netbeans.spi.extexecution.ProcessBuilderImplementation2; +import org.netbeans.spi.extexecution.ProcessParameters; +import org.openide.util.Lookup; import org.openide.util.NbBundle; import org.openide.util.Parameters; import org.openide.util.UserQuestionException; @@ -68,35 +75,49 @@ * for creating the local machine OS processes. *

* Thread safety of this class depends on thread safety of - * the {@link ProcessBuilderImplementation} the class is using. If it is thread + * 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 ProcessBuilderImplementation2} is used and it is (including + * {@link EnvironmentImplementation}) thread safe and does not have any mutable + * configuration accessible via {@link ProcessBuilderImplementation2#getLookup()} + * it is thread safe as well. Otherwise it is not thread safe. + *

+ * The synchronization mechanism use in this object is the {@link ProcessBuilderImplementation} + * or {@link ProcessBuilderImplementation2} object monitor. * * @author Petr Hejl * @since 1.28 */ -public final class ProcessBuilder implements Callable { +public final class ProcessBuilder implements Callable, Lookup.Provider { private final ProcessBuilderImplementation implementation; + private final ProcessBuilderImplementation2 implementation2; + + private final Object lock; + private final String description; - /**GuardedBy("this")*/ + /**GuardedBy("lock")*/ private String executable; - /**GuardedBy("this")*/ + /**GuardedBy("lock")*/ private String workingDirectory; - /**GuardedBy("this")*/ + /**GuardedBy("lock")*/ private List arguments = new ArrayList(); - /**GuardedBy("this")*/ + /**GuardedBy("lock")*/ private List paths = new ArrayList(); - /**GuardedBy("this")*/ + /**GuardedBy("lock")*/ private Map envVariables = new HashMap(); - /**GuardedBy("this")*/ + /**GuardedBy("lock")*/ private boolean redirectErrorStream; static { @@ -106,12 +127,32 @@ public ProcessBuilder createProcessBuilder(ProcessBuilderImplementation impl, String description) { return new ProcessBuilder(impl, description); } + + @Override + public ProcessBuilder createProcessBuilder(ProcessBuilderImplementation2 impl, String description) { + return new ProcessBuilder(impl, description); + } }); } private ProcessBuilder(ProcessBuilderImplementation implementation, String description) { + this(implementation, null, description); + } + + private ProcessBuilder(ProcessBuilderImplementation2 implementation2, String description) { + this(null, implementation2, description); + } + + private ProcessBuilder(ProcessBuilderImplementation implementation, ProcessBuilderImplementation2 implementation2, String description) { + assert implementation == null || implementation2 == null; this.implementation = implementation; + this.implementation2 = implementation2; this.description = description; + if (implementation != null) { + lock = implementation; + } else { + lock = implementation2; + } } /** @@ -122,7 +163,7 @@ * machine */ public static ProcessBuilder getLocal() { - return new ProcessBuilder(new LocalProcessFactory(), + return new ProcessBuilder(new LocalProcessBuilder(), NbBundle.getMessage(ProcessBuilder.class, "LocalProcessBuilder")); } @@ -146,7 +187,7 @@ public void setExecutable(@NonNull String executable) { Parameters.notNull("executable", executable); - synchronized (this) { + synchronized (lock) { this.executable = executable; } } @@ -158,7 +199,7 @@ * @param workingDirectory the working directory of the process */ public void setWorkingDirectory(@NullAllowed String workingDirectory) { - synchronized (this) { + synchronized (lock) { this.workingDirectory = workingDirectory; } } @@ -172,7 +213,7 @@ public void setArguments(@NonNull List arguments) { Parameters.notNull("arguments", arguments); - synchronized (this) { + synchronized (lock) { this.arguments.clear(); this.arguments.addAll(arguments); } @@ -184,11 +225,20 @@ * exception of PATH possibly configured by {@link #setPaths(java.util.List)}. * * @param envVariables the environment variables for the process + * @deprecated use {@link Environment#setVariable(java.lang.String, java.lang.String)} + * on object received by {@link #getEnvironment()} call */ public void setEnvironmentVariables(@NonNull Map envVariables) { Parameters.notNull("envVariables", envVariables); - synchronized (this) { + if (implementation2 != null) { + for (Map.Entry entry : envVariables.entrySet()) { + implementation2.getEnvironment().setVariable(entry.getKey(), entry.getValue()); + } + return; + } + + synchronized (lock) { this.envVariables.clear(); this.envVariables.putAll(envVariables); } @@ -196,15 +246,32 @@ /** * Sets the additional paths to be included in PATH environment - * variable for the process. + * variable for the process. The paths are prepended to the PATH variable + * one by one as specified in the list so in fact the last path in + * the list will be the first one in the PATH variable. + *

+ * This method is semi-deprecated. If {@link #getEnvironment()} is not + * null this call adds paths one by one by call to + * {@link Environment#prependPath(java.lang.String, java.lang.String)} + * on returned object. * * @param paths the additional paths to be included in PATH * environment variable + * @deprecated use {@link Environment#prependPath(java.lang.String, java.lang.String)} + * on object received by {@link #getEnvironment()} call, with + * the first argument being PATH */ public void setPaths(@NonNull List paths) { Parameters.notNull("paths", paths); - synchronized (this) { + if (implementation2 != null) { + for (String path : paths) { + implementation2.getEnvironment().prependPath("PATH", path); // NOI18N + } + return; + } + + synchronized (lock) { this.paths.clear(); this.paths.addAll(paths); } @@ -218,12 +285,86 @@ * @param redirectErrorStream the error stream redirection */ public void setRedirectErrorStream(boolean redirectErrorStream) { - synchronized (this) { + synchronized (lock) { this.redirectErrorStream = redirectErrorStream; } } /** + * Returns the object for environment variables manipulation. + * + * @return the object for environment variables manipulation + * @since 1.37 + */ + @NonNull + public Environment getEnvironment() { + if (implementation2 != null) { + return implementation2.getEnvironment(); + } + return EnvironmentFactory.createEnvironment(new EnvironmentImplementation() { + + @Override + public void prependPath(String name, String value) { + if (!name.equalsIgnoreCase("PATH")) { // NOI18N + throw new UnsupportedOperationException("The deprecated implementation " + + implementation.getClass().getName() + "does not support this method for a non PATH variable."); + } + synchronized (lock) { + paths.add(0, value); + } + } + + @Override + public void setVariable(String name, String value) { + synchronized (lock) { + envVariables.put(name, value); + } + } + + @Override + public String getVariable(String name) { + throw new UnsupportedOperationException("The deprecated implementation " + + implementation.getClass().getName() + "does not support this method."); + } + + @Override + public void appendPath(String name, String value) { + throw new UnsupportedOperationException("The deprecated implementation " + + implementation.getClass().getName() + "does not support this method."); + } + + @Override + public void removeVariable(String name) { + throw new UnsupportedOperationException("The deprecated implementation " + + implementation.getClass().getName() + "does not support this method."); + } + + @Override + public Map values() { + throw new UnsupportedOperationException("The deprecated implementation " + + implementation.getClass().getName() + "does not support this method."); + } + }); + } + + /** + * Returns the associated {@link Lookup}. Extension point provided by + * {@link ProcessBuilderImplementation2}. + * + * @return the associated {@link Lookup}. + * @see ProcessBuilderImplementation2#getLookup() + * @since 1.37 + */ + @Override + public Lookup getLookup() { + if (implementation2 != null) { + return implementation2.getLookup(); + } + return Lookup.EMPTY; + } + + + /** * Creates the new {@link Process} based on the properties configured * in this builder. *

@@ -232,7 +373,7 @@ *

* Since version 1.35 implementors of this method are advised to throw * a {@link UserQuestionException} in case the execution cannot be - * performed and requires additional user confirmation, or configuration. + * performed and requires additional user confirmation, or configuration. * Callers of this method may check for this exception and handle it * appropriately. * @@ -248,52 +389,168 @@ @NonNull @Override public Process call() throws IOException { - String currentExecutable = null; - String currentWorkingDirectory = null; + String currentExecutable; + String currentWorkingDirectory; List currentArguments = new ArrayList(); List currentPaths = new ArrayList(); - Map currentEnvVariables = new HashMap(); - boolean currentRedirectErrorStream = false; + Map currentVariables = new HashMap(); + boolean currentRedirectErrorStream; - synchronized (this) { + synchronized (lock) { currentExecutable = executable; currentWorkingDirectory = workingDirectory; currentArguments.addAll(arguments); currentPaths.addAll(paths); - currentEnvVariables.putAll(envVariables); currentRedirectErrorStream = redirectErrorStream; + if (implementation2 != null) { + currentVariables.putAll(getEnvironment().values()); + } else { + currentVariables.putAll(envVariables); + } } if (currentExecutable == null) { throw new IllegalStateException("The executable has not been configured"); } + if (implementation2 != null) { + ProcessParameters params = ProcessParametersAccessor.getDefault().createProcessParameters( + currentExecutable, currentWorkingDirectory, currentArguments, + currentRedirectErrorStream, currentVariables); + return implementation2.createProcess(params); + } + return implementation.createProcess(currentExecutable, currentWorkingDirectory, currentArguments, - currentPaths, currentEnvVariables, currentRedirectErrorStream); + currentPaths, currentVariables, currentRedirectErrorStream); } - private static class LocalProcessFactory implements ProcessBuilderImplementation { + /** + * Marks an object from which it is possible to get a {@link ProcessBuilder}. + * + * @since 1.37 + */ + 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 ProcessBuilderImplementation2 { + + private final Environment environment = EnvironmentFactory.createEnvironment( + new LocalEnvironment(this, System.getenv())); @Override - public Process createProcess(String executable, String workingDirectory, List arguments, - List paths, Map environment, boolean redirectErrorStream) throws IOException { + public Environment getEnvironment() { + return environment; + } - ExternalProcessBuilder builder = new ExternalProcessBuilder(executable); - if (workingDirectory != null) { - builder = builder.workingDirectory(new File(workingDirectory)); + @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 : arguments) { + for (String argument : parameters.getArguments()) { builder = builder.addArgument(argument); } - for (String path : paths) { - builder = builder.prependPath(new File(path)); - } - for (Map.Entry entry : environment.entrySet()) { + builder = builder.redirectErrorStream(parameters.isRedirectErrorStream()); + + builder = builder.emptySystemVariables(true); + for (Map.Entry entry : parameters.getEnvironmentVariables().entrySet()) { builder = builder.addEnvironmentVariable(entry.getKey(), entry.getValue()); } - builder = builder.redirectErrorStream(redirectErrorStream); 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); + } + } + } + } } diff --git a/extexecution/src/org/netbeans/api/extexecution/input/InputReaders.java b/extexecution/src/org/netbeans/api/extexecution/input/InputReaders.java --- a/extexecution/src/org/netbeans/api/extexecution/input/InputReaders.java +++ b/extexecution/src/org/netbeans/api/extexecution/input/InputReaders.java @@ -46,6 +46,7 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; +import java.io.StringReader; import java.nio.charset.Charset; import org.netbeans.api.annotations.common.CheckForNull; import org.netbeans.api.annotations.common.NonNull; @@ -82,6 +83,12 @@ */ @NonNull public static InputReader forReader(@NonNull Reader reader) { + if (reader instanceof StringReader) { + // unfortunatelly the string reader is always + // ready (isReady() returns true) which I would consider a bug + // when end of string is reached + return new DefaultInputReader(reader, false); + } return new DefaultInputReader(reader, true); } diff --git a/extexecution/src/org/netbeans/api/extexecution/process/ProcessCharset.java b/extexecution/src/org/netbeans/api/extexecution/process/ProcessCharset.java new file mode 100644 --- /dev/null +++ b/extexecution/src/org/netbeans/api/extexecution/process/ProcessCharset.java @@ -0,0 +1,169 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2012 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2012 Sun Microsystems, Inc. + */ +package org.netbeans.api.extexecution.process; + +import java.nio.charset.Charset; +import org.netbeans.api.annotations.common.CheckForNull; +import org.netbeans.api.annotations.common.NonNull; +import org.openide.util.Lookup; +import org.openide.util.Parameters; + +/** + * This class allows client to get input and output charset of + * the {@link Process}. Though the process charsets might not be always + * supported. + *

+ * If the {@link Process} implementation wants to support charsets it has to + * implement {@link Lookup.Provider} and there has to be instance of this class + * in the returned {@link Lookup}. The functionality itself is implemented in + * methods {@link #getInputCharset()} and {@link #getOutputCharset()}. + * + * @author Petr Hejl + * @since 1.37 + */ +public abstract class ProcessCharset { + + private static ProcessCharset find(Process process) { + if (process instanceof Lookup.Provider) { + Lookup.Provider p = (Lookup.Provider) process; + return p.getLookup().lookup(ProcessCharset.class); + } + return null; + } + + /** + * Checks whether the getting of charsets of the {@link Process} is supported. + * If the return value is false methods + * {@link #getInputCharset(java.lang.Process)} and {@link #getOutputCharset(java.lang.Process)} + * will return null for sure. + * + * @param process the process to check + * @return true if process charsets can be received false + * otherwise + */ + public static boolean isSupported(@NonNull Process process) { + Parameters.notNull("process", process); + + return find(process) != null; + } + + /** + * Returns the input charset of the process if supported. In case it is not + * supported or the process can't determine the charset null is + * returned. + * + * @param process the process + * @return the input charset of the process or null + */ + @CheckForNull + public static Charset getInputCharset(@NonNull Process process) { + Parameters.notNull("process", process); + + ProcessCharset processCharset = find(process); + if (processCharset != null) { + return processCharset.getInputCharset(); + } + return null; + } + + /** + * Returns the output charset of the process if supported. In case it is not + * supported or the process can't determine the charset null is + * returned. + * + * @param process the process + * @return the output charset of the process or null + */ + @CheckForNull + public static Charset getOutputCharset(@NonNull Process process) { + Parameters.notNull("process", process); + + ProcessCharset processCharset = find(process); + if (processCharset != null) { + return processCharset.getOutputCharset(); + } + return null; + } + + /** + * Returns the error output charset of the process if supported. In case it + * is not supported or the process can't determine the charset null + * is returned. + * + * @param process the process + * @return the error output charset of the process or null + */ + @CheckForNull + public static Charset getErrorCharset(@NonNull Process process) { + Parameters.notNull("process", process); + + ProcessCharset processCharset = find(process); + if (processCharset != null) { + return processCharset.getErrorCharset(); + } + return null; + } + + /** + * Returns the input charset of a process or null. + * + * @return input charset of a process or null + */ + @CheckForNull + protected abstract Charset getInputCharset(); + + /** + * Returns the output charset of a process or null. + * + * @return output charset of a process or null + */ + @CheckForNull + protected abstract Charset getOutputCharset(); + + /** + * Returns the error output charset of a process or null. + * + * @return error output charset of a process or null + */ + @CheckForNull + protected abstract Charset getErrorCharset(); +} diff --git a/extexecution/src/org/netbeans/api/extexecution/process/ProcessId.java b/extexecution/src/org/netbeans/api/extexecution/process/ProcessId.java new file mode 100644 --- /dev/null +++ b/extexecution/src/org/netbeans/api/extexecution/process/ProcessId.java @@ -0,0 +1,111 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2012 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2012 Sun Microsystems, Inc. + */ +package org.netbeans.api.extexecution.process; + +import org.netbeans.api.annotations.common.CheckForNull; +import org.netbeans.api.annotations.common.NonNull; +import org.openide.util.Lookup; +import org.openide.util.Parameters; + +/** + * This class allows client to request ID of the {@link Process}. Though + * the process ID might not be always supported. + *

+ * If the {@link Process} implementation wants to support the ID it has to + * implement {@link Lookup.Provider} and there has to be instance of this class + * in the returned {@link Lookup}. The functionality itself is implemented in + * method {@link #getId()}. + * + * @author Petr Hejl + * @since 1.37 + */ +public abstract class ProcessId { + + private static ProcessId find(Process process) { + if (process instanceof Lookup.Provider) { + Lookup.Provider p = (Lookup.Provider) process; + return p.getLookup().lookup(ProcessId.class); + } + return null; + } + + /** + * Checks whether the getting of the ID of the {@link Process} is supported. + * If the return value is false the {@link #getId(java.lang.Process)} + * will return null for sure. + * + * @param process the process to check + * @return true if the ID can be received false + * otherwise + */ + public static boolean isSupported(@NonNull Process process) { + Parameters.notNull("process", process); + + return find(process) != null; + } + + /** + * Returns the ID of the process if supported. In case it is not supported + * or the process can't determine its ID null is returned. + * + * @param process the process + * @return the process ID or null + */ + @CheckForNull + public static Integer getId(@NonNull Process process) { + Parameters.notNull("process", process); + + ProcessId processId = find(process); + if (processId != null) { + return processId.getId(); + } + return null; + } + + /** + * Returns the ID of a process or null. + * + * @return ID of a process or null + */ + @CheckForNull + protected abstract Integer getId(); +} diff --git a/extexecution/src/org/netbeans/api/extexecution/process/ProcessSignal.java b/extexecution/src/org/netbeans/api/extexecution/process/ProcessSignal.java new file mode 100644 --- /dev/null +++ b/extexecution/src/org/netbeans/api/extexecution/process/ProcessSignal.java @@ -0,0 +1,181 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2012 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2012 Sun Microsystems, Inc. + */ +package org.netbeans.api.extexecution.process; + +import java.io.IOException; +import org.netbeans.api.annotations.common.NonNull; +import org.openide.util.Lookup; +import org.openide.util.Parameters; + +/** + * This class allows client send a signal to the {@link Process} or to whole + * its group. Though signal sending might not be always supported. + *

+ * If the {@link Process} implementation wants to support the signal sending + * it has to implement {@link Lookup.Provider} and there has to be instance + * of this class in the returned {@link Lookup}. The functionality itself is + * implemented in methods {@link #signal(org.netbeans.api.extexecution.process.ProcessSignal.Signal)} + * and {@link #signalGroup(org.netbeans.api.extexecution.process.ProcessSignal.Signal)}. + * + * @author Petr Hejl + * @since 1.37 + */ +public abstract class ProcessSignal { + + private static ProcessSignal find(Process process) { + if (process instanceof Lookup.Provider) { + Lookup.Provider p = (Lookup.Provider) process; + return p.getLookup().lookup(ProcessSignal.class); + } + return null; + } + + /** + * Checks whether the signal sending is supported for the {@link Process}. + * If the return value is false the signal operations are + * effectively noops. + * + * @param process the process to check + * @return true if the signals can be send false + * otherwise + */ + public static boolean isSupported(@NonNull Process process) { + Parameters.notNull("process", process); + + return find(process) != null; + } + + /** + * Sends a signal to the process. If the signal sending is not supported + * this is effectively noop. + * + * @param process the process where to send signal + * @param signal the signal to send + * @throws IOException if problem with signal sending occurs + */ + public static void signal(@NonNull Process process, @NonNull Signal signal) throws IOException { + Parameters.notNull("process", process); + Parameters.notNull("signal", signal); + + ProcessSignal processSignal = find(process); + if (processSignal != null) { + processSignal.signal(signal); + } + } + + /** + * Sends a signal to the group the process belongs to. If the signal sending + * is not supported this is effectively noop. + * + * @param process the process marking the group where to send signal + * @param signal the signal to send + * @throws IOException if problem with signal sending occurs + */ + public static void signalGroup(@NonNull Process process, @NonNull Signal signal) throws IOException { + Parameters.notNull("process", process); + Parameters.notNull("signal", signal); + + ProcessSignal processSignal = find(process); + if (processSignal != null) { + processSignal.signalGroup(signal); + } + } + + /** + * Sends a given signal to the process. + * + * @param signal the signal to send + * @throws IOException if problem with signal sending occurs + */ + protected abstract void signal(@NonNull Signal signal) throws IOException; + + /** + * Sends a given signal to the group the process belongs to. + * + * @param signal the signal to send + * @throws IOException if problem with signal sending occurs + */ + protected abstract void signalGroup(@NonNull Signal signal) throws IOException; + + public enum Signal { + + NULL, + SIGHUP, + SIGINT, + SIGQUIT, + SIGILL, + SIGTRAP, + SIGABRT, + SIGEMT, + SIGFPE, + SIGKILL, + SIGBUS, + SIGSEGV, + SIGSYS, + SIGPIPE, + SIGALRM, + SIGTERM, + SIGUSR1, + SIGUSR2, + SIGCHLD, + SIGPWR, + SIGWINCH, + SIGURG, + SIGPOLL, + SIGSTOP, + SIGTSTP, + SIGCONT, + SIGTTIN, + SIGTTOU, + SIGVTALRM, + SIGPROF, + SIGXCPU, + SIGWAITING, + SIGLWP, + SIGFREEZE, + SIGTHAW, + SIGCANCEL, + SIGLOST, + SIGXRES, + SIGJVM1; + } +} diff --git a/extexecution/src/org/netbeans/api/extexecution/process/package-info.java b/extexecution/src/org/netbeans/api/extexecution/process/package-info.java new file mode 100644 --- /dev/null +++ b/extexecution/src/org/netbeans/api/extexecution/process/package-info.java @@ -0,0 +1,50 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 1997-2010 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * Contributor(s): + * + * The Original Software is NetBeans. The Initial Developer of the Original + * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun + * Microsystems, Inc. All Rights Reserved. + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + */ + +/** + * The API supporting enhanced functionality for + * a {@link java.lang.Process}. + */ +package org.netbeans.api.extexecution.process; + diff --git a/extexecution/src/org/netbeans/modules/extexecution/EnvironmentAccessor.java b/extexecution/src/org/netbeans/modules/extexecution/EnvironmentAccessor.java new file mode 100644 --- /dev/null +++ b/extexecution/src/org/netbeans/modules/extexecution/EnvironmentAccessor.java @@ -0,0 +1,81 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2012 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2012 Sun Microsystems, Inc. + */ +package org.netbeans.modules.extexecution; + +import org.netbeans.api.extexecution.Environment; +import org.netbeans.spi.extexecution.EnvironmentImplementation; + +/** + * + * @author Petr Hejl + */ +public abstract class EnvironmentAccessor { + + private static volatile EnvironmentAccessor DEFAULT; + + public static EnvironmentAccessor getDefault() { + EnvironmentAccessor a = DEFAULT; + if (a != null) { + return a; + } + + // invokes static initializer of Environment.class + // that will assign value to the DEFAULT field above + Class c = Environment.class; + try { + Class.forName(c.getName(), true, c.getClassLoader()); + } catch (ClassNotFoundException ex) { + assert false : ex; + } + return DEFAULT; + } + + public static void setDefault(EnvironmentAccessor accessor) { + if (DEFAULT != null) { + throw new IllegalStateException(); + } + + DEFAULT = accessor; + } + + public abstract Environment createEnvironment(EnvironmentImplementation impl); +} diff --git a/extexecution/src/org/netbeans/modules/extexecution/ProcessBuilderAccessor.java b/extexecution/src/org/netbeans/modules/extexecution/ProcessBuilderAccessor.java --- a/extexecution/src/org/netbeans/modules/extexecution/ProcessBuilderAccessor.java +++ b/extexecution/src/org/netbeans/modules/extexecution/ProcessBuilderAccessor.java @@ -42,6 +42,7 @@ package org.netbeans.modules.extexecution; import org.netbeans.spi.extexecution.ProcessBuilderImplementation; +import org.netbeans.spi.extexecution.ProcessBuilderImplementation2; /** * @@ -78,4 +79,7 @@ public abstract org.netbeans.api.extexecution.ProcessBuilder createProcessBuilder( ProcessBuilderImplementation impl, String description); + + public abstract org.netbeans.api.extexecution.ProcessBuilder createProcessBuilder( + ProcessBuilderImplementation2 impl, String description); } diff --git a/extexecution/src/org/netbeans/modules/extexecution/ProcessParametersAccessor.java b/extexecution/src/org/netbeans/modules/extexecution/ProcessParametersAccessor.java new file mode 100644 --- /dev/null +++ b/extexecution/src/org/netbeans/modules/extexecution/ProcessParametersAccessor.java @@ -0,0 +1,84 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2012 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2012 Sun Microsystems, Inc. + */ +package org.netbeans.modules.extexecution; + +import java.util.List; +import java.util.Map; +import org.netbeans.spi.extexecution.ProcessParameters; + +/** + * + * @author Petr Hejl + */ +public abstract class ProcessParametersAccessor { + + private static volatile ProcessParametersAccessor DEFAULT; + + public static ProcessParametersAccessor getDefault() { + ProcessParametersAccessor a = DEFAULT; + if (a != null) { + return a; + } + + // invokes static initializer of ProcessParameters.class + // that will assign value to the DEFAULT field above + Class c = ProcessParameters.class; + try { + Class.forName(c.getName(), true, c.getClassLoader()); + } catch (ClassNotFoundException ex) { + assert false : ex; + } + return DEFAULT; + } + + public static void setDefault(ProcessParametersAccessor accessor) { + if (DEFAULT != null) { + throw new IllegalStateException(); + } + + DEFAULT = accessor; + } + + public abstract ProcessParameters createProcessParameters(String executable, + String workingDirectory, List arguments, boolean redirectErrorStream, + Map environmentVariables); +} diff --git a/extexecution/src/org/netbeans/spi/extexecution/EnvironmentFactory.java b/extexecution/src/org/netbeans/spi/extexecution/EnvironmentFactory.java new file mode 100644 --- /dev/null +++ b/extexecution/src/org/netbeans/spi/extexecution/EnvironmentFactory.java @@ -0,0 +1,69 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2011 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2011 Sun Microsystems, Inc. + */ +package org.netbeans.spi.extexecution; + +import org.netbeans.api.extexecution.Environment; +import org.netbeans.modules.extexecution.EnvironmentAccessor; + +/** + * The factory allowing SPI implementors of {@link EnvironmentImplementation} + * to create its API instances {@link Environment}. + * + * @author Petr Hejl + * @since 1.37 + */ +public class EnvironmentFactory { + + private EnvironmentFactory() { + super(); + } + + /** + * Creates the instance of {@link Environment} from its SPI representation. + * + * @param impl SPI representation + * @return the API instance + */ + public static Environment createEnvironment(EnvironmentImplementation impl) { + return EnvironmentAccessor.getDefault().createEnvironment(impl); + } +} diff --git a/extexecution/src/org/netbeans/spi/extexecution/EnvironmentImplementation.java b/extexecution/src/org/netbeans/spi/extexecution/EnvironmentImplementation.java new file mode 100644 --- /dev/null +++ b/extexecution/src/org/netbeans/spi/extexecution/EnvironmentImplementation.java @@ -0,0 +1,114 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2012 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2012 Sun Microsystems, Inc. + */ +package org.netbeans.spi.extexecution; + +import java.util.Map; +import org.netbeans.api.annotations.common.CheckForNull; +import org.netbeans.api.annotations.common.NonNull; +import org.netbeans.api.extexecution.Environment; + +/** + * The interface representing the implementation + * of {@link org.netbeans.api.extexecution.Environment}. + * + * @see Environment + * @author Petr Hejl + * @since 1.37 + */ +public interface EnvironmentImplementation { + + /** + * Returns the value of the variable or null. + * + * @param name the name of the variable + * @return the value of the variable or null + */ + @CheckForNull + String getVariable(@NonNull String name); + + /** + * Appends a path to a path-like variable. The proper path separator should + * be used to separate the new value. + * + * @param name the name of the variable such as for example + * PATH or LD_LIBRARY_PATH + * @param value the value (path to append) + */ + void appendPath(@NonNull String name, @NonNull String value); + + /** + * Prepends a path to a path-like variable. The proper path separator should + * be used to separate the new value. + * + * @param name the name of the variable such as for example + * PATH or LD_LIBRARY_PATH + * @param value the value (path to prepend) + */ + void prependPath(@NonNull String name, @NonNull String value); + + /** + * Sets a value for a variable with the given name. + * + * @param name the name of the variable + * @param value the value + */ + void setVariable(@NonNull String name, @NonNull String value); + + /** + * Removes a variable with the given name. The subsequent call to + * {@link #getVariable(java.lang.String)} with the same argument should + * return null. + * + * @param name the name of the variable + */ + void removeVariable(@NonNull String name); + + /** + * Returns all variable names and associated values as a {@link Map}. + * Changes to the map must not be propagated back to the {@link Environment}. + * + * @return all variable names and associated values + */ + @NonNull + Map values(); + +} diff --git a/extexecution/src/org/netbeans/spi/extexecution/ProcessBuilderFactory.java b/extexecution/src/org/netbeans/spi/extexecution/ProcessBuilderFactory.java --- a/extexecution/src/org/netbeans/spi/extexecution/ProcessBuilderFactory.java +++ b/extexecution/src/org/netbeans/spi/extexecution/ProcessBuilderFactory.java @@ -63,9 +63,24 @@ * @param impl SPI representation * @param description human readable description of the builder * @return the API instance + * @deprecated use {@link #createProcessBuilder(org.netbeans.spi.extexecution.ProcessBuilderImplementation2, java.lang.String)} */ public static org.netbeans.api.extexecution.ProcessBuilder createProcessBuilder( ProcessBuilderImplementation impl, String description) { return ProcessBuilderAccessor.getDefault().createProcessBuilder(impl, description); } + + /** + * Creates the instance of {@link org.netbeans.api.extexecution.ProcessBuilder} + * from its SPI representation. + * + * @param impl SPI representation + * @param description human readable description of the builder + * @return the API instance + * @since 1.37 + */ + public static org.netbeans.api.extexecution.ProcessBuilder createProcessBuilder( + ProcessBuilderImplementation2 impl, String description) { + return ProcessBuilderAccessor.getDefault().createProcessBuilder(impl, description); + } } diff --git a/extexecution/src/org/netbeans/spi/extexecution/ProcessBuilderImplementation.java b/extexecution/src/org/netbeans/spi/extexecution/ProcessBuilderImplementation.java --- a/extexecution/src/org/netbeans/spi/extexecution/ProcessBuilderImplementation.java +++ b/extexecution/src/org/netbeans/spi/extexecution/ProcessBuilderImplementation.java @@ -62,6 +62,7 @@ * @see org.netbeans.api.extexecution.ProcessBuilder * @author Petr Hejl * @since 1.28 + * @deprecated use {@link ProcessBuilderImplementation2} */ public interface ProcessBuilderImplementation { @@ -80,7 +81,7 @@ * the process should be redirected to standard output stream * @return a process created with specified parameters and environment * configuration - * @throws IOException IOException if the process could not be created + * @throws IOException if the process could not be created * @throws UserQuestionException in case there is a need to interact with * user, don't be afraid to throw a subclass of * {@link UserQuestionException} with overriden {@link UserQuestionException#confirmed()} diff --git a/extexecution/src/org/netbeans/spi/extexecution/ProcessBuilderImplementation2.java b/extexecution/src/org/netbeans/spi/extexecution/ProcessBuilderImplementation2.java new file mode 100644 --- /dev/null +++ b/extexecution/src/org/netbeans/spi/extexecution/ProcessBuilderImplementation2.java @@ -0,0 +1,93 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2012 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2012 Sun Microsystems, Inc. + */ +package org.netbeans.spi.extexecution; + +import java.io.IOException; +import org.netbeans.api.annotations.common.NonNull; +import org.netbeans.api.extexecution.Environment; +import org.openide.util.Lookup; + +/** + * The interface representing the implementation + * of {@link org.netbeans.api.extexecution.ProcessBuilder}. + * + * @see org.netbeans.api.extexecution.ProcessBuilder + * @author Petr Hejl + * @since 1.37 + */ +public interface ProcessBuilderImplementation2 extends Lookup.Provider { + + /** + * Returns the object for environment variables manipulation. + * + * @return the object for environment variables manipulation + */ + @NonNull + Environment getEnvironment(); + + /** + * Provides an extension point to the implementors. One may enhance the + * functionality of {@link org.netbeans.api.extexecution.ProcessBuilder} + * by this as the content of this {@link Lookup} is included in + * {@link org.netbeans.api.extexecution.ProcessBuilder#getLookup()} + * + * @return a lookup providing an extension point + */ + @Override + Lookup getLookup(); + + /** + * Creates a process using the specified parameters. + *

+ * The environment variables stored in parameters are acquired by call to + * {@link Environment#values()}. So if the implementation does not aim to be + * or can't thread safe it may check or use the {@link Environment} + * directly. + * + * @param parameters the instance describing the process parameters + * @return a process created with specified parameters + * @throws IOException if the process could not be created + */ + @NonNull + Process createProcess(@NonNull ProcessParameters parameters) throws IOException; + +} diff --git a/extexecution/src/org/netbeans/spi/extexecution/ProcessParameters.java b/extexecution/src/org/netbeans/spi/extexecution/ProcessParameters.java new file mode 100644 --- /dev/null +++ b/extexecution/src/org/netbeans/spi/extexecution/ProcessParameters.java @@ -0,0 +1,142 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2013 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2013 Sun Microsystems, Inc. + */ +package org.netbeans.spi.extexecution; + +import java.util.List; +import java.util.Map; +import org.netbeans.api.annotations.common.CheckForNull; +import org.netbeans.api.annotations.common.NonNull; +import org.netbeans.modules.extexecution.ProcessParametersAccessor; + +/** + * The parameters configured for process creation. + * + * @see ProcessBuilderImplementation2 + * @author Petr Hejl + * @since 1.37 + */ +public final class ProcessParameters { + + private final String executable; + + private final String workingDirectory; + + private final List arguments; + + private final boolean redirectErrorStream; + + private final Map environmentVariables; + + static { + ProcessParametersAccessor.setDefault(new ProcessParametersAccessor() { + + @Override + public ProcessParameters createProcessParameters(String executable, String workingDirectory, + List arguments, boolean redirectErrorStream, Map environmentVariables) { + return new ProcessParameters(executable, workingDirectory, arguments, + redirectErrorStream, environmentVariables); + } + }); + } + + private ProcessParameters(String executable, String workingDirectory, List arguments, + boolean redirectErrorStream, Map environmentVariables) { + this.executable = executable; + this.workingDirectory = workingDirectory; + this.arguments = arguments; + this.redirectErrorStream = redirectErrorStream; + this.environmentVariables = environmentVariables; + } + + /** + * Returns the configured executable. + * + * @return the configured executable + */ + @NonNull + public String getExecutable() { + return executable; + } + + /** + * Returns the configured working directory or null in case it + * was not configured. + * + * @return the configured working directory or null in case it + * was not configured + */ + @CheckForNull + public String getWorkingDirectory() { + return workingDirectory; + } + + /** + * Returns the arguments configured for the process. + * + * @return the arguments configured for the process + */ + @NonNull + public List getArguments() { + return arguments; + } + + /** + * Returns true if standard error stream should be redirected + * to standard output stream. + * + * @return true if standard error stream should be redirected + * to standard output stream + */ + public boolean isRedirectErrorStream() { + return redirectErrorStream; + } + + /** + * Returns the environment variables configured for the process. + * + * @return the environment variables configured for the process + */ + @NonNull + public Map getEnvironmentVariables() { + return environmentVariables; + } +} diff --git a/extexecution/test/unit/src/org/netbeans/api/extexecution/ExecutionServiceTest.java b/extexecution/test/unit/src/org/netbeans/api/extexecution/ExecutionServiceTest.java --- a/extexecution/test/unit/src/org/netbeans/api/extexecution/ExecutionServiceTest.java +++ b/extexecution/test/unit/src/org/netbeans/api/extexecution/ExecutionServiceTest.java @@ -42,11 +42,14 @@ package org.netbeans.api.extexecution; +import java.io.ByteArrayInputStream; import java.io.FilterInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.Reader; +import java.io.StringWriter; +import java.io.Writer; import java.lang.reflect.InvocationTargetException; import java.nio.charset.Charset; import java.util.LinkedList; @@ -358,7 +361,7 @@ final String[] lines = new String[] {"Process line \u1234", "Process line \u1235", "Process line \u1236"}; TestInputStream is = new TestInputStream(TestInputUtils.prepareInputStream(lines, "\n", charset, true)); - TestProcess process = new TestProcess(0, is); + TestProcess process = new TestProcess(0, is, null); is.setProcess(process); TestCallable callable = new TestCallable(); @@ -536,6 +539,123 @@ assertFalse(val.get()); } + public void testWriterService() throws InterruptedException, ExecutionException { + TestProcess process = new TestProcess(0, + new ByteArrayInputStream("testing input".getBytes()), + new ByteArrayInputStream("testing error".getBytes())); + TestCallable callable = new TestCallable(); + callable.addProcess(process); + + StringWriter in = new StringWriter(); + StringWriter err = new StringWriter(); + ExecutionService service = ExecutionService.newService(callable, in, err); + Future task = service.run(); + assertNotNull(task); + + process.waitStarted(); + + process.destroy(); + process.waitFor(); + assertTrue(process.isFinished()); + + assertEquals(0, task.get().intValue()); + assertEquals("testing input", in.toString()); + assertEquals("testing error", err.toString()); + } + + public void testWriterServiceOnce() throws InterruptedException, ExecutionException { + TestProcess process = new TestProcess(0, + new ByteArrayInputStream(new byte[0]), null); + TestCallable callable = new TestCallable(); + callable.addProcess(process); + + ExecutionService service = ExecutionService.newService(callable, + (Writer) null, (Writer) null); + Future task = service.run(); + assertNotNull(task); + + process.waitStarted(); + + process.destroy(); + process.waitFor(); + assertTrue(process.isFinished()); + + assertEquals(0, task.get().intValue()); + + try { + service.run(); + fail("Multiple run should fail with ISE"); + } catch (IllegalStateException ex) { + // expected + } + } + + public void testLineService() throws InterruptedException, ExecutionException { + final String[] linesIn = new String[] {"Process line 1", "Process line 2", "Process line 3"}; + final String[] linesErr = new String[] {"Error line 1", "Error line 2", "Error line 3"}; + + TestInputStream in = new TestInputStream( + TestInputUtils.prepareInputStream(linesIn, "\n", Charset.defaultCharset(), true)); + TestInputStream err = new TestInputStream( + TestInputUtils.prepareInputStream(linesErr, "\n", Charset.defaultCharset(), true)); + TestProcess process = new TestProcess(0, in, err); + TestCallable callable = new TestCallable(); + callable.addProcess(process); + + TestLineProcessor inProcessor = new TestLineProcessor(false); + TestLineProcessor errProcessor = new TestLineProcessor(false); + ExecutionService service = ExecutionService.newService(callable, + inProcessor, errProcessor, null); + Future task = service.run(); + assertNotNull(task); + + process.waitStarted(); + + process.destroy(); + process.waitFor(); + assertTrue(process.isFinished()); + + assertEquals(0, task.get().intValue()); + + List processed = inProcessor.getLinesProcessed(); + assertEquals(linesIn.length, processed.size()); + for (int i = 0; i < linesIn.length; i++) { + assertEquals(linesIn[i], processed.get(i)); + } + processed = errProcessor.getLinesProcessed(); + assertEquals(linesErr.length, processed.size()); + for (int i = 0; i < linesErr.length; i++) { + assertEquals(linesErr[i], processed.get(i)); + } + } + + public void testLineServiceOnce() throws InterruptedException, ExecutionException { + TestProcess process = new TestProcess(0, + new ByteArrayInputStream(new byte[0]), null); + TestCallable callable = new TestCallable(); + callable.addProcess(process); + + ExecutionService service = ExecutionService.newService(callable, + null, null, null); + Future task = service.run(); + assertNotNull(task); + + process.waitStarted(); + + process.destroy(); + process.waitFor(); + assertTrue(process.isFinished()); + + assertEquals(0, task.get().intValue()); + + try { + service.run(); + fail("Multiple run should fail with ISE"); + } catch (IllegalStateException ex) { + // expected + } + } + private static InputOutputManager.InputOutputData getInputOutput(String name, boolean actions, String optionsPath) { @@ -579,6 +699,8 @@ private final InputStream is; + private final InputStream err; + private boolean finished; private boolean started; @@ -586,12 +708,13 @@ public TestProcess(int returnValue) { this(returnValue, TestInputUtils.prepareInputStream( new String[] {"Process line 1", "Process line 2", "Process line 3"}, "\n", - Charset.defaultCharset(), true)); + Charset.defaultCharset(), true), null); } - public TestProcess(int returnValue, InputStream is) { + public TestProcess(int returnValue, InputStream is, InputStream err) { this.returnValue = returnValue; this.is = is; + this.err = err; } public void start() { @@ -637,6 +760,9 @@ @Override public InputStream getErrorStream() { + if (err != null) { + return err; + } return new InputStream() { @Override public int read() throws IOException { diff --git a/extexecution/test/unit/src/org/netbeans/api/extexecution/ProcessBuilderTest.java b/extexecution/test/unit/src/org/netbeans/api/extexecution/ProcessBuilderTest.java --- a/extexecution/test/unit/src/org/netbeans/api/extexecution/ProcessBuilderTest.java +++ b/extexecution/test/unit/src/org/netbeans/api/extexecution/ProcessBuilderTest.java @@ -48,8 +48,13 @@ import java.util.List; import java.util.Map; import org.netbeans.junit.NbTestCase; +import org.netbeans.spi.extexecution.EnvironmentFactory; +import org.netbeans.spi.extexecution.EnvironmentImplementation; import org.netbeans.spi.extexecution.ProcessBuilderFactory; import org.netbeans.spi.extexecution.ProcessBuilderImplementation; +import org.netbeans.spi.extexecution.ProcessBuilderImplementation2; +import org.netbeans.spi.extexecution.ProcessParameters; +import org.openide.util.Lookup; /** * @@ -166,7 +171,7 @@ assertEquals("test2", testBuilder.getPaths().get(0)); } - public void testEnvironment() throws IOException { + public void testEnvironmentVariables() throws IOException { TestProcessBuilder testBuilder = new TestProcessBuilder(); ProcessBuilder builder = ProcessBuilderFactory.createProcessBuilder(testBuilder, "Test builder"); builder.setExecutable("ls"); @@ -201,6 +206,136 @@ assertEquals("value2", testBuilder.getEnvironment().get("key2")); } + public void testEnvironment() throws IOException { + TestProcessBuilder2 testBuilder = new TestProcessBuilder2(); + ProcessBuilder builder = ProcessBuilderFactory.createProcessBuilder(testBuilder, "Test builder"); + builder.setExecutable("ls"); + + builder.getEnvironment().setVariable("key1", "value1"); + builder.getEnvironment().setVariable("key2", "value2"); + + assertEquals("value1", testBuilder.getEnvironment().getVariable("key1")); + assertEquals("value2", testBuilder.getEnvironment().getVariable("key2")); + + builder.call(); + assertEquals(2, testBuilder.getParameters().getEnvironmentVariables().size()); + assertEquals("value1", testBuilder.getParameters().getEnvironmentVariables().get("key1")); + assertEquals("value2", testBuilder.getParameters().getEnvironmentVariables().get("key2")); + + builder.getEnvironment().prependPath("PATH", "/test1"); + builder.getEnvironment().prependPath("PATH", "/test2"); + + builder.call(); + assertEquals(3, testBuilder.getParameters().getEnvironmentVariables().size()); + assertEquals("/test2:/test1", + testBuilder.getParameters().getEnvironmentVariables().get("PATH")); + + builder.getEnvironment().appendPath("PATH", "/test3"); + + builder.call(); + assertEquals(3, testBuilder.getParameters().getEnvironmentVariables().size()); + assertEquals("/test2:/test1:/test3", + testBuilder.getParameters().getEnvironmentVariables().get("PATH")); + + builder.getEnvironment().removeVariable("PATH"); + assertNull(builder.getEnvironment().getVariable("PATH")); + + builder.call(); + assertEquals(2, testBuilder.getParameters().getEnvironmentVariables().size()); + assertNull(testBuilder.getParameters().getEnvironmentVariables().get("PATH")); + } + + public void testFallbackEnvironment() throws IOException { + TestProcessBuilder testBuilder = new TestProcessBuilder(); + ProcessBuilder builder = ProcessBuilderFactory.createProcessBuilder(testBuilder, "Test builder"); + builder.setExecutable("ls"); + + builder.getEnvironment().setVariable("key1", "value1"); + builder.getEnvironment().setVariable("key2", "value2"); + + builder.call(); + assertEquals(2, testBuilder.getEnvironment().size()); + assertEquals("value1", testBuilder.getEnvironment().get("key1")); + assertEquals("value2", testBuilder.getEnvironment().get("key2")); + + builder.getEnvironment().prependPath("PATH", "/test1"); + builder.getEnvironment().prependPath("Path", "/test2"); + + builder.call(); + assertEquals(2, testBuilder.getPaths().size()); + assertEquals("/test2", testBuilder.getPaths().get(0)); + assertEquals("/test1", testBuilder.getPaths().get(1)); + + try { + builder.getEnvironment().prependPath("LD_LIBRARY_PATH", "/test"); + fail("Prepend of unknown path does not throw exception"); + } catch (UnsupportedOperationException ex) { + // expected + } + + try { + builder.getEnvironment().appendPath("PATH", "/test"); + fail("Append of any path does not throw exception"); + } catch (UnsupportedOperationException ex) { + // expected + } + + try { + builder.getEnvironment().getVariable("PATH"); + fail("getVariable() does not throw exception"); + } catch (UnsupportedOperationException ex) { + // expected + } + + try { + builder.getEnvironment().removeVariable("PATH"); + fail("removeVariable() does not throw exception"); + } catch (UnsupportedOperationException ex) { + // expected + } + + try { + builder.getEnvironment().values(); + fail("values() does not throw exception"); + } catch (UnsupportedOperationException ex) { + // expected + } + } + + public void testFallbackEnvironmentCombined() throws IOException { + TestProcessBuilder testBuilder = new TestProcessBuilder(); + ProcessBuilder builder = ProcessBuilderFactory.createProcessBuilder(testBuilder, "Test builder"); + builder.setExecutable("ls"); + + builder.getEnvironment().setVariable("key1", "value1"); + builder.getEnvironment().setVariable("key2", "value2"); + + builder.call(); + assertEquals(2, testBuilder.getEnvironment().size()); + assertEquals("value1", testBuilder.getEnvironment().get("key1")); + assertEquals("value2", testBuilder.getEnvironment().get("key2")); + + builder.setEnvironmentVariables(Collections.singletonMap("key3", "value3")); + + builder.call(); + assertEquals(1, testBuilder.getEnvironment().size()); + assertEquals("value3", testBuilder.getEnvironment().get("key3")); + + builder.getEnvironment().prependPath("PATH", "/test1"); + builder.getEnvironment().prependPath("Path", "/test2"); + + builder.call(); + assertEquals(2, testBuilder.getPaths().size()); + assertEquals("/test2", testBuilder.getPaths().get(0)); + assertEquals("/test1", testBuilder.getPaths().get(1)); + + builder.setPaths(Collections.singletonList("/test3")); + + builder.call(); + assertEquals(1, testBuilder.getPaths().size()); + assertEquals("/test3", testBuilder.getPaths().get(0)); + } + public void testRedirectErrorStream() throws IOException { TestProcessBuilder testBuilder = new TestProcessBuilder(); ProcessBuilder builder = ProcessBuilderFactory.createProcessBuilder(testBuilder, "Test builder"); @@ -216,7 +351,7 @@ assertTrue(testBuilder.isRedirectErrorStream()); } - private class TestProcessBuilder implements ProcessBuilderImplementation { + private static class TestProcessBuilder implements ProcessBuilderImplementation { private String executable; @@ -267,6 +402,80 @@ public String getWorkingDirectory() { return workingDirectory; } + } + private static class TestProcessBuilder2 implements ProcessBuilderImplementation2 { + + private final Environment environment = EnvironmentFactory.createEnvironment(new TestEnvironment()); + + private ProcessParameters parameters; + + @Override + public Environment getEnvironment() { + return environment; + } + + @Override + public Lookup getLookup() { + return Lookup.EMPTY; + } + + @Override + public Process createProcess(ProcessParameters parameters) throws IOException { + this.parameters = parameters; + + return null; + } + + public ProcessParameters getParameters() { + return parameters; + } + } + + private static class TestEnvironment implements EnvironmentImplementation { + + private final Map values = new HashMap(); + + @Override + public String getVariable(String name) { + return values.get(name); + } + + @Override + public void appendPath(String name, String value) { + String orig = values.get(name); + if (orig == null || orig.isEmpty()) { + values.put(name, value); + } else { + // intentionally hardcoded for tests + values.put(name, orig + ":" + value); + } + } + + @Override + public void prependPath(String name, String value) { + String orig = values.get(name); + if (orig == null || orig.isEmpty()) { + values.put(name, value); + } else { + // intentionally hardcoded for tests + values.put(name, value + ":" + orig); + } + } + + @Override + public void setVariable(String name, String value) { + values.put(name, value); + } + + @Override + public void removeVariable(String name) { + values.remove(name); + } + + @Override + public Map values() { + return new HashMap(values); + } } } diff --git a/extexecution/test/unit/src/org/netbeans/api/extexecution/input/InputReadersReaderTest.java b/extexecution/test/unit/src/org/netbeans/api/extexecution/input/InputReadersReaderTest.java --- a/extexecution/test/unit/src/org/netbeans/api/extexecution/input/InputReadersReaderTest.java +++ b/extexecution/test/unit/src/org/netbeans/api/extexecution/input/InputReadersReaderTest.java @@ -47,6 +47,7 @@ import java.io.IOException; import java.io.InputStreamReader; import java.io.Reader; +import java.io.StringReader; import java.nio.charset.Charset; import java.util.Arrays; import org.netbeans.junit.NbTestCase; @@ -85,4 +86,22 @@ assertTrue(Arrays.equals(TEST_CHARS, processor.getCharsProcessed())); } + + public void testReadStringReader() throws IOException { + Reader reader = new StringReader(new String(TEST_CHARS)); + InputReader inputReader = InputReaders.forReader(reader); + TestInputProcessor processor = new TestInputProcessor(false); + + int read = 0; + int retries = 0; + while (read < TEST_CHARS.length && retries < MAX_RETRIES) { + read += inputReader.readInput(processor); + retries++; + } + + assertEquals(read, TEST_CHARS.length); + assertEquals(0, processor.getResetCount()); + + assertTrue(Arrays.equals(TEST_CHARS, processor.getCharsProcessed())); + } } diff --git a/extexecution/test/unit/src/org/netbeans/api/extexecution/process/ProcessCharsetTest.java b/extexecution/test/unit/src/org/netbeans/api/extexecution/process/ProcessCharsetTest.java new file mode 100644 --- /dev/null +++ b/extexecution/test/unit/src/org/netbeans/api/extexecution/process/ProcessCharsetTest.java @@ -0,0 +1,91 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2013 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2013 Sun Microsystems, Inc. + */ +package org.netbeans.api.extexecution.process; + +import java.nio.charset.Charset; +import org.netbeans.junit.NbTestCase; +import org.openide.util.Lookup; +import org.openide.util.lookup.Lookups; + +/** + * + * @author Petr Hejl + */ +public class ProcessCharsetTest extends NbTestCase { + + public ProcessCharsetTest(String name) { + super(name); + } + + public void testProcessCharset() { + assertFalse(ProcessCharset.isSupported(new TestProcess(Lookup.EMPTY))); + + assertNull(ProcessCharset.getOutputCharset(new TestProcess(Lookup.EMPTY))); + assertNull(ProcessCharset.getInputCharset(new TestProcess(Lookup.EMPTY))); + assertNull(ProcessCharset.getErrorCharset(new TestProcess(Lookup.EMPTY))); + + TestProcessCharset charset = new TestProcessCharset(); + TestProcess process = new TestProcess(Lookups.fixed(charset)); + assertTrue(ProcessCharset.isSupported(process)); + assertEquals(charset.getOutputCharset(), ProcessCharset.getOutputCharset(process)); + assertEquals(charset.getInputCharset(), ProcessCharset.getInputCharset(process)); + assertEquals(charset.getErrorCharset(), ProcessCharset.getErrorCharset(process)); + } + + private static class TestProcessCharset extends ProcessCharset { + + @Override + protected Charset getInputCharset() { + return Charset.forName("US-ASCII"); + } + + @Override + protected Charset getOutputCharset() { + return Charset.forName("ISO-8859-1"); + } + + @Override + protected Charset getErrorCharset() { + return Charset.forName("UTF-8"); + } + } +} diff --git a/extexecution/test/unit/src/org/netbeans/api/extexecution/process/ProcessIdTest.java b/extexecution/test/unit/src/org/netbeans/api/extexecution/process/ProcessIdTest.java new file mode 100644 --- /dev/null +++ b/extexecution/test/unit/src/org/netbeans/api/extexecution/process/ProcessIdTest.java @@ -0,0 +1,76 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2013 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2013 Sun Microsystems, Inc. + */ +package org.netbeans.api.extexecution.process; + +import org.netbeans.junit.NbTestCase; +import org.openide.util.Lookup; +import org.openide.util.lookup.Lookups; + +/** + * + * @author Petr Hejl + */ +public class ProcessIdTest extends NbTestCase { + + public ProcessIdTest(String name) { + super(name); + } + + public void testProcessId() { + assertFalse(ProcessId.isSupported(new TestProcess(Lookup.EMPTY))); + + assertNull(ProcessId.getId(new TestProcess(Lookup.EMPTY))); + + TestProcessId id = new TestProcessId(); + TestProcess process = new TestProcess(Lookups.fixed(id)); + assertTrue(ProcessId.isSupported(process)); + assertEquals(id.getId(), ProcessId.getId(process)); + } + + private static class TestProcessId extends ProcessId { + + @Override + protected Integer getId() { + return 1; + } + } +} diff --git a/extexecution/test/unit/src/org/netbeans/api/extexecution/process/ProcessSignalTest.java b/extexecution/test/unit/src/org/netbeans/api/extexecution/process/ProcessSignalTest.java new file mode 100644 --- /dev/null +++ b/extexecution/test/unit/src/org/netbeans/api/extexecution/process/ProcessSignalTest.java @@ -0,0 +1,116 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2013 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2013 Sun Microsystems, Inc. + */ +package org.netbeans.api.extexecution.process; + +import java.io.IOException; +import org.netbeans.junit.NbTestCase; +import org.openide.util.Lookup; +import org.openide.util.lookup.Lookups; + +/** + * + * @author Petr Hejl + */ +public class ProcessSignalTest extends NbTestCase { + + public ProcessSignalTest(String name) { + super(name); + } + + public void testProcessSignal() throws IOException { + assertFalse(ProcessSignal.isSupported(new TestProcess(Lookup.EMPTY))); + + TestProcessSignal signal = new TestProcessSignal(); + TestProcess process = new TestProcess(Lookups.fixed(signal)); + assertTrue(ProcessSignal.isSupported(process)); + + ProcessSignal.signal(process, ProcessSignal.Signal.SIGALRM); + ProcessSignal.signalGroup(process, ProcessSignal.Signal.SIGCANCEL); + + assertEquals(ProcessSignal.Signal.SIGALRM, signal.getSignal()); + assertEquals(ProcessSignal.Signal.SIGCANCEL, signal.getGroupSignal()); + + try { + ProcessSignal.signal(process, ProcessSignal.Signal.NULL); + fail("No exception propagated"); + } catch (IOException ex) { + // expected + } + try { + ProcessSignal.signalGroup(process, ProcessSignal.Signal.NULL); + fail("No exception propagated"); + } catch (IOException ex) { + // expected + } + } + + private static class TestProcessSignal extends ProcessSignal { + + private Signal signal; + + private Signal groupSignal; + + @Override + protected void signal(Signal signal) throws IOException { + if (this.signal != null) { + throw new IOException("Test"); + } + this.signal = signal; + } + + @Override + protected void signalGroup(Signal signal) throws IOException { + if (this.groupSignal != null) { + throw new IOException("Test"); + } + this.groupSignal = signal; + } + + public Signal getSignal() { + return signal; + } + + public Signal getGroupSignal() { + return groupSignal; + } + } +} diff --git a/extexecution/test/unit/src/org/netbeans/api/extexecution/process/TestProcess.java b/extexecution/test/unit/src/org/netbeans/api/extexecution/process/TestProcess.java new file mode 100644 --- /dev/null +++ b/extexecution/test/unit/src/org/netbeans/api/extexecution/process/TestProcess.java @@ -0,0 +1,94 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2013 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2013 Sun Microsystems, Inc. + */ +package org.netbeans.api.extexecution.process; + +import java.io.InputStream; +import java.io.OutputStream; +import org.openide.util.Lookup; + +/** + * + * @author Petr Hejl + */ +public class TestProcess extends Process implements Lookup.Provider { + + private final Lookup lookup; + + public TestProcess(Lookup lookup) { + this.lookup = lookup; + } + + @Override + public Lookup getLookup() { + return lookup; + } + + @Override + public OutputStream getOutputStream() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public InputStream getInputStream() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public InputStream getErrorStream() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public int waitFor() throws InterruptedException { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public int exitValue() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public void destroy() { + throw new UnsupportedOperationException("Not supported yet."); + } +} diff --git a/extexecution/test/unit/src/org/netbeans/spi/extexecution/ProcessParametersTest.java b/extexecution/test/unit/src/org/netbeans/spi/extexecution/ProcessParametersTest.java new file mode 100644 --- /dev/null +++ b/extexecution/test/unit/src/org/netbeans/spi/extexecution/ProcessParametersTest.java @@ -0,0 +1,79 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2013 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2013 Sun Microsystems, Inc. + */ +package org.netbeans.spi.extexecution; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import org.netbeans.junit.NbTestCase; +import org.netbeans.modules.extexecution.ProcessParametersAccessor; + +/** + * + * @author Petr Hejl + */ +public class ProcessParametersTest extends NbTestCase { + + public ProcessParametersTest(String name) { + super(name); + } + + public void testParameters() { + Map variables = new HashMap(); + variables.put("key1", "value1"); + variables.put("key2", "value2"); + + ProcessParameters params = ProcessParametersAccessor.getDefault().createProcessParameters( + "ls", "/home", Collections.singletonList("argument"), true, variables); + + assertEquals("ls", params.getExecutable()); + assertEquals("/home", params.getWorkingDirectory()); + assertTrue(params.isRedirectErrorStream()); + + assertEquals(1, params.getArguments().size()); + assertEquals("argument", params.getArguments().get(0)); + + assertEquals(2, params.getEnvironmentVariables().size()); + assertEquals("value1", params.getEnvironmentVariables().get("key1")); + assertEquals("value2", params.getEnvironmentVariables().get("key2")); + } +}