SubCommandArgumentParser.java revision 49026dcadd8edca32a2628f768a9e0d92a07996e
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at
* trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
* add the following below this CDDL HEADER, with the fields enclosed
* by brackets "[]" replaced with your own identifying information:
* Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*
*
* Portions Copyright 2006-2007 Sun Microsystems, Inc.
*/
/**
* This class defines a variant of the argument parser that can be used with
* applications that use subcommands to customize their behavior and that have a
* different set of options per subcommand (e.g, "cvs checkout" takes different
* options than "cvs commit"). This parser also has the ability to use global
* options that will always be applicable regardless of the subcommand in
* addition to the subcommand-specific arguments. There must not be any
* conflicts between the global options and the option for any subcommand, but
* it is allowed to re-use subcommand-specific options for different purposes
* between different subcommands.
*/
public class SubCommandArgumentParser
{
// The argument that will be used to trigger the display of usage information.
private Argument usageArgument;
// Indicates whether subcommand and long argument names should be treated in a
// case-sensitive manner.
private boolean longArgumentsCaseSensitive;
// Indicates whether the usage information has been displayed.
private boolean usageDisplayed;
// The set of global arguments defined for this parser, referenced by short
// ID.
// The set of global arguments defined for this parser, referenced by
// argument name.
// The set of global arguments defined for this parser, referenced by long
// ID.
// The set of subcommands defined for this parser, referenced by subcommand
// name.
// The total set of global arguments defined for this parser.
// The output stream to which usage information should be printed.
private OutputStream usageOutputStream;
// The fully-qualified name of the Java class that should be invoked to launch
// the program with which this argument parser is associated.
private String mainClassName;
// A human-readable description for the tool, which will be included when
// displaying usage information.
private String toolDescription;
// The raw set of command-line arguments that were provided.
private String[] rawArguments;
// The subcommand requested by the user as part of the command-line arguments.
private SubCommand subCommand;
/**
* Creates a new instance of this subcommand argument parser with no
* arguments.
*
* @param mainClassName The fully-qualified name of the Java
* class that should be invoked to launch
* the program with which this argument
* parser is associated.
* @param toolDescription A human-readable description for the
* tool, which will be included when
* displaying usage information.
* @param longArgumentsCaseSensitive Indicates whether subcommand and long
* argument names should be treated in a
* case-sensitive manner.
*/
boolean longArgumentsCaseSensitive)
{
this.mainClassName = mainClassName;
this.toolDescription = toolDescription;
usageDisplayed = false;
rawArguments = null;
subCommand = null;
}
/**
* Retrieves the fully-qualified name of the Java class that should be invoked
* to launch the program with which this argument parser is associated.
*
* @return The fully-qualified name of the Java class that should be invoked
* to launch the program with which this argument parser is
* associated.
*/
public String getMainClassName()
{
return mainClassName;
}
/**
* Retrieves a human-readable description for this tool, which should be
* included at the top of the command-line usage information.
*
* @return A human-readable description for this tool, or {@code null} if
* none is available.
*/
public String getToolDescription()
{
return toolDescription;
}
/**
* Indicates whether subcommand names and long argument strings should be
* treated in a case-sensitive manner.
*
* @return <CODE>true</CODE> if subcommand names and long argument strings
* should be treated in a case-sensitive manner, or
* <CODE>false</CODE> if they should not.
*/
public boolean longArgumentsCaseSensitive()
{
return longArgumentsCaseSensitive;
}
/**
* Retrieves the list of all global arguments that have been defined for this
* argument parser.
*
* @return The list of all global arguments that have been defined for this
* argument parser.
*/
{
return globalArgumentList;
}
/**
* Indicates whether this argument parser contains a global argument with the
* specified name.
*
* @param argumentName The name for which to make the determination.
*
* @return <CODE>true</CODE> if a global argument exists with the specified
* name, or <CODE>false</CODE> if not.
*/
{
}
/**
* Retrieves the global argument with the specified name.
*
* @param name The name of the global argument to retrieve.
*
* @return The global argument with the specified name, or <CODE>null</CODE>
* if there is no such argument.
*/
{
}
/**
* Retrieves the set of global arguments mapped by the short identifier that
* may be used to reference them. Note that arguments that do not have a
* short identifier will not be present in this list.
*
* @return The set of global arguments mapped by the short identifier that
* may be used to reference them.
*/
{
return globalShortIDMap;
}
/**
* Indicates whether this argument parser has a global argument with the
* specified short ID.
*
* @param shortID The short ID character for which to make the
* determination.
*
* @return <CODE>true</CODE> if a global argument exists with the specified
* short ID, or <CODE>false</CODE> if not.
*/
{
}
/**
* Retrieves the global argument with the specified short identifier.
*
* @param shortID The short identifier for the global argument to retrieve.
*
* @return The global argument with the specified short identifier, or
* <CODE>null</CODE> if there is no such argument.
*/
{
}
/**
* Retrieves the set of global arguments mapped by the long identifier that
* may be used to reference them. Note that arguments that do not have a long
* identifier will not be present in this list.
*
* @return The set of global arguments mapped by the long identifier that may
* be used to reference them.
*/
{
return globalLongIDMap;
}
/**
* Indicates whether this argument parser has a global argument with the
* specified long ID.
*
* @param longID The long ID string for which to make the determination.
*
* @return <CODE>true</CODE> if a global argument exists with the specified
* long ID, or <CODE>false</CODE> if not.
*/
{
}
/**
* Retrieves the global argument with the specified long identifier.
*
* @param longID The long identifier for the global argument to retrieve.
*
* @return The global argument with the specified long identifier, or
* <CODE>null</CODE> if there is no such argument.
*/
{
}
/**
* Retrieves the set of subcommands defined for this argument parser,
* referenced by subcommand name.
*
* @return The set of subcommands defined for this argument parser,
* referenced by subcommand name.
*/
{
return subCommands;
}
/**
* Indicates whether this argument parser has a subcommand with the specified
* name.
*
* @param name The subcommand name for which to make the determination.
*
* @return <CODE>true</CODE> if this argument parser has a subcommand with
* the specified name, or <CODE>false</CODE> if it does not.
*/
{
}
/**
* Retrieves the subcommand with the specified name.
*
* @param name The name of the subcommand to retrieve.
*
* @return The subcommand with the specified name, or <CODE>null</CODE> if no
* such subcommand is defined.
*/
{
}
/**
* Retrieves the subcommand that was selected in the set of command-line
* arguments.
*
* @return The subcommand that was selected in the set of command-line
* arguments, or <CODE>null</CODE> if none was selected.
*/
public SubCommand getSubCommand()
{
return subCommand;
}
/**
* Retrieves the raw set of arguments that were provided.
*
* @return The raw set of arguments that were provided, or <CODE>null</CODE>
* if the argument list has not yet been parsed.
*/
public String[] getRawArguments()
{
return rawArguments;
}
/**
* Adds the provided argument to the set of global arguments handled by this
* parser.
*
* @param argument The argument to be added.
*
* @throws ArgumentException If the provided argument conflicts with another
* global or subcommand argument that has already
* been defined.
*/
throws ArgumentException
{
{
}
{
{
}
}
{
{
argumentName, name);
}
{
{
}
}
}
{
if (! longArgumentsCaseSensitive)
{
}
{
}
{
{
cmdName);
}
}
}
{
}
{
}
}
/**
* Sets the provided argument as one which will automatically trigger the
* output of usage information if it is provided on the command line and no
* further argument validation will be performed. Note that the caller will
* still need to add this argument to the parser with the
* <CODE>addArgument</CODE> method, and the argument should not be required
* and should not take a value. Also, the caller will still need to check
* for the presence of the usage argument after calling
* <CODE>parseArguments</CODE> to know that no further processing will be
* required.
*
* @param argument The argument whose presence should automatically
* trigger the display of usage information.
*/
{
}
/**
* Sets the provided argument as one which will automatically trigger the
* output of usage information if it is provided on the command line and no
* further argument validation will be performed. Note that the caller will
* still need to add this argument to the parser with the
* <CODE>addArgument</CODE> method, and the argument should not be required
* and should not take a value. Also, the caller will still need to check
* for the presence of the usage argument after calling
* <CODE>parseArguments</CODE> to know that no further processing will be
* required.
*
* @param argument The argument whose presence should automatically
* trigger the display of usage information.
* @param outputStream The output stream to which the usage information
* should be written.
*/
{
}
/**
* Adds the provided subcommand to this argument parser. This is only
* intended for use by the <CODE>SubCommand</CODE> constructor and does not
* do any validation of its own to ensure that there are no conflicts with the
* subcommand or any of its arguments.
*
* @param subCommand The subcommand to add to this argument parser.
*/
{
}
/**
* Parses the provided set of arguments and updates the information associated
* with this parser accordingly.
*
* @param rawArguments The raw set of arguments to parse.
*
* @throws ArgumentException If a problem was encountered while parsing the
* provided arguments.
*/
throws ArgumentException
{
}
/**
* Parses the provided set of arguments and updates the information associated
* with this parser accordingly. Default values for unspecified arguments
* may be read from the specified properties file.
*
* @param rawArguments The set of raw arguments to parse.
* @param propertiesFile The path to the properties file to use to
* obtain default values for unspecified
* properties.
* @param requirePropertiesFile Indicates whether the parsing should fail if
* the provided properties file does not exist
* or is not accessible.
*
* @throws ArgumentException If a problem was encountered while parsing the
* provided arguments or interacting with the
* properties file.
*/
boolean requirePropertiesFile)
throws ArgumentException
{
this.rawArguments = rawArguments;
try
{
Properties p = new Properties();
argumentProperties = p;
}
catch (Exception e)
{
{
}
}
}
/**
* Parses the provided set of arguments and updates the information associated
* with this parser accordingly. Default values for unspecified arguments may
* be read from the specified properties if any are provided.
*
* @param rawArguments The set of raw arguments to parse.
* @param argumentProperties A set of properties that may be used to provide
* default values for arguments not included in
* the given raw arguments.
*
* @throws ArgumentException If a problem was encountered while parsing the
* provided arguments.
*/
throws ArgumentException
{
this.rawArguments = rawArguments;
for (int i=0; i < numArguments; i++)
{
{
// This is not legal because we don't allow unnamed trailing arguments
// in this parser.
}
{
// This indicates that we are using the long name to reference the
// argument. It may be in any of the following forms:
// --name
// --name value
// --name=value
if (equalPos < 0)
{
// This is fine. The value is not part of the argument name token.
}
else if (equalPos == 0)
{
// The argument starts with "--=", which is not acceptable.
}
else
{
// The argument is in the form --name=value, so parse them both out.
}
// If we're not case-sensitive, then convert the name to lowercase.
if (! longArgumentsCaseSensitive)
{
}
// See if the specified name references a global argument. If not, then
// see if it references a subcommand argument.
if (a == null)
{
if (subCommand == null)
{
{
// "--help" will always be interpreted as requesting usage
// information.
try
{
} catch (Exception e) {}
return;
}
else
{
// There is no such global argument.
}
}
else
{
if (a == null)
{
{
// "--help" will always be interpreted as requesting usage
// information.
try
{
} catch (Exception e) {}
return;
}
else
{
// There is no such global or subcommand argument.
}
}
}
}
a.setPresent(true);
// If this is the usage argument, then immediately stop and print
// usage information.
if ((usageArgument != null) &&
{
try
{
} catch (Exception e) {}
return;
}
// See if the argument takes a value. If so, then make sure one was
// provided. If not, then make sure none was provided.
if (a.needsValue())
{
{
if ((i+1) == numArguments)
{
}
argValue = rawArguments[++i];
}
{
}
// If the argument already has a value, then make sure it is
// acceptable to have more than one.
if (a.hasValue() && (! a.isMultiValued()))
{
}
}
else
{
{
}
}
}
{
// This indicates that we are using the 1-character name to reference
// the argument. It may be in any of the following forms:
// -n
// -nvalue
// -n value
{
}
{
}
else
{
}
// Get the argument with the specified short ID. It may be either a
// global argument or a subcommand-specific argument.
if (a == null)
{
if (subCommand == null)
{
if (argCharacter == '?')
{
// "-?" will always be interpreted as requesting usage.
try
{
} catch (Exception e) {}
return;
}
else
{
// There is no such argument registered.
}
}
else
{
if (a == null)
{
if (argCharacter == '?')
{
// "-?" will always be interpreted as requesting usage.
try
{
} catch (Exception e) {}
return;
}
else
{
// There is no such argument registered.
}
}
}
}
a.setPresent(true);
// If this is the usage argument, then immediately stop and print
// usage information.
if ((usageArgument != null) &&
{
try
{
} catch (Exception e) {}
return;
}
// See if the argument takes a value. If so, then make sure one was
// provided. If not, then make sure none was provided.
if (a.needsValue())
{
{
if ((i+1) == numArguments)
{
int msgID =
}
argValue = rawArguments[++i];
}
{
}
// If the argument already has a value, then make sure it is
// acceptable to have more than one.
if (a.hasValue() && (! a.isMultiValued()))
{
}
}
else
{
{
// If we've gotten here, then it means that we're in a scenario like
// "-abc" where "a" is a valid argument that doesn't take a value.
// However, this could still be valid if all remaining characters in
// the value are also valid argument characters that don't take
// values.
for (int j=0; j < valueLength; j++)
{
if (b == null)
{
if (subCommand == null)
{
int msgID =
}
else
{
b = subCommand.getArgument(c);
if (b == null)
{
}
}
}
if (b.needsValue())
{
// This means we're in a scenario like "-abc" where b is a
// valid argument that takes a value. We don't support that.
}
else
{
b.setPresent(true);
// If this is the usage argument, then immediately stop and
// print usage information.
if ((usageArgument != null) &&
{
try
{
} catch (Exception e) {}
return;
}
}
}
}
}
}
else
{
// It's not a short or long identifier, so check to see if it is a
// subcommand name. If not, then it's invalid. If so, then make sure
// that it was the only subcommand provided.
if (! longArgumentsCaseSensitive)
{
}
{
}
else if (subCommand == null)
{
subCommand = sc;
}
else
{
}
}
}
// Iterate through all the global arguments and make sure that they have
// values or a suitable default is available.
for (Argument a : globalArgumentList)
{
if ((! a.isPresent()) && a.needsValue())
{
// See if there is a default value in the properties that can be used.
boolean valueSet = false;
{
{
valueSet = true;
}
}
// If there is still no value, then see if the argument defines a
// default.
{
a.addValue(a.getDefaultValue());
valueSet = true;
}
// If there is still no value and the argument is required, then that's
// a problem.
if ((! valueSet) && a.isRequired())
{
}
}
}
// Iterate through all the subcommand-specific arguments and make sure that
// they have values or a suitable default is available.
if (subCommand != null)
{
{
if ((! a.isPresent()) && a.needsValue())
{
// See if there is a default value in the properties that can be used.
boolean valueSet = false;
{
{
valueSet = true;
}
}
// If there is still no value, then see if the argument defines a
// default.
{
a.addValue(a.getDefaultValue());
valueSet = true;
}
// If there is still no value and the argument is required, then
// that's a problem.
if ((! valueSet) && a.isRequired())
{
}
}
}
}
}
/**
* Appends complete usage information to the provided buffer. It will include
* information about global options as well as all subcommand-specific
* options. The output will be somewhat compressed in that it will not
* include any of the descriptions for any of the arguments.
*
* @param buffer The buffer to which the usage information should be
* appended.
*/
{
usageDisplayed = true;
{
}
{
}
else
{
}
int indentNb = 0;
{
{
}
}
indentNb++;
{
{
}
}
for (Argument a : globalArgumentList)
{
if (a.isHidden())
{
continue;
}
if (a.needsValue())
{
if (a == null)
{
value = " {value}";
}
else
{
}
}
else
{
value = "";
}
if (shortIDChar != null)
{
if (longIDString != null)
{
}
}
else
{
if (longIDString != null)
{
}
}
}
}
/**
* Appends usage information for the specified subcommand to the provided
* buffer.
*
* @param buffer The buffer to which the usage information should be
* appended.
* @param subCommand The subcommand for which to display the usage
* information.
*/
{
usageDisplayed = true;
{
}
else
{
}
if ( ! globalArgumentList.isEmpty())
{
}
{
}
{
// If this argument is hidden, then skip it.
if (a.isHidden())
{
continue;
}
// for the argument.
{
if (a.needsValue())
{
}
{
if (a.needsValue())
{
}
if (lineLength > 80)
{
}
else
{
}
}
}
else
{
{
if (a.needsValue())
{
}
}
}
if (a.isRequired())
{
}
}
}
/**
* Retrieves a string containing usage information based on the defined
* arguments.
*
* @return A string containing usage information based on the defined
* arguments.
*/
{
if (subCommand == null)
{
}
else
{
}
}
/**
* Writes usage information based on the defined arguments to the provided
* output stream.
*
* @param outputStream The output stream to which the usage information
* should be written.
*
* @throws IOException If a problem occurs while attempting to write the
* usage information to the provided output stream.
*/
throws IOException
{
if (subCommand == null)
{
}
else
{
}
}
/**
* Indicates whether the usage information has been displayed to the end user
* either by an explicit argument like "-H" or "--help", or by a built-in
* argument like "-?".
*
* @return {@code true} if the usage information has been displayed, or
* {@code false} if not.
*/
public boolean usageDisplayed()
{
return usageDisplayed;
}
/**
* Write one or more lines with the description of the argument. We will
* indent the description five characters and try our best to wrap at or
* before column 79 so it will be friendly to 80-column displays.
*/
{
{
}
else
{
while (s.length() > actualSize)
{
if (spacePos > 0)
{
}
else
{
// There are no spaces in the first actualSize -1 columns. See
// if there is one after that point. If so, then break there.
// If not, then don't break at all.
if (spacePos > 0)
{
}
else
{
s = "";
}
}
}
if (s.length() > 0)
{
}
}
}
}