This is an automated email from the ASF dual-hosted git repository. ggregory pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/commons-exec.git
The following commit(s) were added to refs/heads/master by this push: new 8186ee3 Simplify 8186ee3 is described below commit 8186ee338f01a1fd90028de88638032cc2dbea64 Author: Gary Gregory <garydgreg...@gmail.com> AuthorDate: Sat Jul 9 08:35:46 2022 -0400 Simplify --- .../java/org/apache/commons/exec/CommandLine.java | 880 ++++++++++----------- 1 file changed, 436 insertions(+), 444 deletions(-) diff --git a/src/main/java/org/apache/commons/exec/CommandLine.java b/src/main/java/org/apache/commons/exec/CommandLine.java index d2da131..9df00b1 100644 --- a/src/main/java/org/apache/commons/exec/CommandLine.java +++ b/src/main/java/org/apache/commons/exec/CommandLine.java @@ -1,444 +1,436 @@ -/* - * 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.apache.commons.exec; - -import java.io.File; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; -import java.util.StringTokenizer; -import java.util.Vector; - -import org.apache.commons.exec.util.StringUtils; - -/** - * CommandLine objects help handling command lines specifying processes to - * execute. The class can be used to a command line by an application. - * - */ -public class CommandLine { - - /** - * The arguments of the command. - */ - private final Vector<Argument> arguments = new Vector<>(); - - /** - * The program to execute. - */ - private final String executable; - - /** - * A map of name value pairs used to expand command line arguments - */ - private Map<String, ?> substitutionMap; // N.B. This can contain values other than Strings - - /** - * Was a file being used to set the executable? - */ - private final boolean isFile; - - /** - * Create a command line from a string. - * - * @param line the first element becomes the executable, the rest the arguments - * @return the parsed command line - * @throws IllegalArgumentException If line is null or all whitespace - */ - public static CommandLine parse(final String line) { - return parse(line, null); - } - - /** - * Create a command line from a string. - * - * @param line the first element becomes the executable, the rest the arguments - * @param substitutionMap the name/value pairs used for substitution - * @return the parsed command line - * @throws IllegalArgumentException If line is null or all whitespace - */ - public static CommandLine parse(final String line, final Map<String, ?> substitutionMap) { - - if (line == null) { - throw new IllegalArgumentException("Command line can not be null"); - } - if (line.trim().isEmpty()) { - throw new IllegalArgumentException("Command line can not be empty"); - } - final String[] tmp = translateCommandline(line); - - final CommandLine cl = new CommandLine(tmp[0]); - cl.setSubstitutionMap(substitutionMap); - for (int i = 1; i < tmp.length; i++) { - cl.addArgument(tmp[i]); - } - - return cl; - } - - /** - * Create a command line without any arguments. - * - * @param executable the executable - */ - public CommandLine(final String executable) { - this.isFile=false; - this.executable=toCleanExecutable(executable); - } - - /** - * Create a command line without any arguments. - * - * @param executable the executable file - */ - public CommandLine(final File executable) { - this.isFile=true; - this.executable=toCleanExecutable(executable.getAbsolutePath()); - } - - /** - * Copy constructor. - * - * @param other the instance to copy - */ - public CommandLine(final CommandLine other) - { - this.executable = other.getExecutable(); - this.isFile = other.isFile(); - this.arguments.addAll(other.arguments); - - if (other.getSubstitutionMap() != null) - { - final Map<String, Object> omap = new HashMap<>(); - this.substitutionMap = omap; - final Iterator<String> iterator = other.substitutionMap.keySet().iterator(); - while (iterator.hasNext()) - { - final String key = iterator.next(); - omap.put(key, other.getSubstitutionMap().get(key)); - } - } - } - - /** - * Returns the executable. - * - * @return The executable - */ - public String getExecutable() { - // Expand the executable and replace '/' and '\\' with the platform - // specific file separator char. This is safe here since we know - // that this is a platform specific command. - return StringUtils.fixFileSeparatorChar(expandArgument(executable)); - } - - /** - * Was a file being used to set the executable? - * - * @return true if a file was used for setting the executable - */ - public boolean isFile() { - return isFile; - } - - /** - * Add multiple arguments. Handles parsing of quotes and whitespace. - * - * @param addArguments An array of arguments - * @return The command line itself - */ - public CommandLine addArguments(final String[] addArguments) { - return this.addArguments(addArguments, true); - } - - /** - * Add multiple arguments. - * - * @param addArguments An array of arguments - * @param handleQuoting Add the argument with/without handling quoting - * @return The command line itself - */ - public CommandLine addArguments(final String[] addArguments, final boolean handleQuoting) { - if (addArguments != null) { - for (final String addArgument : addArguments) { - addArgument(addArgument, handleQuoting); - } - } - - return this; - } - - /** - * Add multiple arguments. Handles parsing of quotes and whitespace. - * Please note that the parsing can have undesired side-effects therefore - * it is recommended to build the command line incrementally. - * - * @param addArguments An string containing multiple arguments. - * @return The command line itself - */ - public CommandLine addArguments(final String addArguments) { - return this.addArguments(addArguments, true); - } - - /** - * Add multiple arguments. Handles parsing of quotes and whitespace. - * Please note that the parsing can have undesired side-effects therefore - * it is recommended to build the command line incrementally. - * - * @param addArguments An string containing multiple arguments. - * @param handleQuoting Add the argument with/without handling quoting - * @return The command line itself - */ - public CommandLine addArguments(final String addArguments, final boolean handleQuoting) { - if (addArguments != null) { - final String[] argumentsArray = translateCommandline(addArguments); - addArguments(argumentsArray, handleQuoting); - } - - return this; - } - - /** - * Add a single argument. Handles quoting. - * - * @param argument The argument to add - * @return The command line itself - * @throws IllegalArgumentException If argument contains both single and double quotes - */ - public CommandLine addArgument(final String argument) { - return this.addArgument(argument, true); - } - - /** - * Add a single argument. - * - * @param argument The argument to add - * @param handleQuoting Add the argument with/without handling quoting - * @return The command line itself - */ - public CommandLine addArgument(final String argument, final boolean handleQuoting) { - - if (argument == null) - { - return this; - } - - // check if we can really quote the argument - if not throw an - // IllegalArgumentException - if (handleQuoting) - { - StringUtils.quoteArgument(argument); - } - - arguments.add(new Argument(argument, handleQuoting)); - return this; - } - - /** - * Returns the expanded and quoted command line arguments. - * - * @return The quoted arguments - */ - public String[] getArguments() { - - Argument currArgument; - String expandedArgument; - final String[] result = new String[arguments.size()]; - - for (int i=0; i<result.length; i++) { - currArgument = arguments.get(i); - expandedArgument = expandArgument(currArgument.getValue()); - result[i] = currArgument.isHandleQuoting() ? StringUtils.quoteArgument(expandedArgument) : expandedArgument; - } - - return result; - } - - /** - * @return the substitution map - */ - public Map<String, ?> getSubstitutionMap() { - return substitutionMap; - } - - /** - * Set the substitutionMap to expand variables in the - * command line. - * - * @param substitutionMap the map - */ - public void setSubstitutionMap(final Map<String, ?> substitutionMap) { - this.substitutionMap = substitutionMap; - } - - /** - * Returns the command line as an array of strings. - * - * @return The command line as an string array - */ - public String[] toStrings() { - final String[] result = new String[arguments.size() + 1]; - result[0] = this.getExecutable(); - System.arraycopy(getArguments(), 0, result, 1, result.length-1); - return result; - } - - /** - * Stringify operator returns the command line as a string. - * Parameters are correctly quoted when containing a space or - * left untouched if the are already quoted. - * - * @return the command line as single string - */ - @Override - public String toString() { - return "[" + StringUtils.toString(toStrings(), ", ") + "]"; - } - - // --- Implementation --------------------------------------------------- - - /** - * Expand variables in a command line argument. - * - * @param argument the argument - * @return the expanded string - */ - private String expandArgument(final String argument) { - final StringBuffer stringBuffer = StringUtils.stringSubstitution(argument, this.getSubstitutionMap(), true); - return stringBuffer.toString(); - } - - /** - * Crack a command line. - * - * @param toProcess - * the command line to process - * @return the command line broken into strings. An empty or null toProcess - * parameter results in a zero sized array - */ - private static String[] translateCommandline(final String toProcess) { - if (toProcess == null || toProcess.isEmpty()) { - // no command? no string - return new String[0]; - } - - // parse with a simple finite state machine - - final int normal = 0; - final int inQuote = 1; - final int inDoubleQuote = 2; - int state = normal; - final StringTokenizer tok = new StringTokenizer(toProcess, "\"\' ", true); - final ArrayList<String> list = new ArrayList<>(); - StringBuilder current = new StringBuilder(); - boolean lastTokenHasBeenQuoted = false; - - while (tok.hasMoreTokens()) { - final String nextTok = tok.nextToken(); - switch (state) { - case inQuote: - if ("\'".equals(nextTok)) { - lastTokenHasBeenQuoted = true; - state = normal; - } else { - current.append(nextTok); - } - break; - case inDoubleQuote: - if ("\"".equals(nextTok)) { - lastTokenHasBeenQuoted = true; - state = normal; - } else { - current.append(nextTok); - } - break; - default: - if ("\'".equals(nextTok)) { - state = inQuote; - } else if ("\"".equals(nextTok)) { - state = inDoubleQuote; - } else if (" ".equals(nextTok)) { - if (lastTokenHasBeenQuoted || current.length() != 0) { - list.add(current.toString()); - current = new StringBuilder(); - } - } else { - current.append(nextTok); - } - lastTokenHasBeenQuoted = false; - break; - } - } - - if (lastTokenHasBeenQuoted || current.length() != 0) { - list.add(current.toString()); - } - - if (state == inQuote || state == inDoubleQuote) { - throw new IllegalArgumentException("Unbalanced quotes in " - + toProcess); - } - - final String[] args = new String[list.size()]; - return list.toArray(args); - } - - /** - * Cleans the executable string. The argument is trimmed and '/' and '\\' are - * replaced with the platform specific file separator char - * - * @param dirtyExecutable the executable - * @return the platform-specific executable string - */ - private String toCleanExecutable(final String dirtyExecutable) { - if (dirtyExecutable == null) { - throw new IllegalArgumentException("Executable can not be null"); - } - if (dirtyExecutable.trim().isEmpty()) { - throw new IllegalArgumentException("Executable can not be empty"); - } - return StringUtils.fixFileSeparatorChar(dirtyExecutable); - } - - /** - * Encapsulates a command line argument. - */ - class Argument { - - private final String value; - private final boolean handleQuoting; - - private Argument(final String value, final boolean handleQuoting) - { - this.value = value.trim(); - this.handleQuoting = handleQuoting; - } - - private String getValue() - { - return value; - } - - private boolean isHandleQuoting() - { - return handleQuoting; - } - } -} +/* + * 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.apache.commons.exec; + +import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; +import java.util.StringTokenizer; +import java.util.Vector; + +import org.apache.commons.exec.util.StringUtils; + +/** + * CommandLine objects help handling command lines specifying processes to + * execute. The class can be used to a command line by an application. + * + */ +public class CommandLine { + + /** + * The arguments of the command. + */ + private final Vector<Argument> arguments = new Vector<>(); + + /** + * The program to execute. + */ + private final String executable; + + /** + * A map of name value pairs used to expand command line arguments + */ + private Map<String, ?> substitutionMap; // N.B. This can contain values other than Strings + + /** + * Was a file being used to set the executable? + */ + private final boolean isFile; + + /** + * Create a command line from a string. + * + * @param line the first element becomes the executable, the rest the arguments + * @return the parsed command line + * @throws IllegalArgumentException If line is null or all whitespace + */ + public static CommandLine parse(final String line) { + return parse(line, null); + } + + /** + * Create a command line from a string. + * + * @param line the first element becomes the executable, the rest the arguments + * @param substitutionMap the name/value pairs used for substitution + * @return the parsed command line + * @throws IllegalArgumentException If line is null or all whitespace + */ + public static CommandLine parse(final String line, final Map<String, ?> substitutionMap) { + + if (line == null) { + throw new IllegalArgumentException("Command line can not be null"); + } + if (line.trim().isEmpty()) { + throw new IllegalArgumentException("Command line can not be empty"); + } + final String[] tmp = translateCommandline(line); + + final CommandLine cl = new CommandLine(tmp[0]); + cl.setSubstitutionMap(substitutionMap); + for (int i = 1; i < tmp.length; i++) { + cl.addArgument(tmp[i]); + } + + return cl; + } + + /** + * Create a command line without any arguments. + * + * @param executable the executable + */ + public CommandLine(final String executable) { + this.isFile=false; + this.executable=toCleanExecutable(executable); + } + + /** + * Create a command line without any arguments. + * + * @param executable the executable file + */ + public CommandLine(final File executable) { + this.isFile=true; + this.executable=toCleanExecutable(executable.getAbsolutePath()); + } + + /** + * Copy constructor. + * + * @param other the instance to copy + */ + public CommandLine(final CommandLine other) + { + this.executable = other.getExecutable(); + this.isFile = other.isFile(); + this.arguments.addAll(other.arguments); + + if (other.getSubstitutionMap() != null) + { + this.substitutionMap = new HashMap<>(other.getSubstitutionMap()); + } + } + + /** + * Returns the executable. + * + * @return The executable + */ + public String getExecutable() { + // Expand the executable and replace '/' and '\\' with the platform + // specific file separator char. This is safe here since we know + // that this is a platform specific command. + return StringUtils.fixFileSeparatorChar(expandArgument(executable)); + } + + /** + * Was a file being used to set the executable? + * + * @return true if a file was used for setting the executable + */ + public boolean isFile() { + return isFile; + } + + /** + * Add multiple arguments. Handles parsing of quotes and whitespace. + * + * @param addArguments An array of arguments + * @return The command line itself + */ + public CommandLine addArguments(final String[] addArguments) { + return this.addArguments(addArguments, true); + } + + /** + * Add multiple arguments. + * + * @param addArguments An array of arguments + * @param handleQuoting Add the argument with/without handling quoting + * @return The command line itself + */ + public CommandLine addArguments(final String[] addArguments, final boolean handleQuoting) { + if (addArguments != null) { + for (final String addArgument : addArguments) { + addArgument(addArgument, handleQuoting); + } + } + + return this; + } + + /** + * Add multiple arguments. Handles parsing of quotes and whitespace. + * Please note that the parsing can have undesired side-effects therefore + * it is recommended to build the command line incrementally. + * + * @param addArguments An string containing multiple arguments. + * @return The command line itself + */ + public CommandLine addArguments(final String addArguments) { + return this.addArguments(addArguments, true); + } + + /** + * Add multiple arguments. Handles parsing of quotes and whitespace. + * Please note that the parsing can have undesired side-effects therefore + * it is recommended to build the command line incrementally. + * + * @param addArguments An string containing multiple arguments. + * @param handleQuoting Add the argument with/without handling quoting + * @return The command line itself + */ + public CommandLine addArguments(final String addArguments, final boolean handleQuoting) { + if (addArguments != null) { + final String[] argumentsArray = translateCommandline(addArguments); + addArguments(argumentsArray, handleQuoting); + } + + return this; + } + + /** + * Add a single argument. Handles quoting. + * + * @param argument The argument to add + * @return The command line itself + * @throws IllegalArgumentException If argument contains both single and double quotes + */ + public CommandLine addArgument(final String argument) { + return this.addArgument(argument, true); + } + + /** + * Add a single argument. + * + * @param argument The argument to add + * @param handleQuoting Add the argument with/without handling quoting + * @return The command line itself + */ + public CommandLine addArgument(final String argument, final boolean handleQuoting) { + + if (argument == null) + { + return this; + } + + // check if we can really quote the argument - if not throw an + // IllegalArgumentException + if (handleQuoting) + { + StringUtils.quoteArgument(argument); + } + + arguments.add(new Argument(argument, handleQuoting)); + return this; + } + + /** + * Returns the expanded and quoted command line arguments. + * + * @return The quoted arguments + */ + public String[] getArguments() { + + Argument currArgument; + String expandedArgument; + final String[] result = new String[arguments.size()]; + + for (int i=0; i<result.length; i++) { + currArgument = arguments.get(i); + expandedArgument = expandArgument(currArgument.getValue()); + result[i] = currArgument.isHandleQuoting() ? StringUtils.quoteArgument(expandedArgument) : expandedArgument; + } + + return result; + } + + /** + * @return the substitution map + */ + public Map<String, ?> getSubstitutionMap() { + return substitutionMap; + } + + /** + * Set the substitutionMap to expand variables in the + * command line. + * + * @param substitutionMap the map + */ + public void setSubstitutionMap(final Map<String, ?> substitutionMap) { + this.substitutionMap = substitutionMap; + } + + /** + * Returns the command line as an array of strings. + * + * @return The command line as an string array + */ + public String[] toStrings() { + final String[] result = new String[arguments.size() + 1]; + result[0] = this.getExecutable(); + System.arraycopy(getArguments(), 0, result, 1, result.length-1); + return result; + } + + /** + * Stringify operator returns the command line as a string. + * Parameters are correctly quoted when containing a space or + * left untouched if the are already quoted. + * + * @return the command line as single string + */ + @Override + public String toString() { + return "[" + StringUtils.toString(toStrings(), ", ") + "]"; + } + + // --- Implementation --------------------------------------------------- + + /** + * Expand variables in a command line argument. + * + * @param argument the argument + * @return the expanded string + */ + private String expandArgument(final String argument) { + final StringBuffer stringBuffer = StringUtils.stringSubstitution(argument, this.getSubstitutionMap(), true); + return stringBuffer.toString(); + } + + /** + * Crack a command line. + * + * @param toProcess + * the command line to process + * @return the command line broken into strings. An empty or null toProcess + * parameter results in a zero sized array + */ + private static String[] translateCommandline(final String toProcess) { + if (toProcess == null || toProcess.isEmpty()) { + // no command? no string + return new String[0]; + } + + // parse with a simple finite state machine + + final int normal = 0; + final int inQuote = 1; + final int inDoubleQuote = 2; + int state = normal; + final StringTokenizer tok = new StringTokenizer(toProcess, "\"\' ", true); + final ArrayList<String> list = new ArrayList<>(); + StringBuilder current = new StringBuilder(); + boolean lastTokenHasBeenQuoted = false; + + while (tok.hasMoreTokens()) { + final String nextTok = tok.nextToken(); + switch (state) { + case inQuote: + if ("\'".equals(nextTok)) { + lastTokenHasBeenQuoted = true; + state = normal; + } else { + current.append(nextTok); + } + break; + case inDoubleQuote: + if ("\"".equals(nextTok)) { + lastTokenHasBeenQuoted = true; + state = normal; + } else { + current.append(nextTok); + } + break; + default: + if ("\'".equals(nextTok)) { + state = inQuote; + } else if ("\"".equals(nextTok)) { + state = inDoubleQuote; + } else if (" ".equals(nextTok)) { + if (lastTokenHasBeenQuoted || current.length() != 0) { + list.add(current.toString()); + current = new StringBuilder(); + } + } else { + current.append(nextTok); + } + lastTokenHasBeenQuoted = false; + break; + } + } + + if (lastTokenHasBeenQuoted || current.length() != 0) { + list.add(current.toString()); + } + + if (state == inQuote || state == inDoubleQuote) { + throw new IllegalArgumentException("Unbalanced quotes in " + + toProcess); + } + + final String[] args = new String[list.size()]; + return list.toArray(args); + } + + /** + * Cleans the executable string. The argument is trimmed and '/' and '\\' are + * replaced with the platform specific file separator char + * + * @param dirtyExecutable the executable + * @return the platform-specific executable string + */ + private String toCleanExecutable(final String dirtyExecutable) { + if (dirtyExecutable == null) { + throw new IllegalArgumentException("Executable can not be null"); + } + if (dirtyExecutable.trim().isEmpty()) { + throw new IllegalArgumentException("Executable can not be empty"); + } + return StringUtils.fixFileSeparatorChar(dirtyExecutable); + } + + /** + * Encapsulates a command line argument. + */ + class Argument { + + private final String value; + private final boolean handleQuoting; + + private Argument(final String value, final boolean handleQuoting) + { + this.value = value.trim(); + this.handleQuoting = handleQuoting; + } + + private String getValue() + { + return value; + } + + private boolean isHandleQuoting() + { + return handleQuoting; + } + } +}