| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.netbeans.api.extexecution.base; |
| |
| import java.io.File; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashMap; |
| 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.api.annotations.common.NullAllowed; |
| import org.openide.util.Lookup; |
| |
| /** |
| * Allows to augment or replace process parameters for a single execution action. |
| * The class is intended to be used by launchers which build parameters based on some |
| * persistent configuration (project, workspace) to allow additions, or replacements |
| * for a single execution only. |
| * <p> |
| * It is <b>strongly recommended</b> for any feature that performs execution of a process to support {@link ExplicitProcessParameters}, |
| * from a contextual {@link Lookup}, or at worst from {@link Lookup#getDefault()}. It will allow for future customizations and |
| * automation of the feature, enhancing the process launch for various environments, technologies etc. |
| * <p> |
| * <i>Note:</i> please refer also to {@code StartupExtender} API in the {@code extexecution} module, which contributes globally |
| * to launcher arguments. |
| * <p> |
| * Two groups of parameters are recognized: {@link #getLauncherArguments()}, which should be passed |
| * first to the process (i.e. launcher parameters) and {@link #getArguments()} that represent the ordinary |
| * process arguments. |
| * <div class="nonnormative"> |
| * For <b>java applications</b> when {@code java} executable is used to launch the application, or even Maven project (see below), the <b>launcherArgs</b> should correspond to VM |
| * arguments, and <b>args</b> correspond to the main class' arguments (passed to the main class). Additional environment variables can be specified. |
| * </div> |
| * <p> |
| * If the object is marked as {@link #isArgReplacement()}, the launcher implementor SHOULD replace all |
| * default or configured parameters with contents of this instruction. Both arguments and launcherArguments can have value {@code null}, which means "undefined": |
| * in that case, the relevant group of configured parameters should not be affected. |
| * <p> |
| * Since these parameters are passed <b>externally</b>, there's an utility method, {@link #buildExplicitParameters(org.openide.util.Lookup)} |
| * that builds the explicit parameter instruction based on {@link Lookup} contents. The parameters are |
| * merged in the order of the {@link Builder#position configured rank} and appearance (in the sort ascending order). |
| * The default rank is {@code 0}, which allows both append or prepend parameters. If an item's |
| * {@link ExplicitProcessParametersTest#isArgReplacement()} is true, all arguments collected so far are discarded. |
| * <p> |
| * <div class="nonnormative"> |
| * If the combining algorithm is acceptable for the caller's purpose, the following pattern may be used to build the final |
| * command line: |
| * <div> |
| * {@snippet file="org/netbeans/api/extexecution/base/ExplicitProcessParametersTest.java" region="decorateWithExplicitParametersSample"} |
| * </div> |
| * This example will combine some args and extra args from project, or configuration with arguments passed from the |
| * {@code runContext} Lookup. |
| * Supposing that a Maven project module supports {@code ExplicitProcessParameters} (it does from version 2/2.144), the caller may influence or override the |
| * parameters passed to the maven exec:exec task (for Run action) this way: |
| * <code><pre> |
| * ActionProvider ap = ... ; // obtain ActionProvider from the project. |
| * ExplicitProcessParameters explicit = ExplicitProcessParameters.builder(). |
| * launcherArg("-DvmArg2=2"). |
| * arg("paramY"). |
| * build(); |
| * ap.invokeAction(ActionProvider.COMMAND_RUN, Lookups.fixed(explicit)); |
| * </pre></code> |
| * By default, <b>args</b> instruction(s) will discard the default parameters, so the above example will also <b>ignore</b> all application |
| * parameters provided in maven action mapping. The caller may, for example, want to just <b>append</b> parameters (i.e. list of files ?) and |
| * completely replace (default) VM parameters which may be unsuitable for the operation: |
| * {@snippet file="org/netbeans/api/extexecution/base/ExplicitProcessParametersTest.java" region="testDiscardDefaultVMParametersAppendAppParameters"} |
| * <p> |
| * Note that multiple {@code ExplicitProcessParameters} instances may be added to the Lookup, acting as append or replacement |
| * for the parameters collected so far. |
| * </div> |
| * @author sdedic |
| * @since 1.16 |
| */ |
| public final class ExplicitProcessParameters { |
| final int position; |
| private final List<String> launcherArguments; |
| private final List<String> arguments; |
| private final boolean replaceArgs; |
| private final boolean replaceLauncherArgs; |
| private final File workingDirectory; |
| private final Map<String, String> environmentVars; |
| |
| private ExplicitProcessParameters(int position, List<String> launcherArguments, |
| List<String> arguments, boolean appendArgs, boolean appendLauncherArgs, |
| File workingDirectory, Map<String, String> environmentVars) { |
| this.position = position; |
| this.launcherArguments = launcherArguments == null ? null : Collections.unmodifiableList(launcherArguments); |
| this.arguments = arguments == null ? null : Collections.unmodifiableList(arguments); |
| this.replaceArgs = appendArgs; |
| this.replaceLauncherArgs = appendLauncherArgs; |
| this.workingDirectory = workingDirectory; |
| this.environmentVars = environmentVars == null ? null : Collections.unmodifiableMap(environmentVars); |
| } |
| |
| private static final ExplicitProcessParameters EMPTY = new ExplicitProcessParameters(0, null, null, false, false, null, null); |
| |
| /** |
| * Returns an empty instance of parameters that has no effect. DO NOT check for emptiness by |
| * equality or reference using the instance; use {@link #isEmpty()}. |
| * @return empty instance. |
| */ |
| public static ExplicitProcessParameters empty() { |
| return EMPTY; |
| } |
| |
| /** |
| * Returns true, if the instance has no effect when {@link Builder#combine}d onto base parameters. |
| * @return true, if no effect is expected. |
| */ |
| public boolean isEmpty() { |
| boolean change = false; |
| if (isArgReplacement() || isLauncherArgReplacement()) { |
| return false; |
| } |
| return ((arguments == null) || arguments.isEmpty()) && |
| (launcherArguments == null || launcherArguments.isEmpty()) && |
| workingDirectory == null && |
| (environmentVars == null || environmentVars.isEmpty()); |
| } |
| |
| /** |
| * Returns the arguments to be passed. Returns {@code null} if the object does not |
| * want to alter the argument list. |
| * @return arguments to be passed or {@code null} if the argument list should not be altered. |
| */ |
| public List<String> getArguments() { |
| return arguments; |
| } |
| |
| /** |
| * Returns the launcher arguments to be passed. Returns {@code null} if the object does not |
| * want to alter the argument list. |
| * @return arguments to be passed or {@code null} if the launcher argument list should not be altered. |
| */ |
| public List<String> getLauncherArguments() { |
| return launcherArguments; |
| } |
| |
| /** |
| * Instructs to replace arguments collected so far. |
| * @return true, if arguments collected should be discarded. |
| */ |
| public boolean isArgReplacement() { |
| return replaceArgs; |
| } |
| |
| /** |
| * Instructs to replace launcher arguments collected so far. |
| * @return true, if launcher arguments collected should be discarded. |
| */ |
| public boolean isLauncherArgReplacement() { |
| return replaceLauncherArgs; |
| } |
| |
| /** |
| * Returns the argument lists merged. Launcher arguments (if any) are passed first, followed |
| * by {@code middle} (if any), then (normal) arguments. The method is a convenience to build |
| * a complete command line for the launcher + command + command arguments. |
| * @return combined arguments. |
| */ |
| public @NonNull List<String> getAllArguments(List<String> middle) { |
| List<String> a = new ArrayList<>(); |
| if (launcherArguments != null) { |
| a.addAll(launcherArguments); |
| } |
| if (middle != null && !middle.isEmpty()) { |
| a.addAll(middle); |
| } |
| if (arguments != null) { |
| a.addAll(arguments); |
| } |
| return a; |
| } |
| |
| /** |
| * Returns the argument lists merged. Launcher arguments (if any) are passed first, followed |
| * by {@code middle} (if any), then (normal) arguments. The method is a convenience to build |
| * a complete command line for the launcher + command + command arguments. |
| * @return combined arguments. |
| */ |
| public @NonNull List<String> getAllArguments(@NullAllowed String... middle) { |
| return getAllArguments(middle == null ? Collections.emptyList() : Arrays.asList(middle)); |
| } |
| |
| /** |
| * Returns working directory to be set for the process. |
| * |
| * @return working directory, or <code>nul</code> |
| * @since 1.20 |
| */ |
| public @CheckForNull File getWorkingDirectory() { |
| return workingDirectory; |
| } |
| |
| /** |
| * Returns a map of additional environment variables to be set for the process. |
| * Always non-null. Values of existing environment variables are overridden. |
| * A <code>null</code> value of a variable should be interpreted as a removal |
| * of that variable from the environment. |
| * |
| * @return map of additional environment variables |
| * @since 1.20 |
| */ |
| public @NonNull Map<String, String> getEnvironmentVariables() { |
| return environmentVars != null ? environmentVars : Collections.emptyMap(); |
| } |
| |
| /** |
| * Merges ExplicitProcessParameters instructions found in the Lookup. See {@link #buildExplicitParameters(java.util.Collection)} |
| * for more details. |
| * @param context context for the execution |
| * @return merged instructions |
| */ |
| @NonNull |
| public static ExplicitProcessParameters buildExplicitParameters(Lookup context) { |
| return buildExplicitParameters(context.lookupAll(ExplicitProcessParameters.class)); |
| } |
| |
| /** |
| * Merges individual instruction. |
| * This method serves as a convenience and uniform ("standard") methods to merge argument lists for process execution. Should be used |
| * whenever a process (build, run, tool, ...) is executed. If the feature diverges, it should document how it processes the |
| * {@link ExplicitProcessParameters}. It is <b>strongly recommended</b> to support explicit parameters in order to allow for |
| * customizations and automation. |
| * <p> |
| * Processes instructions in the order of {@link Builder#position(int)} and appearance. Whenever an item is flagged as |
| * a replacement, all arguments (launcher arguments) collected to that point are discarded. Item's arguments (launcher arguments) |
| * will become the only ones listed. |
| * <p> |
| * <i>Note:</i> if a replacement instruction and all the following (if any) have {@link #getArguments()} {@code null} (= no change), |
| * the result will report <b>no change</b>. It is therefore possible to <b>discard all contributions</b> by appending a no-change replacement |
| * last. |
| * <p> |
| * Environment variables are overridden by newly set variables. |
| * |
| * @param items individual instructions. |
| * @return combined instructions. |
| */ |
| public static ExplicitProcessParameters buildExplicitParameters(Collection<? extends ExplicitProcessParameters> items) { |
| List<? extends ExplicitProcessParameters> all = new ArrayList<>(items); |
| Collections.sort(all, (a, b) -> a.position - b.position); |
| Builder b = builder(); |
| for (ExplicitProcessParameters item : all) { |
| b.combine(item); |
| } |
| return b.build(); |
| } |
| |
| public static Builder builder() { |
| return new Builder(); |
| } |
| |
| /** |
| * Builds the {@link ExplicitProcessParameters} instance. The builder initially: |
| * <ul> |
| * <li><b>appends</b> launcher arguments |
| * <li><b>replaces</b> (normal) arguments |
| * </ul> |
| * and the mode can be overridden for each group. |
| */ |
| public static final class Builder { |
| private int position = 0; |
| private List<String> launcherArguments = null; |
| private List<String> arguments = null; |
| private Boolean replaceArgs; |
| private Boolean replaceLauncherArgs; |
| private File workingDirectory = null; |
| private Map<String, String> environmentVars; |
| |
| private void initArgs() { |
| if (arguments == null) { |
| arguments = new ArrayList<>(); |
| } |
| } |
| |
| /** |
| * Appends a single argument. {@code null} is ignored. |
| * @param a argument |
| * @return the builder |
| */ |
| public Builder arg(@NullAllowed String a) { |
| if (a == null) { |
| return this; |
| } |
| initArgs(); |
| arguments.add(a); |
| return this; |
| } |
| |
| /** |
| * Appends arguments in the list. {@code null} is ignored as well as {@code null} |
| * items in the list. |
| * @param args argument list |
| * @return the builder |
| */ |
| public Builder args(@NullAllowed List<String> args) { |
| if (args == null) { |
| return this; |
| } |
| // init even if the list is empty. |
| initArgs(); |
| args.forEach(this::arg); |
| return this; |
| } |
| |
| /** |
| * Appends arguments in the list. {@code null} is ignored as well as {@code null} |
| * items in the list. |
| * @param args argument list |
| * @return the builder |
| */ |
| public Builder args(@NullAllowed String... args) { |
| if (args == null) { |
| return this; |
| } |
| return args(Arrays.asList(args)); |
| } |
| |
| private void initLauncherArgs() { |
| if (launcherArguments == null) { |
| launcherArguments = new ArrayList<>(); |
| } |
| } |
| |
| /** |
| * Appends a single launcher argument. {@code null} is ignored. |
| * @param a launcher argument |
| * @return the builder |
| */ |
| public Builder launcherArg(@NullAllowed String a) { |
| if (a == null) { |
| return this; |
| } |
| initLauncherArgs(); |
| launcherArguments.add(a); |
| return this; |
| } |
| |
| /** |
| * Appends arguments in the list. {@code null} is ignored as well as {@code null} |
| * items in the list. |
| * @param args argument list |
| * @return the builder |
| */ |
| public Builder launcherArgs(@NullAllowed List<String> args) { |
| if (args == null) { |
| return this; |
| } |
| initLauncherArgs(); |
| args.forEach(this::launcherArg); |
| return this; |
| } |
| |
| /** |
| * Appends arguments in the list. {@code null} is ignored as well as {@code null} |
| * items in the list. |
| * @param args argument list |
| * @return the builder |
| */ |
| public Builder launcherArgs(@NullAllowed String... args) { |
| if (args == null) { |
| return this; |
| } |
| return launcherArgs(Arrays.asList(args)); |
| } |
| |
| /** |
| * Changes the combining mode for args. Setting to true instructs |
| * that all arguments that may precede should be discarded and the |
| * arguments provided by the built {@link ExplicitProcessParameters} are the only |
| * ones passed to the process. |
| * @param replace true to replace, false to append |
| * @return the builder |
| */ |
| public Builder replaceArgs(boolean replace) { |
| this.replaceArgs = replace; |
| return this; |
| } |
| |
| /** |
| * Changes the combining mode for launcher args. Setting to true instructs |
| * that all arguments that may precede should be discarded and the |
| * launcher arguments provided by the built {@link ExplicitProcessParameters} are the only |
| * ones passed to the process. |
| * @param replace true to replace, false to append |
| * @return the builder |
| */ |
| public Builder replaceLauncherArgs(boolean replace) { |
| this.replaceLauncherArgs = replace; |
| return this; |
| } |
| |
| /** |
| * Sets working directory to be used for the process. |
| * |
| * @param workingDirectory the working directory |
| * @return the builder |
| * @since 1.20 |
| */ |
| public Builder workingDirectory(File workingDirectory) { |
| this.workingDirectory = workingDirectory; |
| return this; |
| } |
| |
| /** |
| * Provide additional environment variables for the process. Values of |
| * existing environment variables are overridden. <code>null</code> values |
| * are interpreted as removal of the respective variables from the environment. |
| * |
| * @param env a map of additional environment variables |
| * @return the builder |
| * @since 1.20 |
| */ |
| public Builder environmentVariables(Map<String, String> env) { |
| if (!env.isEmpty()) { |
| if (this.environmentVars == null) { |
| this.environmentVars = new HashMap<>(); |
| } |
| this.environmentVars.putAll(env); |
| } |
| return this; |
| } |
| |
| /** |
| * Provide an additional environment variable for the process. If the variable |
| * already exists, it's overridden with the new value. |
| * |
| * @param name name of the environment variable |
| * @param value value of the environment variable, or <code>null</code> in which case an existing variable is to be removed. |
| * @return the builder |
| * @since 1.20 |
| */ |
| public Builder environmentVariable(String name, String value) { |
| if (this.environmentVars == null) { |
| this.environmentVars = new HashMap<>(); |
| } |
| this.environmentVars.put(name, value); |
| return this; |
| } |
| |
| /** |
| * Defines a position for combining. The default rank is {@code 0}. When used in a collection in |
| * {@link ExplicitProcessParameters#buildExplicitParameters(java.util.Collection)}, instances are sorted |
| * by their position, in ascending order (lowest first). |
| * |
| * @param position rank of the instruction |
| * @return the builder |
| */ |
| public Builder position(int position) { |
| this.position = position; |
| return this; |
| } |
| |
| /** |
| * Apply {@link ExplicitProcessParameters} on top of this Builder's state. |
| * It will merge in the passed instruction as described in {@link ExplicitProcessParameters#buildExplicitParameters(java.util.Collection)}. |
| * |
| * @param p the instruction to combine |
| * @return the modified builder |
| */ |
| public Builder combine(@NullAllowed ExplicitProcessParameters p) { |
| if (p == null) { |
| return this; |
| } |
| if (p.isLauncherArgReplacement()) { |
| launcherArguments = null; |
| if (p.getLauncherArguments() != null) { |
| replaceLauncherArgs = true; |
| } else { |
| replaceLauncherArgs = null; |
| } |
| } |
| if (p.isArgReplacement()) { |
| arguments = null; |
| if (p.getArguments() != null) { |
| replaceArgs = true; |
| } else { |
| replaceArgs = null; |
| } |
| } |
| if (p.getLauncherArguments() != null) { |
| launcherArgs(p.getLauncherArguments()); |
| } |
| if (p.getArguments() != null) { |
| args(p.getArguments()); |
| } |
| if (p.getWorkingDirectory() != null) { |
| workingDirectory(p.getWorkingDirectory()); |
| } |
| if (!p.getEnvironmentVariables().isEmpty()) { |
| environmentVariables(p.getEnvironmentVariables()); |
| } |
| return this; |
| } |
| |
| /** |
| * Produces the {@link ExplicitProcessParameters} instruction. |
| * @return the {@link ExplicitProcessParameters} instance. |
| */ |
| public ExplicitProcessParameters build() { |
| boolean aa = replaceArgs != null ? replaceArgs : arguments != null; |
| boolean apa = replaceLauncherArgs != null ? replaceLauncherArgs : false; |
| |
| return new ExplicitProcessParameters(position, launcherArguments, arguments, |
| // if no args / launcher args given and no explicit instruction on append, |
| // make the args appending. |
| aa, apa, workingDirectory, environmentVars); |
| } |
| } |
| } |