Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* Copyright 2008-2019 Ping Identity Corporation
* All Rights Reserved.
*/
/*
* Copyright (C) 2008-2019 Ping Identity Corporation
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License (GPLv2 only)
* or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see .
*/
package com.unboundid.util.args;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.Serializable;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.unboundid.ldap.sdk.unboundidds.tools.ToolUtils;
import com.unboundid.util.CommandLineTool;
import com.unboundid.util.Debug;
import com.unboundid.util.ObjectPair;
import com.unboundid.util.StaticUtils;
import com.unboundid.util.ThreadSafety;
import com.unboundid.util.ThreadSafetyLevel;
import com.unboundid.util.Validator;
import static com.unboundid.util.args.ArgsMessages.*;
/**
* This class provides an argument parser, which may be used to process command
* line arguments provided to Java applications. See the package-level Javadoc
* documentation for details regarding the capabilities of the argument parser.
*/
@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
public final class ArgumentParser
implements Serializable
{
/**
* The name of the system property that can be used to specify the default
* properties file that should be used to obtain the default values for
* arguments not specified via the command line.
*/
public static final String PROPERTY_DEFAULT_PROPERTIES_FILE_PATH =
ArgumentParser.class.getName() + ".propertiesFilePath";
/**
* The name of an environment variable that can be used to specify the default
* properties file that should be used to obtain the default values for
* arguments not specified via the command line.
*/
public static final String ENV_DEFAULT_PROPERTIES_FILE_PATH =
"UNBOUNDID_TOOL_PROPERTIES_FILE_PATH";
/**
* The name of the argument used to specify the path to a file to which all
* output should be written.
*/
private static final String ARG_NAME_OUTPUT_FILE = "outputFile";
/**
* The name of the argument used to indicate that output should be written to
* both the output file and the console.
*/
private static final String ARG_NAME_TEE_OUTPUT = "teeOutput";
/**
* The name of the argument used to specify the path to a properties file from
* which to obtain the default values for arguments not specified via the
* command line.
*/
private static final String ARG_NAME_PROPERTIES_FILE_PATH =
"propertiesFilePath";
/**
* The name of the argument used to specify the path to a file to be generated
* with information about the properties that the tool supports.
*/
private static final String ARG_NAME_GENERATE_PROPERTIES_FILE =
"generatePropertiesFile";
/**
* The name of the argument used to indicate that the tool should not use any
* properties file to obtain default values for arguments not specified via
* the command line.
*/
private static final String ARG_NAME_NO_PROPERTIES_FILE = "noPropertiesFile";
/**
* The name of the argument used to indicate that the tool should suppress the
* comment that lists the argument values obtained from a properties file.
*/
private static final String ARG_NAME_SUPPRESS_PROPERTIES_FILE_COMMENT =
"suppressPropertiesFileComment";
/**
* The serial version UID for this serializable class.
*/
private static final long serialVersionUID = 3053102992180360269L;
// The command-line tool with which this argument parser is associated, if
// any.
private volatile CommandLineTool commandLineTool;
// The properties file used to obtain arguments for this tool.
private volatile File propertiesFileUsed;
// The maximum number of trailing arguments allowed to be provided.
private final int maxTrailingArgs;
// The minimum number of trailing arguments allowed to be provided.
private final int minTrailingArgs;
// The set of named arguments associated with this parser, indexed by short
// identifier.
private final LinkedHashMap namedArgsByShortID;
// The set of named arguments associated with this parser, indexed by long
// identifier.
private final LinkedHashMap namedArgsByLongID;
// The set of subcommands associated with this parser, indexed by name.
private final LinkedHashMap subCommandsByName;
// The full set of named arguments associated with this parser.
private final List namedArgs;
// Sets of arguments in which if the key argument is provided, then at least
// one of the value arguments must also be provided.
private final List>> dependentArgumentSets;
// Sets of arguments in which at most one argument in the list is allowed to
// be present.
private final List> exclusiveArgumentSets;
// Sets of arguments in which at least one argument in the list is required to
// be present.
private final List> requiredArgumentSets;
// A list of any arguments set from the properties file rather than explicitly
// provided on the command line.
private final List argumentsSetFromPropertiesFile;
// The list of trailing arguments provided on the command line.
private final List trailingArgs;
// The full list of subcommands associated with this argument parser.
private final List subCommands;
// A list of additional paragraphs that make up the complete description for
// the associated command.
private final List additionalCommandDescriptionParagraphs;
// The description for the associated command.
private final String commandDescription;
// The name for the associated command.
private final String commandName;
// The placeholder string for the trailing arguments.
private final String trailingArgsPlaceholder;
// The subcommand with which this argument parser is associated.
private volatile SubCommand parentSubCommand;
// The subcommand that was included in the set of command-line arguments.
private volatile SubCommand selectedSubCommand;
/**
* Creates a new instance of this argument parser with the provided
* information. It will not allow unnamed trailing arguments.
*
* @param commandName The name of the application or utility with
* which this argument parser is associated. It
* must not be {@code null}.
* @param commandDescription A description of the application or utility
* with which this argument parser is associated.
* It will be included in generated usage
* information. It must not be {@code null}.
*
* @throws ArgumentException If either the command name or command
* description is {@code null},
*/
public ArgumentParser(final String commandName,
final String commandDescription)
throws ArgumentException
{
this(commandName, commandDescription, 0, null);
}
/**
* Creates a new instance of this argument parser with the provided
* information.
*
* @param commandName The name of the application or utility
* with which this argument parser is
* associated. It must not be {@code null}.
* @param commandDescription A description of the application or
* utility with which this argument parser is
* associated. It will be included in
* generated usage information. It must not
* be {@code null}.
* @param maxTrailingArgs The maximum number of trailing arguments
* that may be provided to this command. A
* value of zero indicates that no trailing
* arguments will be allowed. A value less
* than zero will indicate that there is no
* limit on the number of trailing arguments
* allowed.
* @param trailingArgsPlaceholder A placeholder string that will be included
* in usage output to indicate what trailing
* arguments may be provided. It must not be
* {@code null} if {@code maxTrailingArgs} is
* anything other than zero.
*
* @throws ArgumentException If either the command name or command
* description is {@code null}, or if the maximum
* number of trailing arguments is non-zero and
* the trailing arguments placeholder is
* {@code null}.
*/
public ArgumentParser(final String commandName,
final String commandDescription,
final int maxTrailingArgs,
final String trailingArgsPlaceholder)
throws ArgumentException
{
this(commandName, commandDescription, 0, maxTrailingArgs,
trailingArgsPlaceholder);
}
/**
* Creates a new instance of this argument parser with the provided
* information.
*
* @param commandName The name of the application or utility
* with which this argument parser is
* associated. It must not be {@code null}.
* @param commandDescription A description of the application or
* utility with which this argument parser is
* associated. It will be included in
* generated usage information. It must not
* be {@code null}.
* @param minTrailingArgs The minimum number of trailing arguments
* that must be provided for this command. A
* value of zero indicates that the command
* may be invoked without any trailing
* arguments.
* @param maxTrailingArgs The maximum number of trailing arguments
* that may be provided to this command. A
* value of zero indicates that no trailing
* arguments will be allowed. A value less
* than zero will indicate that there is no
* limit on the number of trailing arguments
* allowed.
* @param trailingArgsPlaceholder A placeholder string that will be included
* in usage output to indicate what trailing
* arguments may be provided. It must not be
* {@code null} if {@code maxTrailingArgs} is
* anything other than zero.
*
* @throws ArgumentException If either the command name or command
* description is {@code null}, or if the maximum
* number of trailing arguments is non-zero and
* the trailing arguments placeholder is
* {@code null}.
*/
public ArgumentParser(final String commandName,
final String commandDescription,
final int minTrailingArgs,
final int maxTrailingArgs,
final String trailingArgsPlaceholder)
throws ArgumentException
{
this(commandName, commandDescription, null, minTrailingArgs,
maxTrailingArgs, trailingArgsPlaceholder);
}
/**
* Creates a new instance of this argument parser with the provided
* information.
*
* @param commandName
* The name of the application or utility with which this
* argument parser is associated. It must not be {@code null}.
* @param commandDescription
* A description of the application or utility with which this
* argument parser is associated. It will be included in
* generated usage information. It must not be {@code null}.
* @param additionalCommandDescriptionParagraphs
* A list of additional paragraphs that should be included in the
* tool description (with {@code commandDescription} providing
* the text for the first paragraph). This may be {@code null}
* or empty if the tool description should only include a
* single paragraph.
* @param minTrailingArgs
* The minimum number of trailing arguments that must be provided
* for this command. A value of zero indicates that the command
* may be invoked without any trailing arguments.
* @param maxTrailingArgs
* The maximum number of trailing arguments that may be provided
* to this command. A value of zero indicates that no trailing
* arguments will be allowed. A value less than zero will
* indicate that there is no limit on the number of trailing
* arguments allowed.
* @param trailingArgsPlaceholder
* A placeholder string that will be included in usage output to
* indicate what trailing arguments may be provided. It must not
* be {@code null} if {@code maxTrailingArgs} is anything other
* than zero.
*
* @throws ArgumentException If either the command name or command
* description is {@code null}, or if the maximum
* number of trailing arguments is non-zero and
* the trailing arguments placeholder is
* {@code null}.
*/
public ArgumentParser(final String commandName,
final String commandDescription,
final List additionalCommandDescriptionParagraphs,
final int minTrailingArgs, final int maxTrailingArgs,
final String trailingArgsPlaceholder)
throws ArgumentException
{
if (commandName == null)
{
throw new ArgumentException(ERR_PARSER_COMMAND_NAME_NULL.get());
}
if (commandDescription == null)
{
throw new ArgumentException(ERR_PARSER_COMMAND_DESCRIPTION_NULL.get());
}
if ((maxTrailingArgs != 0) && (trailingArgsPlaceholder == null))
{
throw new ArgumentException(
ERR_PARSER_TRAILING_ARGS_PLACEHOLDER_NULL.get());
}
this.commandName = commandName;
this.commandDescription = commandDescription;
this.trailingArgsPlaceholder = trailingArgsPlaceholder;
if (additionalCommandDescriptionParagraphs == null)
{
this.additionalCommandDescriptionParagraphs = Collections.emptyList();
}
else
{
this.additionalCommandDescriptionParagraphs =
Collections.unmodifiableList(
new ArrayList<>(additionalCommandDescriptionParagraphs));
}
if (minTrailingArgs >= 0)
{
this.minTrailingArgs = minTrailingArgs;
}
else
{
this.minTrailingArgs = 0;
}
if (maxTrailingArgs >= 0)
{
this.maxTrailingArgs = maxTrailingArgs;
}
else
{
this.maxTrailingArgs = Integer.MAX_VALUE;
}
if (this.minTrailingArgs > this.maxTrailingArgs)
{
throw new ArgumentException(ERR_PARSER_TRAILING_ARGS_COUNT_MISMATCH.get(
this.minTrailingArgs, this.maxTrailingArgs));
}
namedArgsByShortID =
new LinkedHashMap<>(StaticUtils.computeMapCapacity(20));
namedArgsByLongID = new LinkedHashMap<>(StaticUtils.computeMapCapacity(20));
namedArgs = new ArrayList<>(20);
trailingArgs = new ArrayList<>(20);
dependentArgumentSets = new ArrayList<>(20);
exclusiveArgumentSets = new ArrayList<>(20);
requiredArgumentSets = new ArrayList<>(20);
parentSubCommand = null;
selectedSubCommand = null;
subCommands = new ArrayList<>(20);
subCommandsByName = new LinkedHashMap<>(StaticUtils.computeMapCapacity(20));
propertiesFileUsed = null;
argumentsSetFromPropertiesFile = new ArrayList<>(20);
commandLineTool = null;
}
/**
* Creates a new argument parser that is a "clean" copy of the provided source
* argument parser.
*
* @param source The source argument parser to use for this argument
* parser.
* @param subCommand The subcommand with which this argument parser is to be
* associated.
*/
ArgumentParser(final ArgumentParser source, final SubCommand subCommand)
{
commandName = source.commandName;
commandDescription = source.commandDescription;
minTrailingArgs = source.minTrailingArgs;
maxTrailingArgs = source.maxTrailingArgs;
trailingArgsPlaceholder = source.trailingArgsPlaceholder;
additionalCommandDescriptionParagraphs =
source.additionalCommandDescriptionParagraphs;
propertiesFileUsed = null;
argumentsSetFromPropertiesFile = new ArrayList<>(20);
trailingArgs = new ArrayList<>(20);
namedArgs = new ArrayList<>(source.namedArgs.size());
namedArgsByLongID = new LinkedHashMap<>(
StaticUtils.computeMapCapacity(source.namedArgsByLongID.size()));
namedArgsByShortID = new LinkedHashMap<>(
StaticUtils.computeMapCapacity(source.namedArgsByShortID.size()));
final LinkedHashMap argsByID = new LinkedHashMap<>(
StaticUtils.computeMapCapacity(source.namedArgs.size()));
for (final Argument sourceArg : source.namedArgs)
{
final Argument a = sourceArg.getCleanCopy();
try
{
a.setRegistered();
}
catch (final ArgumentException ae)
{
// This should never happen.
Debug.debugException(ae);
}
namedArgs.add(a);
argsByID.put(a.getIdentifierString(), a);
for (final Character c : a.getShortIdentifiers(true))
{
namedArgsByShortID.put(c, a);
}
for (final String s : a.getLongIdentifiers(true))
{
namedArgsByLongID.put(StaticUtils.toLowerCase(s), a);
}
}
dependentArgumentSets =
new ArrayList<>(source.dependentArgumentSets.size());
for (final ObjectPair> p :
source.dependentArgumentSets)
{
final Set sourceSet = p.getSecond();
final LinkedHashSet newSet = new LinkedHashSet<>(
StaticUtils.computeMapCapacity(sourceSet.size()));
for (final Argument a : sourceSet)
{
newSet.add(argsByID.get(a.getIdentifierString()));
}
final Argument sourceFirst = p.getFirst();
final Argument newFirst = argsByID.get(sourceFirst.getIdentifierString());
dependentArgumentSets.add(
new ObjectPair>(newFirst, newSet));
}
exclusiveArgumentSets =
new ArrayList<>(source.exclusiveArgumentSets.size());
for (final Set sourceSet : source.exclusiveArgumentSets)
{
final LinkedHashSet newSet = new LinkedHashSet<>(
StaticUtils.computeMapCapacity(sourceSet.size()));
for (final Argument a : sourceSet)
{
newSet.add(argsByID.get(a.getIdentifierString()));
}
exclusiveArgumentSets.add(newSet);
}
requiredArgumentSets =
new ArrayList<>(source.requiredArgumentSets.size());
for (final Set sourceSet : source.requiredArgumentSets)
{
final LinkedHashSet newSet = new LinkedHashSet<>(
StaticUtils.computeMapCapacity(sourceSet.size()));
for (final Argument a : sourceSet)
{
newSet.add(argsByID.get(a.getIdentifierString()));
}
requiredArgumentSets.add(newSet);
}
parentSubCommand = subCommand;
selectedSubCommand = null;
subCommands = new ArrayList<>(source.subCommands.size());
subCommandsByName = new LinkedHashMap<>(
StaticUtils.computeMapCapacity(source.subCommandsByName.size()));
for (final SubCommand sc : source.subCommands)
{
subCommands.add(sc.getCleanCopy());
for (final String name : sc.getNames(true))
{
subCommandsByName.put(StaticUtils.toLowerCase(name), sc);
}
}
}
/**
* Retrieves the name of the application or utility with which this command
* line argument parser is associated.
*
* @return The name of the application or utility with which this command
* line argument parser is associated.
*/
public String getCommandName()
{
return commandName;
}
/**
* Retrieves a description of the application or utility with which this
* command line argument parser is associated. If the description should
* include multiple paragraphs, then this method will return the text for the
* first paragraph, and the
* {@link #getAdditionalCommandDescriptionParagraphs()} method should return a
* list with the text for all subsequent paragraphs.
*
* @return A description of the application or utility with which this
* command line argument parser is associated.
*/
public String getCommandDescription()
{
return commandDescription;
}
/**
* Retrieves a list containing the the text for all subsequent paragraphs to
* include in the description for the application or utility with which this
* command line argument parser is associated. If the description should have
* multiple paragraphs, then the {@link #getCommandDescription()} method will
* provide the text for the first paragraph and this method will provide the
* text for the subsequent paragraphs. If the description should only have a
* single paragraph, then the text of that paragraph should be returned by the
* {@code getCommandDescription} method, and this method will return an empty
* list.
*
* @return A list containing the text for all subsequent paragraphs to
* include in the description for the application or utility with
* which this command line argument parser is associated, or an empty
* list if the description should only include a single paragraph.
*/
public List getAdditionalCommandDescriptionParagraphs()
{
return additionalCommandDescriptionParagraphs;
}
/**
* Indicates whether this argument parser allows any unnamed trailing
* arguments to be provided.
*
* @return {@code true} if at least one unnamed trailing argument may be
* provided, or {@code false} if not.
*/
public boolean allowsTrailingArguments()
{
return (maxTrailingArgs != 0);
}
/**
* Indicates whether this argument parser requires at least unnamed trailing
* argument to be provided.
*
* @return {@code true} if at least one unnamed trailing argument must be
* provided, or {@code false} if the tool may be invoked without any
* such arguments.
*/
public boolean requiresTrailingArguments()
{
return (minTrailingArgs != 0);
}
/**
* Retrieves the placeholder string that will be provided in usage information
* to indicate what may be included in the trailing arguments.
*
* @return The placeholder string that will be provided in usage information
* to indicate what may be included in the trailing arguments, or
* {@code null} if unnamed trailing arguments are not allowed.
*/
public String getTrailingArgumentsPlaceholder()
{
return trailingArgsPlaceholder;
}
/**
* Retrieves the minimum number of unnamed trailing arguments that must be
* provided.
*
* @return The minimum number of unnamed trailing arguments that must be
* provided.
*/
public int getMinTrailingArguments()
{
return minTrailingArgs;
}
/**
* Retrieves the maximum number of unnamed trailing arguments that may be
* provided.
*
* @return The maximum number of unnamed trailing arguments that may be
* provided.
*/
public int getMaxTrailingArguments()
{
return maxTrailingArgs;
}
/**
* Updates this argument parser to enable support for a properties file that
* can be used to specify the default values for any properties that were not
* supplied via the command line. This method should be invoked after the
* argument parser has been configured with all of the other arguments that it
* supports and before the {@link #parse} method is invoked. In addition,
* after invoking the {@code parse} method, the caller must also invoke the
* {@link #getGeneratedPropertiesFile} method to determine if the only
* processing performed that should be performed is the generation of a
* properties file that will have already been performed.
*
* This method will update the argument parser to add the following additional
* arguments:
*
*
* {@code propertiesFilePath} -- Specifies the path to the properties file
* that should be used to obtain default values for any arguments not
* provided on the command line. If this is not specified and the
* {@code noPropertiesFile} argument is not present, then the argument
* parser may use a default properties file path specified using either
* the {@code com.unboundid.util.args.ArgumentParser..propertiesFilePath}
* system property or the {@code UNBOUNDID_TOOL_PROPERTIES_FILE_PATH}
* environment variable.
*
*
* {@code generatePropertiesFile} -- Indicates that the tool should
* generate a properties file for this argument parser and write it to the
* specified location. The generated properties file will not have any
* properties set, but will include comments that describe all of the
* supported arguments, as well general information about the use of a
* properties file. If this argument is specified on the command line,
* then no other arguments should be given.
*
*
* {@code noPropertiesFile} -- Indicates that the tool should not use a
* properties file to obtain default values for any arguments not provided
* on the command line.
*
*
*
* @throws ArgumentException If any of the arguments related to properties
* file processing conflicts with an argument that
* has already been added to the argument parser.
*/
public void enablePropertiesFileSupport()
throws ArgumentException
{
final FileArgument propertiesFilePath = new FileArgument(null,
ARG_NAME_PROPERTIES_FILE_PATH, false, 1, null,
INFO_ARG_DESCRIPTION_PROP_FILE_PATH.get(), true, true, true, false);
propertiesFilePath.setUsageArgument(true);
propertiesFilePath.addLongIdentifier("properties-file-path", true);
addArgument(propertiesFilePath);
final FileArgument generatePropertiesFile = new FileArgument(null,
ARG_NAME_GENERATE_PROPERTIES_FILE, false, 1, null,
INFO_ARG_DESCRIPTION_GEN_PROP_FILE.get(), false, true, true, false);
generatePropertiesFile.setUsageArgument(true);
generatePropertiesFile.addLongIdentifier("generate-properties-file", true);
addArgument(generatePropertiesFile);
final BooleanArgument noPropertiesFile = new BooleanArgument(null,
ARG_NAME_NO_PROPERTIES_FILE, INFO_ARG_DESCRIPTION_NO_PROP_FILE.get());
noPropertiesFile.setUsageArgument(true);
noPropertiesFile.addLongIdentifier("no-properties-file", true);
addArgument(noPropertiesFile);
final BooleanArgument suppressPropertiesFileComment = new BooleanArgument(
null, ARG_NAME_SUPPRESS_PROPERTIES_FILE_COMMENT, 1,
INFO_ARG_DESCRIPTION_SUPPRESS_PROP_FILE_COMMENT.get());
suppressPropertiesFileComment.setUsageArgument(true);
suppressPropertiesFileComment.addLongIdentifier(
"suppress-properties-file-comment", true);
addArgument(suppressPropertiesFileComment);
// The propertiesFilePath and noPropertiesFile arguments cannot be used
// together.
addExclusiveArgumentSet(propertiesFilePath, noPropertiesFile);
}
/**
* Indicates whether this argument parser was used to generate a properties
* file. If so, then the tool invoking the parser should return without
* performing any further processing.
*
* @return A {@code File} object that represents the path to the properties
* file that was generated, or {@code null} if no properties file was
* generated.
*/
public File getGeneratedPropertiesFile()
{
final Argument a = getNamedArgument(ARG_NAME_GENERATE_PROPERTIES_FILE);
if ((a == null) || (! a.isPresent()) || (! (a instanceof FileArgument)))
{
return null;
}
return ((FileArgument) a).getValue();
}
/**
* Retrieves the named argument with the specified short identifier.
*
* @param shortIdentifier The short identifier of the argument to retrieve.
* It must not be {@code null}.
*
* @return The named argument with the specified short identifier, or
* {@code null} if there is no such argument.
*/
public Argument getNamedArgument(final Character shortIdentifier)
{
Validator.ensureNotNull(shortIdentifier);
return namedArgsByShortID.get(shortIdentifier);
}
/**
* Retrieves the named argument with the specified identifier.
*
* @param identifier The identifier of the argument to retrieve. It may be
* the long identifier without any dashes, the short
* identifier character preceded by a single dash, or the
* long identifier preceded by two dashes. It must not be
* {@code null}.
*
* @return The named argument with the specified long identifier, or
* {@code null} if there is no such argument.
*/
public Argument getNamedArgument(final String identifier)
{
Validator.ensureNotNull(identifier);
if (identifier.startsWith("--") && (identifier.length() > 2))
{
return namedArgsByLongID.get(
StaticUtils.toLowerCase(identifier.substring(2)));
}
else if (identifier.startsWith("-") && (identifier.length() == 2))
{
return namedArgsByShortID.get(identifier.charAt(1));
}
else
{
return namedArgsByLongID.get(StaticUtils.toLowerCase(identifier));
}
}
/**
* Retrieves the argument list argument with the specified identifier.
*
* @param identifier The identifier of the argument to retrieve. It may be
* the long identifier without any dashes, the short
* identifier character preceded by a single dash, or the
* long identifier preceded by two dashes. It must not be
* {@code null}.
*
* @return The argument list argument with the specified identifier, or
* {@code null} if there is no such argument.
*/
public ArgumentListArgument getArgumentListArgument(final String identifier)
{
final Argument a = getNamedArgument(identifier);
if (a == null)
{
return null;
}
else
{
return (ArgumentListArgument) a;
}
}
/**
* Retrieves the Boolean argument with the specified identifier.
*
* @param identifier The identifier of the argument to retrieve. It may be
* the long identifier without any dashes, the short
* identifier character preceded by a single dash, or the
* long identifier preceded by two dashes. It must not be
* {@code null}.
*
* @return The Boolean argument with the specified identifier, or
* {@code null} if there is no such argument.
*/
public BooleanArgument getBooleanArgument(final String identifier)
{
final Argument a = getNamedArgument(identifier);
if (a == null)
{
return null;
}
else
{
return (BooleanArgument) a;
}
}
/**
* Retrieves the Boolean value argument with the specified identifier.
*
* @param identifier The identifier of the argument to retrieve. It may be
* the long identifier without any dashes, the short
* identifier character preceded by a single dash, or the
* long identifier preceded by two dashes. It must not be
* {@code null}.
*
* @return The Boolean value argument with the specified identifier, or
* {@code null} if there is no such argument.
*/
public BooleanValueArgument getBooleanValueArgument(final String identifier)
{
final Argument a = getNamedArgument(identifier);
if (a == null)
{
return null;
}
else
{
return (BooleanValueArgument) a;
}
}
/**
* Retrieves the control argument with the specified identifier.
*
* @param identifier The identifier of the argument to retrieve. It may be
* the long identifier without any dashes, the short
* identifier character preceded by a single dash, or the
* long identifier preceded by two dashes. It must not be
* {@code null}.
*
* @return The control argument with the specified identifier, or
* {@code null} if there is no such argument.
*/
public ControlArgument getControlArgument(final String identifier)
{
final Argument a = getNamedArgument(identifier);
if (a == null)
{
return null;
}
else
{
return (ControlArgument) a;
}
}
/**
* Retrieves the DN argument with the specified identifier.
*
* @param identifier The identifier of the argument to retrieve. It may be
* the long identifier without any dashes, the short
* identifier character preceded by a single dash, or the
* long identifier preceded by two dashes. It must not be
* {@code null}.
*
* @return The DN argument with the specified identifier, or
* {@code null} if there is no such argument.
*/
public DNArgument getDNArgument(final String identifier)
{
final Argument a = getNamedArgument(identifier);
if (a == null)
{
return null;
}
else
{
return (DNArgument) a;
}
}
/**
* Retrieves the duration argument with the specified identifier.
*
* @param identifier The identifier of the argument to retrieve. It may be
* the long identifier without any dashes, the short
* identifier character preceded by a single dash, or the
* long identifier preceded by two dashes. It must not be
* {@code null}.
*
* @return The duration argument with the specified identifier, or
* {@code null} if there is no such argument.
*/
public DurationArgument getDurationArgument(final String identifier)
{
final Argument a = getNamedArgument(identifier);
if (a == null)
{
return null;
}
else
{
return (DurationArgument) a;
}
}
/**
* Retrieves the file argument with the specified identifier.
*
* @param identifier The identifier of the argument to retrieve. It may be
* the long identifier without any dashes, the short
* identifier character preceded by a single dash, or the
* long identifier preceded by two dashes. It must not be
* {@code null}.
*
* @return The file argument with the specified identifier, or
* {@code null} if there is no such argument.
*/
public FileArgument getFileArgument(final String identifier)
{
final Argument a = getNamedArgument(identifier);
if (a == null)
{
return null;
}
else
{
return (FileArgument) a;
}
}
/**
* Retrieves the filter argument with the specified identifier.
*
* @param identifier The identifier of the argument to retrieve. It may be
* the long identifier without any dashes, the short
* identifier character preceded by a single dash, or the
* long identifier preceded by two dashes. It must not be
* {@code null}.
*
* @return The filter argument with the specified identifier, or
* {@code null} if there is no such argument.
*/
public FilterArgument getFilterArgument(final String identifier)
{
final Argument a = getNamedArgument(identifier);
if (a == null)
{
return null;
}
else
{
return (FilterArgument) a;
}
}
/**
* Retrieves the integer argument with the specified identifier.
*
* @param identifier The identifier of the argument to retrieve. It may be
* the long identifier without any dashes, the short
* identifier character preceded by a single dash, or the
* long identifier preceded by two dashes. It must not be
* {@code null}.
*
* @return The integer argument with the specified identifier, or
* {@code null} if there is no such argument.
*/
public IntegerArgument getIntegerArgument(final String identifier)
{
final Argument a = getNamedArgument(identifier);
if (a == null)
{
return null;
}
else
{
return (IntegerArgument) a;
}
}
/**
* Retrieves the scope argument with the specified identifier.
*
* @param identifier The identifier of the argument to retrieve. It may be
* the long identifier without any dashes, the short
* identifier character preceded by a single dash, or the
* long identifier preceded by two dashes. It must not be
* {@code null}.
*
* @return The scope argument with the specified identifier, or
* {@code null} if there is no such argument.
*/
public ScopeArgument getScopeArgument(final String identifier)
{
final Argument a = getNamedArgument(identifier);
if (a == null)
{
return null;
}
else
{
return (ScopeArgument) a;
}
}
/**
* Retrieves the string argument with the specified identifier.
*
* @param identifier The identifier of the argument to retrieve. It may be
* the long identifier without any dashes, the short
* identifier character preceded by a single dash, or the
* long identifier preceded by two dashes. It must not be
* {@code null}.
*
* @return The string argument with the specified identifier, or
* {@code null} if there is no such argument.
*/
public StringArgument getStringArgument(final String identifier)
{
final Argument a = getNamedArgument(identifier);
if (a == null)
{
return null;
}
else
{
return (StringArgument) a;
}
}
/**
* Retrieves the timestamp argument with the specified identifier.
*
* @param identifier The identifier of the argument to retrieve. It may be
* the long identifier without any dashes, the short
* identifier character preceded by a single dash, or the
* long identifier preceded by two dashes. It must not be
* {@code null}.
*
* @return The timestamp argument with the specified identifier, or
* {@code null} if there is no such argument.
*/
public TimestampArgument getTimestampArgument(final String identifier)
{
final Argument a = getNamedArgument(identifier);
if (a == null)
{
return null;
}
else
{
return (TimestampArgument) a;
}
}
/**
* Retrieves the set of named arguments defined for use with this argument
* parser.
*
* @return The set of named arguments defined for use with this argument
* parser.
*/
public List getNamedArguments()
{
return Collections.unmodifiableList(namedArgs);
}
/**
* Registers the provided argument with this argument parser.
*
* @param argument The argument to be registered.
*
* @throws ArgumentException If the provided argument conflicts with another
* argument already registered with this parser.
*/
public void addArgument(final Argument argument)
throws ArgumentException
{
argument.setRegistered();
for (final Character c : argument.getShortIdentifiers(true))
{
if (namedArgsByShortID.containsKey(c))
{
throw new ArgumentException(ERR_PARSER_SHORT_ID_CONFLICT.get(c));
}
if ((parentSubCommand != null) &&
(parentSubCommand.getArgumentParser().namedArgsByShortID.containsKey(
c)))
{
throw new ArgumentException(ERR_PARSER_SHORT_ID_CONFLICT.get(c));
}
}
for (final String s : argument.getLongIdentifiers(true))
{
if (namedArgsByLongID.containsKey(StaticUtils.toLowerCase(s)))
{
throw new ArgumentException(ERR_PARSER_LONG_ID_CONFLICT.get(s));
}
if ((parentSubCommand != null) &&
(parentSubCommand.getArgumentParser().namedArgsByLongID.containsKey(
StaticUtils.toLowerCase(s))))
{
throw new ArgumentException(ERR_PARSER_LONG_ID_CONFLICT.get(s));
}
}
for (final SubCommand sc : subCommands)
{
final ArgumentParser parser = sc.getArgumentParser();
for (final Character c : argument.getShortIdentifiers(true))
{
if (parser.namedArgsByShortID.containsKey(c))
{
throw new ArgumentException(
ERR_PARSER_SHORT_ID_CONFLICT_WITH_SUBCOMMAND.get(c,
sc.getPrimaryName()));
}
}
for (final String s : argument.getLongIdentifiers(true))
{
if (parser.namedArgsByLongID.containsKey(StaticUtils.toLowerCase(s)))
{
throw new ArgumentException(
ERR_PARSER_LONG_ID_CONFLICT_WITH_SUBCOMMAND.get(s,
sc.getPrimaryName()));
}
}
}
for (final Character c : argument.getShortIdentifiers(true))
{
namedArgsByShortID.put(c, argument);
}
for (final String s : argument.getLongIdentifiers(true))
{
namedArgsByLongID.put(StaticUtils.toLowerCase(s), argument);
}
namedArgs.add(argument);
}
/**
* Retrieves the list of dependent argument sets for this argument parser. If
* an argument contained as the first object in the pair in a dependent
* argument set is provided, then at least one of the arguments in the paired
* set must also be provided.
*
* @return The list of dependent argument sets for this argument parser.
*/
public List>> getDependentArgumentSets()
{
return Collections.unmodifiableList(dependentArgumentSets);
}
/**
* Adds the provided collection of arguments as dependent upon the given
* argument. All of the arguments must have already been registered with this
* argument parser using the {@link #addArgument} method.
*
* @param targetArgument The argument whose presence indicates that at
* least one of the dependent arguments must also
* be present. It must not be {@code null}, and
* it must have already been registered with this
* argument parser.
* @param dependentArguments The set of arguments from which at least one
* argument must be present if the target argument
* is present. It must not be {@code null} or
* empty, and all arguments must have already been
* registered with this argument parser.
*/
public void addDependentArgumentSet(final Argument targetArgument,
final Collection dependentArguments)
{
Validator.ensureNotNull(targetArgument, dependentArguments);
Validator.ensureFalse(dependentArguments.isEmpty(),
"The ArgumentParser.addDependentArgumentSet method must not be " +
"called with an empty collection of dependentArguments");
Validator.ensureTrue(namedArgs.contains(targetArgument),
"The ArgumentParser.addDependentArgumentSet method may only be used " +
"if all of the provided arguments have already been registered " +
"with the argument parser via the ArgumentParser.addArgument " +
"method. The " + targetArgument.getIdentifierString() +
" argument has not been registered with the argument parser.");
for (final Argument a : dependentArguments)
{
Validator.ensureTrue(namedArgs.contains(a),
"The ArgumentParser.addDependentArgumentSet method may only be " +
"used if all of the provided arguments have already been " +
"registered with the argument parser via the " +
"ArgumentParser.addArgument method. The " +
a.getIdentifierString() + " argument has not been registered " +
"with the argument parser.");
}
final LinkedHashSet argSet =
new LinkedHashSet<>(dependentArguments);
dependentArgumentSets.add(
new ObjectPair>(targetArgument, argSet));
}
/**
* Adds the provided collection of arguments as dependent upon the given
* argument. All of the arguments must have already been registered with this
* argument parser using the {@link #addArgument} method.
*
* @param targetArgument The argument whose presence indicates that at least
* one of the dependent arguments must also be
* present. It must not be {@code null}, and it must
* have already been registered with this argument
* parser.
* @param dependentArg1 The first argument in the set of arguments in which
* at least one argument must be present if the target
* argument is present. It must not be {@code null},
* and it must have already been registered with this
* argument parser.
* @param remaining The remaining arguments in the set of arguments in
* which at least one argument must be present if the
* target argument is present. It may be {@code null}
* or empty if no additional dependent arguments are
* needed, but if it is non-empty then all arguments
* must have already been registered with this
* argument parser.
*/
public void addDependentArgumentSet(final Argument targetArgument,
final Argument dependentArg1,
final Argument... remaining)
{
Validator.ensureNotNull(targetArgument, dependentArg1);
Validator.ensureTrue(namedArgs.contains(targetArgument),
"The ArgumentParser.addDependentArgumentSet method may only be used " +
"if all of the provided arguments have already been registered " +
"with the argument parser via the ArgumentParser.addArgument " +
"method. The " + targetArgument.getIdentifierString() +
" argument has not been registered with the argument parser.");
Validator.ensureTrue(namedArgs.contains(dependentArg1),
"The ArgumentParser.addDependentArgumentSet method may only be used " +
"if all of the provided arguments have already been registered " +
"with the argument parser via the ArgumentParser.addArgument " +
"method. The " + dependentArg1.getIdentifierString() +
" argument has not been registered with the argument parser.");
if (remaining != null)
{
for (final Argument a : remaining)
{
Validator.ensureTrue(namedArgs.contains(a),
"The ArgumentParser.addDependentArgumentSet method may only be " +
"used if all of the provided arguments have already been " +
"registered with the argument parser via the " +
"ArgumentParser.addArgument method. The " +
a.getIdentifierString() + " argument has not been " +
"registered with the argument parser.");
}
}
final LinkedHashSet argSet =
new LinkedHashSet<>(StaticUtils.computeMapCapacity(10));
argSet.add(dependentArg1);
if (remaining != null)
{
argSet.addAll(Arrays.asList(remaining));
}
dependentArgumentSets.add(
new ObjectPair>(targetArgument, argSet));
}
/**
* Adds the provided set of arguments as mutually dependent, such that if any
* of the arguments is provided, then all of them must be provided. It will
* be implemented by creating multiple dependent argument sets (one for each
* argument in the provided collection).
*
* @param arguments The collection of arguments to be used to create the
* dependent argument sets. It must not be {@code null},
* and must contain at least two elements.
*/
public void addMutuallyDependentArgumentSet(
final Collection arguments)
{
Validator.ensureNotNullWithMessage(arguments,
"ArgumentParser.addMutuallyDependentArgumentSet.arguments must not " +
"be null.");
Validator.ensureTrue((arguments.size() >= 2),
"ArgumentParser.addMutuallyDependentArgumentSet.arguments must " +
"contain at least two elements.");
for (final Argument a : arguments)
{
Validator.ensureTrue(namedArgs.contains(a),
"ArgumentParser.addMutuallyDependentArgumentSet invoked with " +
"argument " + a.getIdentifierString() +
" that is not registered with the argument parser.");
}
final Set allArgsSet = new HashSet<>(arguments);
for (final Argument a : allArgsSet)
{
final Set dependentArgs = new HashSet<>(allArgsSet);
dependentArgs.remove(a);
addDependentArgumentSet(a, dependentArgs);
}
}
/**
* Adds the provided set of arguments as mutually dependent, such that if any
* of the arguments is provided, then all of them must be provided. It will
* be implemented by creating multiple dependent argument sets (one for each
* argument in the provided collection).
*
* @param arg1 The first argument to include in the mutually dependent
* argument set. It must not be {@code null}.
* @param arg2 The second argument to include in the mutually dependent
* argument set. It must not be {@code null}.
* @param remaining An optional set of additional arguments to include in
* the mutually dependent argument set. It may be
* {@code null} or empty if only two arguments should be
* included in the mutually dependent argument set.
*/
public void addMutuallyDependentArgumentSet(final Argument arg1,
final Argument arg2,
final Argument... remaining)
{
Validator.ensureNotNullWithMessage(arg1,
"ArgumentParser.addMutuallyDependentArgumentSet.arg1 must not be " +
"null.");
Validator.ensureNotNullWithMessage(arg2,
"ArgumentParser.addMutuallyDependentArgumentSet.arg2 must not be " +
"null.");
final List args = new ArrayList<>(10);
args.add(arg1);
args.add(arg2);
if (remaining != null)
{
args.addAll(Arrays.asList(remaining));
}
addMutuallyDependentArgumentSet(args);
}
/**
* Retrieves the list of exclusive argument sets for this argument parser.
* If an argument contained in an exclusive argument set is provided, then
* none of the other arguments in that set may be provided. It is acceptable
* for none of the arguments in the set to be provided, unless the same set
* of arguments is also defined as a required argument set.
*
* @return The list of exclusive argument sets for this argument parser.
*/
public List> getExclusiveArgumentSets()
{
return Collections.unmodifiableList(exclusiveArgumentSets);
}
/**
* Adds the provided collection of arguments as an exclusive argument set, in
* which at most one of the arguments may be provided. All of the arguments
* must have already been registered with this argument parser using the
* {@link #addArgument} method.
*
* @param exclusiveArguments The collection of arguments to form an
* exclusive argument set. It must not be
* {@code null}, and all of the arguments must
* have already been registered with this argument
* parser.
*/
public void addExclusiveArgumentSet(
final Collection exclusiveArguments)
{
Validator.ensureNotNull(exclusiveArguments);
for (final Argument a : exclusiveArguments)
{
Validator.ensureTrue(namedArgs.contains(a),
"The ArgumentParser.addExclusiveArgumentSet method may only be " +
"used if all of the provided arguments have already been " +
"registered with the argument parser via the " +
"ArgumentParser.addArgument method. The " +
a.getIdentifierString() + " argument has not been " +
"registered with the argument parser.");
}
final LinkedHashSet argSet =
new LinkedHashSet<>(exclusiveArguments);
exclusiveArgumentSets.add(Collections.unmodifiableSet(argSet));
}
/**
* Adds the provided set of arguments as an exclusive argument set, in
* which at most one of the arguments may be provided. All of the arguments
* must have already been registered with this argument parser using the
* {@link #addArgument} method.
*
* @param arg1 The first argument to include in the exclusive argument
* set. It must not be {@code null}, and it must have
* already been registered with this argument parser.
* @param arg2 The second argument to include in the exclusive argument
* set. It must not be {@code null}, and it must have
* already been registered with this argument parser.
* @param remaining Any additional arguments to include in the exclusive
* argument set. It may be {@code null} or empty if no
* additional exclusive arguments are needed, but if it is
* non-empty then all arguments must have already been
* registered with this argument parser.
*/
public void addExclusiveArgumentSet(final Argument arg1, final Argument arg2,
final Argument... remaining)
{
Validator.ensureNotNull(arg1, arg2);
Validator.ensureTrue(namedArgs.contains(arg1),
"The ArgumentParser.addExclusiveArgumentSet method may only be " +
"used if all of the provided arguments have already been " +
"registered with the argument parser via the " +
"ArgumentParser.addArgument method. The " +
arg1.getIdentifierString() + " argument has not been " +
"registered with the argument parser.");
Validator.ensureTrue(namedArgs.contains(arg2),
"The ArgumentParser.addExclusiveArgumentSet method may only be " +
"used if all of the provided arguments have already been " +
"registered with the argument parser via the " +
"ArgumentParser.addArgument method. The " +
arg2.getIdentifierString() + " argument has not been " +
"registered with the argument parser.");
if (remaining != null)
{
for (final Argument a : remaining)
{
Validator.ensureTrue(namedArgs.contains(a),
"The ArgumentParser.addExclusiveArgumentSet method may only be " +
"used if all of the provided arguments have already been " +
"registered with the argument parser via the " +
"ArgumentParser.addArgument method. The " +
a.getIdentifierString() + " argument has not been " +
"registered with the argument parser.");
}
}
final LinkedHashSet argSet =
new LinkedHashSet<>(StaticUtils.computeMapCapacity(10));
argSet.add(arg1);
argSet.add(arg2);
if (remaining != null)
{
argSet.addAll(Arrays.asList(remaining));
}
exclusiveArgumentSets.add(Collections.unmodifiableSet(argSet));
}
/**
* Retrieves the list of required argument sets for this argument parser. At
* least one of the arguments contained in this set must be provided. If this
* same set is also defined as an exclusive argument set, then exactly one
* of those arguments must be provided.
*
* @return The list of required argument sets for this argument parser.
*/
public List> getRequiredArgumentSets()
{
return Collections.unmodifiableList(requiredArgumentSets);
}
/**
* Adds the provided collection of arguments as a required argument set, in
* which at least one of the arguments must be provided. All of the arguments
* must have already been registered with this argument parser using the
* {@link #addArgument} method.
*
* @param requiredArguments The collection of arguments to form an
* required argument set. It must not be
* {@code null}, and all of the arguments must have
* already been registered with this argument
* parser.
*/
public void addRequiredArgumentSet(
final Collection requiredArguments)
{
Validator.ensureNotNull(requiredArguments);
for (final Argument a : requiredArguments)
{
Validator.ensureTrue(namedArgs.contains(a),
"The ArgumentParser.addRequiredArgumentSet method may only be " +
"used if all of the provided arguments have already been " +
"registered with the argument parser via the " +
"ArgumentParser.addArgument method. The " +
a.getIdentifierString() + " argument has not been " +
"registered with the argument parser.");
}
final LinkedHashSet argSet =
new LinkedHashSet<>(requiredArguments);
requiredArgumentSets.add(Collections.unmodifiableSet(argSet));
}
/**
* Adds the provided set of arguments as a required argument set, in which
* at least one of the arguments must be provided. All of the arguments must
* have already been registered with this argument parser using the
* {@link #addArgument} method.
*
* @param arg1 The first argument to include in the required argument
* set. It must not be {@code null}, and it must have
* already been registered with this argument parser.
* @param arg2 The second argument to include in the required argument
* set. It must not be {@code null}, and it must have
* already been registered with this argument parser.
* @param remaining Any additional arguments to include in the required
* argument set. It may be {@code null} or empty if no
* additional required arguments are needed, but if it is
* non-empty then all arguments must have already been
* registered with this argument parser.
*/
public void addRequiredArgumentSet(final Argument arg1, final Argument arg2,
final Argument... remaining)
{
Validator.ensureNotNull(arg1, arg2);
Validator.ensureTrue(namedArgs.contains(arg1),
"The ArgumentParser.addRequiredArgumentSet method may only be " +
"used if all of the provided arguments have already been " +
"registered with the argument parser via the " +
"ArgumentParser.addArgument method. The " +
arg1.getIdentifierString() + " argument has not been " +
"registered with the argument parser.");
Validator.ensureTrue(namedArgs.contains(arg2),
"The ArgumentParser.addRequiredArgumentSet method may only be " +
"used if all of the provided arguments have already been " +
"registered with the argument parser via the " +
"ArgumentParser.addArgument method. The " +
arg2.getIdentifierString() + " argument has not been " +
"registered with the argument parser.");
if (remaining != null)
{
for (final Argument a : remaining)
{
Validator.ensureTrue(namedArgs.contains(a),
"The ArgumentParser.addRequiredArgumentSet method may only be " +
"used if all of the provided arguments have already been " +
"registered with the argument parser via the " +
"ArgumentParser.addArgument method. The " +
a.getIdentifierString() + " argument has not been " +
"registered with the argument parser.");
}
}
final LinkedHashSet argSet =
new LinkedHashSet<>(StaticUtils.computeMapCapacity(10));
argSet.add(arg1);
argSet.add(arg2);
if (remaining != null)
{
argSet.addAll(Arrays.asList(remaining));
}
requiredArgumentSets.add(Collections.unmodifiableSet(argSet));
}
/**
* Indicates whether any subcommands have been registered with this argument
* parser.
*
* @return {@code true} if one or more subcommands have been registered with
* this argument parser, or {@code false} if not.
*/
public boolean hasSubCommands()
{
return (! subCommands.isEmpty());
}
/**
* Retrieves the subcommand that was provided in the set of command-line
* arguments, if any.
*
* @return The subcommand that was provided in the set of command-line
* arguments, or {@code null} if there is none.
*/
public SubCommand getSelectedSubCommand()
{
return selectedSubCommand;
}
/**
* Specifies the subcommand that was provided in the set of command-line
* arguments.
*
* @param subcommand The subcommand that was provided in the set of
* command-line arguments. It may be {@code null} if no
* subcommand should be used.
*/
void setSelectedSubCommand(final SubCommand subcommand)
{
selectedSubCommand = subcommand;
if (subcommand != null)
{
subcommand.setPresent();
}
}
/**
* Retrieves a list of all subcommands associated with this argument parser.
*
* @return A list of all subcommands associated with this argument parser, or
* an empty list if there are no associated subcommands.
*/
public List getSubCommands()
{
return Collections.unmodifiableList(subCommands);
}
/**
* Retrieves the subcommand for the provided name.
*
* @param name The name of the subcommand to retrieve.
*
* @return The subcommand with the provided name, or {@code null} if there is
* no such subcommand.
*/
public SubCommand getSubCommand(final String name)
{
if (name == null)
{
return null;
}
return subCommandsByName.get(StaticUtils.toLowerCase(name));
}
/**
* Registers the provided subcommand with this argument parser.
*
* @param subCommand The subcommand to register with this argument parser.
* It must not be {@code null}.
*
* @throws ArgumentException If this argument parser does not allow
* subcommands, if there is a conflict between any
* of the names of the provided subcommand and an
* already-registered subcommand, or if there is a
* conflict between any of the subcommand-specific
* arguments and global arguments.
*/
public void addSubCommand(final SubCommand subCommand)
throws ArgumentException
{
// Ensure that the subcommand isn't already registered with an argument
// parser.
if (subCommand.getGlobalArgumentParser() != null)
{
throw new ArgumentException(
ERR_PARSER_SUBCOMMAND_ALREADY_REGISTERED_WITH_PARSER.get());
}
// Ensure that the caller isn't trying to create a nested subcommand.
if (parentSubCommand != null)
{
throw new ArgumentException(
ERR_PARSER_CANNOT_CREATE_NESTED_SUBCOMMAND.get(
parentSubCommand.getPrimaryName()));
}
// Ensure that this argument parser doesn't allow trailing arguments.
if (allowsTrailingArguments())
{
throw new ArgumentException(
ERR_PARSER_WITH_TRAILING_ARGS_CANNOT_HAVE_SUBCOMMANDS.get());
}
// Ensure that the subcommand doesn't have any names that conflict with an
// existing subcommand.
for (final String name : subCommand.getNames(true))
{
if (subCommandsByName.containsKey(StaticUtils.toLowerCase(name)))
{
throw new ArgumentException(
ERR_SUBCOMMAND_NAME_ALREADY_IN_USE.get(name));
}
}
// Register the subcommand.
for (final String name : subCommand.getNames(true))
{
subCommandsByName.put(StaticUtils.toLowerCase(name), subCommand);
}
subCommands.add(subCommand);
subCommand.setGlobalArgumentParser(this);
}
/**
* Registers the provided additional name for this subcommand.
*
* @param name The name to be registered. It must not be
* {@code null} or empty.
* @param subCommand The subcommand with which the name is associated. It
* must not be {@code null}.
*
* @throws ArgumentException If the provided name is already in use.
*/
void addSubCommand(final String name, final SubCommand subCommand)
throws ArgumentException
{
final String lowerName = StaticUtils.toLowerCase(name);
if (subCommandsByName.containsKey(lowerName))
{
throw new ArgumentException(
ERR_SUBCOMMAND_NAME_ALREADY_IN_USE.get(name));
}
subCommandsByName.put(lowerName, subCommand);
}
/**
* Retrieves the set of unnamed trailing arguments in the provided command
* line arguments.
*
* @return The set of unnamed trailing arguments in the provided command line
* arguments, or an empty list if there were none.
*/
public List getTrailingArguments()
{
return Collections.unmodifiableList(trailingArgs);
}
/**
* Resets this argument parser so that it appears as if it had not been used
* to parse any command-line arguments.
*/
void reset()
{
selectedSubCommand = null;
for (final Argument a : namedArgs)
{
a.reset();
}
propertiesFileUsed = null;
argumentsSetFromPropertiesFile.clear();
trailingArgs.clear();
}
/**
* Clears the set of trailing arguments for this argument parser.
*/
void resetTrailingArguments()
{
trailingArgs.clear();
}
/**
* Adds the provided value to the set of trailing arguments.
*
* @param value The value to add to the set of trailing arguments.
*
* @throws ArgumentException If the parser already has the maximum allowed
* number of trailing arguments.
*/
void addTrailingArgument(final String value)
throws ArgumentException
{
if ((maxTrailingArgs > 0) && (trailingArgs.size() >= maxTrailingArgs))
{
throw new ArgumentException(ERR_PARSER_TOO_MANY_TRAILING_ARGS.get(value,
commandName, maxTrailingArgs));
}
trailingArgs.add(value);
}
/**
* Retrieves the properties file that was used to obtain values for arguments
* not set on the command line.
*
* @return The properties file that was used to obtain values for arguments
* not set on the command line, or {@code null} if no properties file
* was used.
*/
public File getPropertiesFileUsed()
{
return propertiesFileUsed;
}
/**
* Retrieves a list of the string representations of any arguments used for
* the associated tool that were set from a properties file rather than
* provided on the command line. The values of any arguments marked as
* sensitive will be obscured.
*
* @return A list of the string representations any arguments used for the
* associated tool that were set from a properties file rather than
* provided on the command line, or an empty list if no arguments
* were set from a properties file.
*/
public List getArgumentsSetFromPropertiesFile()
{
return Collections.unmodifiableList(argumentsSetFromPropertiesFile);
}
/**
* Indicates whether the comment listing arguments obtained from a properties
* file should be suppressed.
*
* @return {@code true} if the comment listing arguments obtained from a
* properties file should be suppressed, or {@code false} if not.
*/
public boolean suppressPropertiesFileComment()
{
final BooleanArgument arg =
getBooleanArgument(ARG_NAME_SUPPRESS_PROPERTIES_FILE_COMMENT);
return ((arg != null) && arg.isPresent());
}
/**
* Creates a copy of this argument parser that is "clean" and appears as if it
* has not been used to parse an argument set. The new parser will have all
* of the same arguments and constraints as this parser.
*
* @return The "clean" copy of this argument parser.
*/
public ArgumentParser getCleanCopy()
{
return new ArgumentParser(this, null);
}
/**
* Parses the provided set of arguments.
*
* @param args An array containing the argument information to parse. It
* must not be {@code null}.
*
* @throws ArgumentException If a problem occurs while attempting to parse
* the argument information.
*/
public void parse(final String[] args)
throws ArgumentException
{
// Iterate through the provided args strings and process them.
ArgumentParser subCommandParser = null;
boolean inTrailingArgs = false;
boolean skipFinalValidation = false;
String subCommandName = null;
for (int i=0; i < args.length; i++)
{
final String s = args[i];
if (inTrailingArgs)
{
if (maxTrailingArgs == 0)
{
throw new ArgumentException(ERR_PARSER_TRAILING_ARGS_NOT_ALLOWED.get(
s, commandName));
}
else if (trailingArgs.size() >= maxTrailingArgs)
{
throw new ArgumentException(ERR_PARSER_TOO_MANY_TRAILING_ARGS.get(s,
commandName, maxTrailingArgs));
}
else
{
trailingArgs.add(s);
}
}
else if (s.equals("--"))
{
// This signifies the end of the named arguments and the beginning of
// the trailing arguments.
inTrailingArgs = true;
}
else if (s.startsWith("--"))
{
// There may be an equal sign to separate the name from the value.
final String argName;
final int equalPos = s.indexOf('=');
if (equalPos > 0)
{
argName = s.substring(2, equalPos);
}
else
{
argName = s.substring(2);
}
final String lowerName = StaticUtils.toLowerCase(argName);
Argument a = namedArgsByLongID.get(lowerName);
if ((a == null) && (subCommandParser != null))
{
a = subCommandParser.namedArgsByLongID.get(lowerName);
}
if (a == null)
{
throw new ArgumentException(ERR_PARSER_NO_SUCH_LONG_ID.get(argName));
}
else if (a.isUsageArgument())
{
skipFinalValidation |= skipFinalValidationBecauseOfArgument(a);
}
a.incrementOccurrences();
if (a.takesValue())
{
if (equalPos > 0)
{
a.addValue(s.substring(equalPos+1));
}
else
{
i++;
if (i >= args.length)
{
throw new ArgumentException(ERR_PARSER_LONG_ARG_MISSING_VALUE.get(
argName));
}
else
{
a.addValue(args[i]);
}
}
}
else
{
if (equalPos > 0)
{
throw new ArgumentException(
ERR_PARSER_LONG_ARG_DOESNT_TAKE_VALUE.get(argName));
}
}
}
else if (s.startsWith("-"))
{
if (s.length() == 1)
{
throw new ArgumentException(ERR_PARSER_UNEXPECTED_DASH.get());
}
else if (s.length() == 2)
{
final char c = s.charAt(1);
Argument a = namedArgsByShortID.get(c);
if ((a == null) && (subCommandParser != null))
{
a = subCommandParser.namedArgsByShortID.get(c);
}
if (a == null)
{
throw new ArgumentException(ERR_PARSER_NO_SUCH_SHORT_ID.get(c));
}
else if (a.isUsageArgument())
{
skipFinalValidation |= skipFinalValidationBecauseOfArgument(a);
}
a.incrementOccurrences();
if (a.takesValue())
{
i++;
if (i >= args.length)
{
throw new ArgumentException(
ERR_PARSER_SHORT_ARG_MISSING_VALUE.get(c));
}
else
{
a.addValue(args[i]);
}
}
}
else
{
char c = s.charAt(1);
Argument a = namedArgsByShortID.get(c);
if ((a == null) && (subCommandParser != null))
{
a = subCommandParser.namedArgsByShortID.get(c);
}
if (a == null)
{
throw new ArgumentException(ERR_PARSER_NO_SUCH_SHORT_ID.get(c));
}
else if (a.isUsageArgument())
{
skipFinalValidation |= skipFinalValidationBecauseOfArgument(a);
}
a.incrementOccurrences();
if (a.takesValue())
{
a.addValue(s.substring(2));
}
else
{
// The rest of the characters in the string must also resolve to
// arguments that don't take values.
for (int j=2; j < s.length(); j++)
{
c = s.charAt(j);
a = namedArgsByShortID.get(c);
if ((a == null) && (subCommandParser != null))
{
a = subCommandParser.namedArgsByShortID.get(c);
}
if (a == null)
{
throw new ArgumentException(
ERR_PARSER_NO_SUBSEQUENT_SHORT_ARG.get(c, s));
}
else if (a.isUsageArgument())
{
skipFinalValidation |= skipFinalValidationBecauseOfArgument(a);
}
a.incrementOccurrences();
if (a.takesValue())
{
throw new ArgumentException(
ERR_PARSER_SUBSEQUENT_SHORT_ARG_TAKES_VALUE.get(
c, s));
}
}
}
}
}
else if (subCommands.isEmpty())
{
inTrailingArgs = true;
if (maxTrailingArgs == 0)
{
throw new ArgumentException(ERR_PARSER_TRAILING_ARGS_NOT_ALLOWED.get(
s, commandName));
}
else
{
trailingArgs.add(s);
}
}
else
{
if (selectedSubCommand == null)
{
subCommandName = s;
selectedSubCommand =
subCommandsByName.get(StaticUtils.toLowerCase(s));
if (selectedSubCommand == null)
{
throw new ArgumentException(ERR_PARSER_NO_SUCH_SUBCOMMAND.get(s,
commandName));
}
else
{
selectedSubCommand.setPresent();
subCommandParser = selectedSubCommand.getArgumentParser();
}
}
else
{
throw new ArgumentException(ERR_PARSER_CONFLICTING_SUBCOMMANDS.get(
subCommandName, s));
}
}
}
// Perform any appropriate processing related to the use of a properties
// file.
if (! handlePropertiesFile())
{
return;
}
// If a usage argument was provided, then no further validation should be
// performed.
if (skipFinalValidation)
{
return;
}
// If any subcommands are defined, then one must have been provided.
if ((! subCommands.isEmpty()) && (selectedSubCommand == null))
{
throw new ArgumentException(
ERR_PARSER_MISSING_SUBCOMMAND.get(commandName));
}
doFinalValidation(this);
if (selectedSubCommand != null)
{
doFinalValidation(selectedSubCommand.getArgumentParser());
}
}
/**
* Sets the command-line tool with which this argument parser is associated.
*
* @param commandLineTool The command-line tool with which this argument
* parser is associated. It may be {@code null} if
* there is no associated command-line tool.
*/
public void setCommandLineTool(final CommandLineTool commandLineTool)
{
this.commandLineTool = commandLineTool;
}
/**
* Performs the final validation for the provided argument parser.
*
* @param parser The argument parser for which to perform the final
* validation.
*
* @throws ArgumentException If a validation problem is encountered.
*/
private static void doFinalValidation(final ArgumentParser parser)
throws ArgumentException
{
// Make sure that all required arguments have values.
for (final Argument a : parser.namedArgs)
{
if (a.isRequired() && (! a.isPresent()))
{
throw new ArgumentException(ERR_PARSER_MISSING_REQUIRED_ARG.get(
a.getIdentifierString()));
}
}
// Make sure that at least the minimum number of trailing arguments were
// provided.
if (parser.trailingArgs.size() < parser.minTrailingArgs)
{
throw new ArgumentException(ERR_PARSER_NOT_ENOUGH_TRAILING_ARGS.get(
parser.commandName, parser.minTrailingArgs,
parser.trailingArgsPlaceholder));
}
// Make sure that there are no dependent argument set conflicts.
for (final ObjectPair> p :
parser.dependentArgumentSets)
{
final Argument targetArg = p.getFirst();
if (targetArg.getNumOccurrences() > 0)
{
final Set argSet = p.getSecond();
boolean found = false;
for (final Argument a : argSet)
{
if (a.getNumOccurrences() > 0)
{
found = true;
break;
}
}
if (! found)
{
if (argSet.size() == 1)
{
throw new ArgumentException(
ERR_PARSER_DEPENDENT_CONFLICT_SINGLE.get(
targetArg.getIdentifierString(),
argSet.iterator().next().getIdentifierString()));
}
else
{
boolean first = true;
final StringBuilder buffer = new StringBuilder();
for (final Argument a : argSet)
{
if (first)
{
first = false;
}
else
{
buffer.append(", ");
}
buffer.append(a.getIdentifierString());
}
throw new ArgumentException(
ERR_PARSER_DEPENDENT_CONFLICT_MULTIPLE.get(
targetArg.getIdentifierString(), buffer.toString()));
}
}
}
}
// Make sure that there are no exclusive argument set conflicts.
for (final Set argSet : parser.exclusiveArgumentSets)
{
Argument setArg = null;
for (final Argument a : argSet)
{
if (a.getNumOccurrences() > 0)
{
if (setArg == null)
{
setArg = a;
}
else
{
throw new ArgumentException(ERR_PARSER_EXCLUSIVE_CONFLICT.get(
setArg.getIdentifierString(),
a.getIdentifierString()));
}
}
}
}
// Make sure that there are no required argument set conflicts.
for (final Set argSet : parser.requiredArgumentSets)
{
boolean found = false;
for (final Argument a : argSet)
{
if (a.getNumOccurrences() > 0)
{
found = true;
break;
}
}
if (! found)
{
boolean first = true;
final StringBuilder buffer = new StringBuilder();
for (final Argument a : argSet)
{
if (first)
{
first = false;
}
else
{
buffer.append(", ");
}
buffer.append(a.getIdentifierString());
}
throw new ArgumentException(ERR_PARSER_REQUIRED_CONFLICT.get(
buffer.toString()));
}
}
}
/**
* Indicates whether the provided argument is one that indicates that the
* parser should skip all validation except that performed when assigning
* values from command-line arguments. Validation that will be skipped
* includes ensuring that all required arguments have values, ensuring that
* the minimum number of trailing arguments were provided, and ensuring that
* there were no dependent/exclusive/required argument set conflicts.
*
* @param a The argument for which to make the determination.
*
* @return {@code true} if the provided argument is one that indicates that
* final validation should be skipped, or {@code false} if not.
*/
private static boolean skipFinalValidationBecauseOfArgument(final Argument a)
{
// We will skip final validation for all usage arguments except the ones
// used for interacting with properties and output files.
if (ARG_NAME_PROPERTIES_FILE_PATH.equals(a.getLongIdentifier()) ||
ARG_NAME_NO_PROPERTIES_FILE.equals(a.getLongIdentifier()) ||
ARG_NAME_SUPPRESS_PROPERTIES_FILE_COMMENT.equals(
a.getLongIdentifier()) ||
ARG_NAME_OUTPUT_FILE.equals(a.getLongIdentifier()) ||
ARG_NAME_TEE_OUTPUT.equals(a.getLongIdentifier()))
{
return false;
}
return a.isUsageArgument();
}
/**
* Performs any appropriate properties file processing for this argument
* parser.
*
* @return {@code true} if the tool should continue processing, or
* {@code false} if it should return immediately.
*
* @throws ArgumentException If a problem is encountered while attempting
* to parse a properties file or update arguments
* with the values contained in it.
*/
private boolean handlePropertiesFile()
throws ArgumentException
{
final BooleanArgument noPropertiesFile;
final FileArgument generatePropertiesFile;
final FileArgument propertiesFilePath;
try
{
propertiesFilePath = getFileArgument(ARG_NAME_PROPERTIES_FILE_PATH);
generatePropertiesFile =
getFileArgument(ARG_NAME_GENERATE_PROPERTIES_FILE);
noPropertiesFile = getBooleanArgument(ARG_NAME_NO_PROPERTIES_FILE);
}
catch (final Exception e)
{
Debug.debugException(e);
// This should only ever happen if the argument parser has an argument
// with a name that conflicts with one of the properties file arguments
// but isn't of the right type. In this case, we'll assume that no
// properties file will be used.
return true;
}
// If any of the properties file arguments isn't defined, then we'll assume
// that no properties file will be used.
if ((propertiesFilePath == null) || (generatePropertiesFile == null) ||
(noPropertiesFile == null))
{
return true;
}
// If the noPropertiesFile argument is present, then don't do anything but
// make sure that neither of the other arguments was specified.
if (noPropertiesFile.isPresent())
{
if (propertiesFilePath.isPresent())
{
throw new ArgumentException(ERR_PARSER_EXCLUSIVE_CONFLICT.get(
noPropertiesFile.getIdentifierString(),
propertiesFilePath.getIdentifierString()));
}
else if (generatePropertiesFile.isPresent())
{
throw new ArgumentException(ERR_PARSER_EXCLUSIVE_CONFLICT.get(
noPropertiesFile.getIdentifierString(),
generatePropertiesFile.getIdentifierString()));
}
else
{
return true;
}
}
// If the generatePropertiesFile argument is present, then make sure the
// propertiesFilePath argument is not set and generate the output.
if (generatePropertiesFile.isPresent())
{
if (propertiesFilePath.isPresent())
{
throw new ArgumentException(ERR_PARSER_EXCLUSIVE_CONFLICT.get(
generatePropertiesFile.getIdentifierString(),
propertiesFilePath.getIdentifierString()));
}
else
{
generatePropertiesFile(
generatePropertiesFile.getValue().getAbsolutePath());
return false;
}
}
// If the propertiesFilePath argument is present, then try to make use of
// the specified file.
if (propertiesFilePath.isPresent())
{
final File propertiesFile = propertiesFilePath.getValue();
if (propertiesFile.exists() && propertiesFile.isFile())
{
handlePropertiesFile(propertiesFilePath.getValue());
}
else
{
throw new ArgumentException(
ERR_PARSER_NO_SUCH_PROPERTIES_FILE.get(
propertiesFilePath.getIdentifierString(),
propertiesFile.getAbsolutePath()));
}
return true;
}
// We may still use a properties file if the path was specified in either a
// JVM property or an environment variable. If both are defined, the JVM
// property will take precedence. If a property or environment variable
// specifies an invalid value, then we'll just ignore it.
String path = StaticUtils.getSystemProperty(
PROPERTY_DEFAULT_PROPERTIES_FILE_PATH);
if (path == null)
{
path = StaticUtils.getEnvironmentVariable(
ENV_DEFAULT_PROPERTIES_FILE_PATH);
}
if (path != null)
{
final File propertiesFile = new File(path);
if (propertiesFile.exists() && propertiesFile.isFile())
{
handlePropertiesFile(propertiesFile);
}
}
return true;
}
/**
* Write an empty properties file for this argument parser to the specified
* path.
*
* @param path The path to the properties file to be written.
*
* @throws ArgumentException If a problem is encountered while writing the
* properties file.
*/
private void generatePropertiesFile(final String path)
throws ArgumentException
{
final PrintWriter w;
try
{
// The java.util.Properties specification states that properties files
// should be read using the ISO 8859-1 character set.
w = new PrintWriter(new OutputStreamWriter(new FileOutputStream(path),
StandardCharsets.ISO_8859_1));
}
catch (final Exception e)
{
Debug.debugException(e);
throw new ArgumentException(
ERR_PARSER_GEN_PROPS_CANNOT_OPEN_FILE.get(path,
StaticUtils.getExceptionMessage(e)),
e);
}
try
{
wrapComment(w, INFO_PARSER_GEN_PROPS_HEADER_1.get(commandName));
w.println('#');
wrapComment(w,
INFO_PARSER_GEN_PROPS_HEADER_2.get(commandName,
ARG_NAME_PROPERTIES_FILE_PATH,
PROPERTY_DEFAULT_PROPERTIES_FILE_PATH,
ENV_DEFAULT_PROPERTIES_FILE_PATH, ARG_NAME_NO_PROPERTIES_FILE));
w.println('#');
wrapComment(w, INFO_PARSER_GEN_PROPS_HEADER_3.get());
w.println('#');
wrapComment(w, INFO_PARSER_GEN_PROPS_HEADER_4.get());
w.println('#');
wrapComment(w, INFO_PARSER_GEN_PROPS_HEADER_5.get(commandName));
for (final Argument a : getNamedArguments())
{
writeArgumentProperties(w, null, a);
}
for (final SubCommand sc : getSubCommands())
{
for (final Argument a : sc.getArgumentParser().getNamedArguments())
{
writeArgumentProperties(w, sc, a);
}
}
}
finally
{
w.close();
}
}
/**
* Writes information about the provided argument to the given writer.
*
* @param w The writer to which the properties should be written. It must
* not be {@code null}.
* @param sc The subcommand with which the argument is associated. It may
* be {@code null} if the provided argument is a global argument.
* @param a The argument for which to write the properties. It must not be
* {@code null}.
*/
private void writeArgumentProperties(final PrintWriter w,
final SubCommand sc,
final Argument a)
{
if (a.isUsageArgument() || a.isHidden())
{
return;
}
w.println();
w.println();
wrapComment(w, a.getDescription());
w.println('#');
final String constraints = a.getValueConstraints();
if ((constraints != null) && (! constraints.isEmpty()) &&
(! (a instanceof BooleanArgument)))
{
wrapComment(w, constraints);
w.println('#');
}
final String identifier;
if (a.getLongIdentifier() != null)
{
identifier = a.getLongIdentifier();
}
else
{
identifier = a.getIdentifierString();
}
String placeholder = a.getValuePlaceholder();
if (placeholder == null)
{
if (a instanceof BooleanArgument)
{
placeholder = "{true|false}";
}
else
{
placeholder = "";
}
}
final String propertyName;
if (sc == null)
{
propertyName = commandName + '.' + identifier;
}
else
{
propertyName = commandName + '.' + sc.getPrimaryName() + '.' + identifier;
}
w.println("# " + propertyName + '=' + placeholder);
if (a.isPresent())
{
for (final String s : a.getValueStringRepresentations(false))
{
w.println(propertyName + '=' + s);
}
}
}
/**
* Wraps the given string and writes it as a comment to the provided writer.
*
* @param w The writer to use to write the wrapped and commented string.
* @param s The string to be wrapped and written.
*/
private static void wrapComment(final PrintWriter w, final String s)
{
for (final String line : StaticUtils.wrapLine(s, 77))
{
w.println("# " + line);
}
}
/**
* Reads the contents of the specified properties file and updates the
* configured arguments as appropriate.
*
* @param propertiesFile The properties file to process.
*
* @throws ArgumentException If a problem is encountered while examining the
* properties file, or while trying to assign a
* property value to a corresponding argument.
*/
private void handlePropertiesFile(final File propertiesFile)
throws ArgumentException
{
final String propertiesFilePath = propertiesFile.getAbsolutePath();
InputStream inputStream = null;
final BufferedReader reader;
try
{
inputStream = new FileInputStream(propertiesFile);
// Handle the case in which the properties file may be encrypted.
final List cachedPasswords;
final PrintStream err;
final PrintStream out;
final CommandLineTool tool = commandLineTool;
if (tool == null)
{
cachedPasswords = Collections.emptyList();
out = System.out;
err = System.err;
}
else
{
cachedPasswords =
tool.getPasswordFileReader().getCachedEncryptionPasswords();
out = tool.getOut();
err = tool.getErr();
}
final ObjectPair encryptionData =
ToolUtils.getPossiblyPassphraseEncryptedInputStream(inputStream,
cachedPasswords, true,
INFO_PARSER_PROMPT_FOR_PROP_FILE_ENC_PW.get(
propertiesFile.getAbsolutePath()),
ERR_PARSER_WRONG_PROP_FILE_ENC_PW.get(
propertiesFile.getAbsolutePath()),
out, err);
inputStream = encryptionData.getFirst();
if ((tool != null) && (encryptionData.getSecond() != null))
{
tool.getPasswordFileReader().addToEncryptionPasswordCache(
encryptionData.getSecond());
}
// Handle the case in which the properties file may be compressed.
inputStream = ToolUtils.getPossiblyGZIPCompressedInputStream(inputStream);
// The java.util.Properties specification states that properties files
// should be read using the ISO 8859-1 character set, and that characters
// that cannot be encoded in that format should be represented using
// Unicode escapes that start with a backslash, a lowercase letter "u",
// and four hexadecimal digits. To provide compatibility with the Java
// Properties file format (except we also support the same property
// appearing multiple times), we will also use that encoding and will
// support Unicode escape sequences.
reader = new BufferedReader(new InputStreamReader(inputStream,
StandardCharsets.ISO_8859_1));
}
catch (final Exception e)
{
if (inputStream != null)
{
try
{
inputStream.close();
}
catch (final Exception e2)
{
Debug.debugException(e2);
}
}
Debug.debugException(e);
throw new ArgumentException(
ERR_PARSER_CANNOT_OPEN_PROP_FILE.get(propertiesFilePath,
StaticUtils.getExceptionMessage(e)),
e);
}
try
{
// Read all of the lines of the file, ignoring comments and unwrapping
// properties that span multiple lines.
boolean lineIsContinued = false;
int lineNumber = 0;
final ArrayList> propertyLines =
new ArrayList<>(10);
while (true)
{
String line;
try
{
line = reader.readLine();
lineNumber++;
}
catch (final Exception e)
{
Debug.debugException(e);
throw new ArgumentException(
ERR_PARSER_ERROR_READING_PROP_FILE.get(propertiesFilePath,
StaticUtils.getExceptionMessage(e)),
e);
}
// If the line is null, then we've reached the end of the file. If we
// expect a previous line to have been continued, then this is an error.
if (line == null)
{
if (lineIsContinued)
{
throw new ArgumentException(
ERR_PARSER_PROP_FILE_MISSING_CONTINUATION.get(
(lineNumber-1), propertiesFilePath));
}
break;
}
// See if the line has any leading whitespace, and if so then trim it
// off. If there is leading whitespace, then make sure that we expect
// the previous line to be continued.
final int initialLength = line.length();
line = StaticUtils.trimLeading(line);
final boolean hasLeadingWhitespace = (line.length() < initialLength);
if (hasLeadingWhitespace && (! lineIsContinued))
{
throw new ArgumentException(
ERR_PARSER_PROP_FILE_UNEXPECTED_LEADING_SPACE.get(
propertiesFilePath, lineNumber));
}
// If the line is empty or starts with "#", then skip it. But make sure
// we didn't expect the previous line to be continued.
if ((line.isEmpty()) || line.startsWith("#"))
{
if (lineIsContinued)
{
throw new ArgumentException(
ERR_PARSER_PROP_FILE_MISSING_CONTINUATION.get(
(lineNumber-1), propertiesFilePath));
}
continue;
}
// See if the line ends with a backslash and if so then trim it off.
final boolean hasTrailingBackslash = line.endsWith("\\");
if (line.endsWith("\\"))
{
line = line.substring(0, (line.length() - 1));
}
// If the previous line needs to be continued, then append the new line
// to it. Otherwise, add it as a new line.
if (lineIsContinued)
{
propertyLines.get(propertyLines.size() - 1).getSecond().append(line);
}
else
{
propertyLines.add(
new ObjectPair<>(lineNumber, new StringBuilder(line)));
}
lineIsContinued = hasTrailingBackslash;
}
// Parse all of the lines into a map of identifiers and their
// corresponding values.
propertiesFileUsed = propertiesFile;
if (propertyLines.isEmpty())
{
return;
}
final HashMap> propertyMap =
new HashMap<>(StaticUtils.computeMapCapacity(propertyLines.size()));
for (final ObjectPair p : propertyLines)
{
lineNumber = p.getFirst();
final String line = handleUnicodeEscapes(propertiesFilePath, lineNumber,
p.getSecond());
final int equalPos = line.indexOf('=');
if (equalPos <= 0)
{
throw new ArgumentException(ERR_PARSER_MALFORMED_PROP_LINE.get(
propertiesFilePath, lineNumber, line));
}
final String propertyName = line.substring(0, equalPos).trim();
final String propertyValue = line.substring(equalPos+1).trim();
if (propertyValue.isEmpty())
{
// The property doesn't have a value, so we can ignore it.
continue;
}
// An argument can have multiple identifiers, and we will allow any of
// them to be used to reference it. To deal with this, we'll map the
// argument identifier to its corresponding argument and then use the
// preferred identifier for that argument in the map. The same applies
// to subcommand names.
boolean prefixedWithToolName = false;
boolean prefixedWithSubCommandName = false;
Argument a = getNamedArgument(propertyName);
if (a == null)
{
// It could be that the argument name was prefixed with the tool name.
// Check to see if that was the case.
if (propertyName.startsWith(commandName + '.'))
{
prefixedWithToolName = true;
String basePropertyName =
propertyName.substring(commandName.length()+1);
a = getNamedArgument(basePropertyName);
if (a == null)
{
final int periodPos = basePropertyName.indexOf('.');
if (periodPos > 0)
{
final String subCommandName =
basePropertyName.substring(0, periodPos);
if ((selectedSubCommand != null) &&
selectedSubCommand.hasName(subCommandName))
{
prefixedWithSubCommandName = true;
basePropertyName = basePropertyName.substring(periodPos+1);
a = selectedSubCommand.getArgumentParser().getNamedArgument(
basePropertyName);
}
}
else if (selectedSubCommand != null)
{
a = selectedSubCommand.getArgumentParser().getNamedArgument(
basePropertyName);
}
}
}
else if (selectedSubCommand != null)
{
a = selectedSubCommand.getArgumentParser().getNamedArgument(
propertyName);
}
}
if (a == null)
{
// This could mean that there's a typo in the property name, but it's
// more likely the case that the property is for a different tool. In
// either case, we'll ignore it.
continue;
}
final String canonicalPropertyName;
if (prefixedWithToolName)
{
if (prefixedWithSubCommandName)
{
canonicalPropertyName = commandName + '.' +
selectedSubCommand.getPrimaryName() + '.' +
a.getIdentifierString();
}
else
{
canonicalPropertyName = commandName + '.' + a.getIdentifierString();
}
}
else
{
canonicalPropertyName = a.getIdentifierString();
}
ArrayList valueList = propertyMap.get(canonicalPropertyName);
if (valueList == null)
{
valueList = new ArrayList<>(5);
propertyMap.put(canonicalPropertyName, valueList);
}
valueList.add(propertyValue);
}
// Iterate through all of the named arguments for the argument parser and
// see if we should use the properties to assign values to any of the
// arguments that weren't provided on the command line.
setArgsFromPropertiesFile(propertyMap, false);
// If there is a selected subcommand, then iterate through all of its
// arguments.
if (selectedSubCommand != null)
{
setArgsFromPropertiesFile(propertyMap, true);
}
}
finally
{
try
{
reader.close();
}
catch (final Exception e)
{
Debug.debugException(e);
}
}
}
/**
* Retrieves a string that contains the contents of the provided buffer, but
* with any Unicode escape sequences converted to the appropriate character
* representation, and any other escapes having the initial backslash
* removed.
*
* @param propertiesFilePath The path to the properties file
* @param lineNumber The line number on which the property definition
* starts.
* @param buffer The buffer containing the data to be processed. It
* must not be {@code null} but may be empty.
*
* @return A string that contains the contents of the provided buffer, but
* with any Unicode escape sequences converted to the appropriate
* character representation.
*
* @throws ArgumentException If a malformed Unicode escape sequence is
* encountered.
*/
static String handleUnicodeEscapes(final String propertiesFilePath,
final int lineNumber,
final StringBuilder buffer)
throws ArgumentException
{
int pos = 0;
while (pos < buffer.length())
{
final char c = buffer.charAt(pos);
if (c == '\\')
{
if (pos <= (buffer.length() - 5))
{
final char nextChar = buffer.charAt(pos+1);
if ((nextChar == 'u') || (nextChar == 'U'))
{
try
{
final String hexDigits = buffer.substring(pos+2, pos+6);
final byte[] bytes = StaticUtils.fromHex(hexDigits);
final int i = ((bytes[0] & 0xFF) << 8) | (bytes[1] & 0xFF);
buffer.setCharAt(pos, (char) i);
for (int j=0; j < 5; j++)
{
buffer.deleteCharAt(pos+1);
}
}
catch (final Exception e)
{
Debug.debugException(e);
throw new ArgumentException(
ERR_PARSER_MALFORMED_UNICODE_ESCAPE.get(propertiesFilePath,
lineNumber),
e);
}
}
else
{
buffer.deleteCharAt(pos);
}
}
}
pos++;
}
return buffer.toString();
}
/**
* Sets the values of any arguments not provided on the command line but
* defined in the properties file.
*
* @param propertyMap A map of properties read from the properties file.
* @param useSubCommand Indicates whether to use the argument parser
* associated with the selected subcommand rather than
* the global argument parser.
*
* @throws ArgumentException If a problem is encountered while examining the
* properties file, or while trying to assign a
* property value to a corresponding argument.
*/
private void setArgsFromPropertiesFile(
final Map> propertyMap,
final boolean useSubCommand)
throws ArgumentException
{
final ArgumentParser p;
if (useSubCommand)
{
p = selectedSubCommand.getArgumentParser();
}
else
{
p = this;
}
for (final Argument a : p.namedArgs)
{
// If the argument was provided on the command line, then that will always
// override anything that might be in the properties file.
if (a.getNumOccurrences() > 0)
{
continue;
}
// If the argument is part of an exclusive argument set, and if one of
// the other arguments in that set was provided on the command line, then
// don't look in the properties file for a value for the argument.
boolean exclusiveArgumentHasValue = false;
exclusiveArgumentLoop:
for (final Set exclusiveArgumentSet : exclusiveArgumentSets)
{
if (exclusiveArgumentSet.contains(a))
{
for (final Argument exclusiveArg : exclusiveArgumentSet)
{
if (exclusiveArg.getNumOccurrences() > 0)
{
exclusiveArgumentHasValue = true;
break exclusiveArgumentLoop;
}
}
}
}
if (exclusiveArgumentHasValue)
{
continue;
}
// If we should use a subcommand, then see if the properties file has a
// property that is specific to the selected subcommand. Then fall back
// to a property that is specific to the tool, and finally fall back to
// checking for a set of values that are generic to any tool that has an
// argument with that name.
List values = null;
if (useSubCommand)
{
values = propertyMap.get(commandName + '.' +
selectedSubCommand.getPrimaryName() + '.' +
a.getIdentifierString());
}
if (values == null)
{
values = propertyMap.get(commandName + '.' + a.getIdentifierString());
}
if (values == null)
{
values = propertyMap.get(a.getIdentifierString());
}
if (values != null)
{
for (final String value : values)
{
if (a instanceof BooleanArgument)
{
// We'll treat this as a BooleanValueArgument.
final BooleanValueArgument bva = new BooleanValueArgument(
a.getShortIdentifier(), a.getLongIdentifier(), false, null,
a.getDescription());
bva.addValue(value);
if (bva.getValue())
{
a.incrementOccurrences();
argumentsSetFromPropertiesFile.add(a.getIdentifierString());
}
}
else
{
a.addValue(value);
a.incrementOccurrences();
argumentsSetFromPropertiesFile.add(a.getIdentifierString());
if (a.isSensitive())
{
argumentsSetFromPropertiesFile.add("***REDACTED***");
}
else
{
argumentsSetFromPropertiesFile.add(value);
}
}
}
}
}
}
/**
* Retrieves lines that make up the usage information for this program,
* optionally wrapping long lines.
*
* @param maxWidth The maximum line width to use for the output. If this is
* less than or equal to zero, then no wrapping will be
* performed.
*
* @return The lines that make up the usage information for this program.
*/
public List getUsage(final int maxWidth)
{
// If a subcommand was selected, then provide usage specific to that
// subcommand.
if (selectedSubCommand != null)
{
return getSubCommandUsage(maxWidth);
}
// First is a description of the command.
final ArrayList lines = new ArrayList<>(100);
lines.addAll(StaticUtils.wrapLine(commandDescription, maxWidth));
lines.add("");
for (final String additionalDescriptionParagraph :
additionalCommandDescriptionParagraphs)
{
lines.addAll(StaticUtils.wrapLine(additionalDescriptionParagraph,
maxWidth));
lines.add("");
}
// If the tool supports subcommands, and if there are fewer than 10
// subcommands, then display them inline.
if ((! subCommands.isEmpty()) && (subCommands.size() < 10))
{
lines.add(INFO_USAGE_SUBCOMMANDS_HEADER.get());
lines.add("");
for (final SubCommand sc : subCommands)
{
final StringBuilder nameBuffer = new StringBuilder();
nameBuffer.append(" ");
final Iterator nameIterator = sc.getNames(false).iterator();
while (nameIterator.hasNext())
{
nameBuffer.append(nameIterator.next());
if (nameIterator.hasNext())
{
nameBuffer.append(", ");
}
}
lines.add(nameBuffer.toString());
for (final String descriptionLine :
StaticUtils.wrapLine(sc.getDescription(), (maxWidth - 4)))
{
lines.add(" " + descriptionLine);
}
lines.add("");
}
}
// Next comes the usage. It may include neither, either, or both of the
// set of options and trailing arguments.
if (! subCommands.isEmpty())
{
lines.addAll(StaticUtils.wrapLine(
INFO_USAGE_SUBCOMMAND_USAGE.get(commandName), maxWidth));
}
else if (namedArgs.isEmpty())
{
if (maxTrailingArgs == 0)
{
lines.addAll(StaticUtils.wrapLine(
INFO_USAGE_NOOPTIONS_NOTRAILING.get(commandName), maxWidth));
}
else
{
lines.addAll(StaticUtils.wrapLine(INFO_USAGE_NOOPTIONS_TRAILING.get(
commandName, trailingArgsPlaceholder), maxWidth));
}
}
else
{
if (maxTrailingArgs == 0)
{
lines.addAll(StaticUtils.wrapLine(
INFO_USAGE_OPTIONS_NOTRAILING.get(commandName), maxWidth));
}
else
{
lines.addAll(StaticUtils.wrapLine(INFO_USAGE_OPTIONS_TRAILING.get(
commandName, trailingArgsPlaceholder), maxWidth));
}
}
if (! namedArgs.isEmpty())
{
lines.add("");
lines.add(INFO_USAGE_OPTIONS_INCLUDE.get());
// If there are any argument groups, then collect the arguments in those
// groups.
boolean hasRequired = false;
final LinkedHashMap> argumentsByGroup =
new LinkedHashMap<>(StaticUtils.computeMapCapacity(10));
final ArrayList argumentsWithoutGroup =
new ArrayList<>(namedArgs.size());
final ArrayList usageArguments =
new ArrayList<>(namedArgs.size());
for (final Argument a : namedArgs)
{
if (a.isHidden())
{
// This argument shouldn't be included in the usage output.
continue;
}
if (a.isRequired() && (! a.hasDefaultValue()))
{
hasRequired = true;
}
final String argumentGroup = a.getArgumentGroupName();
if (argumentGroup == null)
{
if (a.isUsageArgument())
{
usageArguments.add(a);
}
else
{
argumentsWithoutGroup.add(a);
}
}
else
{
List groupArgs = argumentsByGroup.get(argumentGroup);
if (groupArgs == null)
{
groupArgs = new ArrayList<>(10);
argumentsByGroup.put(argumentGroup, groupArgs);
}
groupArgs.add(a);
}
}
// Iterate through the defined argument groups and display usage
// information for each of them.
for (final Map.Entry> e :
argumentsByGroup.entrySet())
{
lines.add("");
lines.add(" " + e.getKey());
lines.add("");
for (final Argument a : e.getValue())
{
getArgUsage(a, lines, true, maxWidth);
}
}
if (! argumentsWithoutGroup.isEmpty())
{
if (argumentsByGroup.isEmpty())
{
for (final Argument a : argumentsWithoutGroup)
{
getArgUsage(a, lines, false, maxWidth);
}
}
else
{
lines.add("");
lines.add(" " + INFO_USAGE_UNGROUPED_ARGS.get());
lines.add("");
for (final Argument a : argumentsWithoutGroup)
{
getArgUsage(a, lines, true, maxWidth);
}
}
}
if (! usageArguments.isEmpty())
{
if (argumentsByGroup.isEmpty())
{
for (final Argument a : usageArguments)
{
getArgUsage(a, lines, false, maxWidth);
}
}
else
{
lines.add("");
lines.add(" " + INFO_USAGE_USAGE_ARGS.get());
lines.add("");
for (final Argument a : usageArguments)
{
getArgUsage(a, lines, true, maxWidth);
}
}
}
if (hasRequired)
{
lines.add("");
if (argumentsByGroup.isEmpty())
{
lines.add("* " + INFO_USAGE_ARG_IS_REQUIRED.get());
}
else
{
lines.add(" * " + INFO_USAGE_ARG_IS_REQUIRED.get());
}
}
}
return lines;
}
/**
* Retrieves lines that make up the usage information for the selected
* subcommand.
*
* @param maxWidth The maximum line width to use for the output. If this is
* less than or equal to zero, then no wrapping will be
* performed.
*
* @return The lines that make up the usage information for the selected
* subcommand.
*/
private List getSubCommandUsage(final int maxWidth)
{
// First is a description of the subcommand.
final ArrayList lines = new ArrayList<>(100);
lines.addAll(
StaticUtils.wrapLine(selectedSubCommand.getDescription(), maxWidth));
lines.add("");
// Next comes the usage.
lines.addAll(StaticUtils.wrapLine(INFO_SUBCOMMAND_USAGE_OPTIONS.get(
commandName, selectedSubCommand.getPrimaryName()), maxWidth));
final ArgumentParser parser = selectedSubCommand.getArgumentParser();
if (! parser.namedArgs.isEmpty())
{
lines.add("");
lines.add(INFO_USAGE_OPTIONS_INCLUDE.get());
// If there are any argument groups, then collect the arguments in those
// groups.
boolean hasRequired = false;
final LinkedHashMap> argumentsByGroup =
new LinkedHashMap<>(StaticUtils.computeMapCapacity(10));
final ArrayList argumentsWithoutGroup =
new ArrayList<>(parser.namedArgs.size());
final ArrayList usageArguments =
new ArrayList<>(parser.namedArgs.size());
for (final Argument a : parser.namedArgs)
{
if (a.isHidden())
{
// This argument shouldn't be included in the usage output.
continue;
}
if (a.isRequired() && (! a.hasDefaultValue()))
{
hasRequired = true;
}
final String argumentGroup = a.getArgumentGroupName();
if (argumentGroup == null)
{
if (a.isUsageArgument())
{
usageArguments.add(a);
}
else
{
argumentsWithoutGroup.add(a);
}
}
else
{
List groupArgs = argumentsByGroup.get(argumentGroup);
if (groupArgs == null)
{
groupArgs = new ArrayList<>(10);
argumentsByGroup.put(argumentGroup, groupArgs);
}
groupArgs.add(a);
}
}
// Iterate through the defined argument groups and display usage
// information for each of them.
for (final Map.Entry> e :
argumentsByGroup.entrySet())
{
lines.add("");
lines.add(" " + e.getKey());
lines.add("");
for (final Argument a : e.getValue())
{
getArgUsage(a, lines, true, maxWidth);
}
}
if (! argumentsWithoutGroup.isEmpty())
{
if (argumentsByGroup.isEmpty())
{
for (final Argument a : argumentsWithoutGroup)
{
getArgUsage(a, lines, false, maxWidth);
}
}
else
{
lines.add("");
lines.add(" " + INFO_USAGE_UNGROUPED_ARGS.get());
lines.add("");
for (final Argument a : argumentsWithoutGroup)
{
getArgUsage(a, lines, true, maxWidth);
}
}
}
if (! usageArguments.isEmpty())
{
if (argumentsByGroup.isEmpty())
{
for (final Argument a : usageArguments)
{
getArgUsage(a, lines, false, maxWidth);
}
}
else
{
lines.add("");
lines.add(" " + INFO_USAGE_USAGE_ARGS.get());
lines.add("");
for (final Argument a : usageArguments)
{
getArgUsage(a, lines, true, maxWidth);
}
}
}
if (hasRequired)
{
lines.add("");
if (argumentsByGroup.isEmpty())
{
lines.add("* " + INFO_USAGE_ARG_IS_REQUIRED.get());
}
else
{
lines.add(" * " + INFO_USAGE_ARG_IS_REQUIRED.get());
}
}
}
return lines;
}
/**
* Adds usage information for the provided argument to the given list.
*
* @param a The argument for which to get the usage information.
* @param lines The list to which the resulting lines should be added.
* @param indent Indicates whether to indent each line.
* @param maxWidth The maximum width of each line, in characters.
*/
private static void getArgUsage(final Argument a, final List lines,
final boolean indent, final int maxWidth)
{
final StringBuilder argLine = new StringBuilder();
if (indent && (maxWidth > 10))
{
if (a.isRequired() && (! a.hasDefaultValue()))
{
argLine.append(" * ");
}
else
{
argLine.append(" ");
}
}
else if (a.isRequired() && (! a.hasDefaultValue()))
{
argLine.append("* ");
}
boolean first = true;
for (final Character c : a.getShortIdentifiers(false))
{
if (first)
{
argLine.append('-');
first = false;
}
else
{
argLine.append(", -");
}
argLine.append(c);
}
for (final String s : a.getLongIdentifiers(false))
{
if (first)
{
argLine.append("--");
first = false;
}
else
{
argLine.append(", --");
}
argLine.append(s);
}
final String valuePlaceholder = a.getValuePlaceholder();
if (valuePlaceholder != null)
{
argLine.append(' ');
argLine.append(valuePlaceholder);
}
// If we need to wrap the argument line, then align the dashes on the left
// edge.
int subsequentLineWidth = maxWidth - 4;
if (subsequentLineWidth < 4)
{
subsequentLineWidth = maxWidth;
}
final List identifierLines =
StaticUtils.wrapLine(argLine.toString(), maxWidth,
subsequentLineWidth);
for (int i=0; i < identifierLines.size(); i++)
{
if (i == 0)
{
lines.add(identifierLines.get(0));
}
else
{
lines.add(" " + identifierLines.get(i));
}
}
// The description should be wrapped, if necessary. We'll also want to
// indent it (unless someone chose an absurdly small wrap width) to make
// it stand out from the argument lines.
final String description = a.getDescription();
if (maxWidth > 10)
{
final String indentString;
if (indent)
{
indentString = " ";
}
else
{
indentString = " ";
}
final List descLines = StaticUtils.wrapLine(description,
(maxWidth-indentString.length()));
for (final String s : descLines)
{
lines.add(indentString + s);
}
}
else
{
lines.addAll(StaticUtils.wrapLine(description, maxWidth));
}
}
/**
* Writes usage information for this program to the provided output stream
* using the UTF-8 encoding, optionally wrapping long lines.
*
* @param outputStream The output stream to which the usage information
* should be written. It must not be {@code null}.
* @param maxWidth The maximum line width to use for the output. If
* this is less than or equal to zero, then no wrapping
* will be performed.
*
* @throws IOException If an error occurs while attempting to write to the
* provided output stream.
*/
public void getUsage(final OutputStream outputStream, final int maxWidth)
throws IOException
{
final List usageLines = getUsage(maxWidth);
for (final String s : usageLines)
{
outputStream.write(StaticUtils.getBytes(s));
outputStream.write(StaticUtils.EOL_BYTES);
}
}
/**
* Retrieves a string representation of the usage information.
*
* @param maxWidth The maximum line width to use for the output. If this is
* less than or equal to zero, then no wrapping will be
* performed.
*
* @return A string representation of the usage information
*/
public String getUsageString(final int maxWidth)
{
final StringBuilder buffer = new StringBuilder();
getUsageString(buffer, maxWidth);
return buffer.toString();
}
/**
* Appends a string representation of the usage information to the provided
* buffer.
*
* @param buffer The buffer to which the information should be appended.
* @param maxWidth The maximum line width to use for the output. If this is
* less than or equal to zero, then no wrapping will be
* performed.
*/
public void getUsageString(final StringBuilder buffer, final int maxWidth)
{
for (final String line : getUsage(maxWidth))
{
buffer.append(line);
buffer.append(StaticUtils.EOL);
}
}
/**
* Retrieves a string representation of this argument parser.
*
* @return A string representation of this argument parser.
*/
@Override()
public String toString()
{
final StringBuilder buffer = new StringBuilder();
toString(buffer);
return buffer.toString();
}
/**
* Appends a string representation of this argument parser to the provided
* buffer.
*
* @param buffer The buffer to which the information should be appended.
*/
public void toString(final StringBuilder buffer)
{
buffer.append("ArgumentParser(commandName='");
buffer.append(commandName);
buffer.append("', commandDescription={");
buffer.append('\'');
buffer.append(commandDescription);
buffer.append('\'');
if (additionalCommandDescriptionParagraphs != null)
{
for (final String additionalParagraph :
additionalCommandDescriptionParagraphs)
{
buffer.append(", '");
buffer.append(additionalParagraph);
buffer.append('\'');
}
}
buffer.append("}, minTrailingArgs=");
buffer.append(minTrailingArgs);
buffer.append(", maxTrailingArgs=");
buffer.append(maxTrailingArgs);
if (trailingArgsPlaceholder != null)
{
buffer.append(", trailingArgsPlaceholder='");
buffer.append(trailingArgsPlaceholder);
buffer.append('\'');
}
buffer.append(", namedArgs={");
final Iterator iterator = namedArgs.iterator();
while (iterator.hasNext())
{
iterator.next().toString(buffer);
if (iterator.hasNext())
{
buffer.append(", ");
}
}
buffer.append('}');
if (! subCommands.isEmpty())
{
buffer.append(", subCommands={");
final Iterator subCommandIterator = subCommands.iterator();
while (subCommandIterator.hasNext())
{
subCommandIterator.next().toString(buffer);
if (subCommandIterator.hasNext())
{
buffer.append(", ");
}
}
buffer.append('}');
}
buffer.append(')');
}
}