From Andrey
package com.taitl.world.commandline;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/*
* Copyright 2011 Taitl Design. All rights reserved.
*/
/**
* CommandLineParser class takes command line arguments and parses them into a
* map of [switch, list of values] pairs. A switch on the command line can have
* no, one, or many values. The command line can also have arguments that are
* not related to any switches.
* <p>
* Usage:
* <p>
*
* <pre>
* public static void main(String[] arguments)
* {
* CommandLineParser commandLineParser = new CommandLineParser();
* commandLineParser.setPossibleSwitches("--version(0) --usage(0)"
* + " --help(0) --multi(*)");
* commandLineParser.setEmptyCommandLineSwitch("--usage");
* commandLineParser.setImplicitSwitch("--file(1)");
* commandLineParser.setArguments(arguments);
* // ...same as commandLineParser.parse(arguments);
*
* if (commandLineParser.isUsageRequested())
* {
* System.out.println(usageText);
* }
* else if (commandLineParser.isVersionRequested())
* {
* System.out.println(VERSION_STRING);
* }
* else if (commandLineParser.isHelpRequested())
* {
* System.out.println(helpText);
* }
* else if (commandLineParser.isSwitchPresent("--multi"))
* {
* // Process custom switch
* List<String> switchValues = commandLineParser
* .getSwitchValues("--multi");
* String[] arguments = commandLineParser.getSwitchlessArguments();
* // ...
* }
* }
* </pre>
*
* @author Andrey Potekhin, 03/08/2011
*/
public class CommandLineParser
{
/** Default usage switch name: --usage. */
static final String DEFAULT_USAGE_SWITCH = "--usage";
/** Default help switch name: --help. */
static final String DEFAULT_HELP_SWITCH = "--help";
/** Default version switch name: --version. */
static final String DEFAULT_VERSION_SWITCH = "--version";
/** Default switch name prefixes: -- and -. */
static final String DEFAULT_SWITCH_PREFIXES = "(--|-)";
/** Default value of usage switch, defaulted to --usage. */
private String usageSwitchName = DEFAULT_USAGE_SWITCH;
/** Default value of usage switch, defaulted to --help. */
private String helpSwitchName = DEFAULT_HELP_SWITCH;
/** Default value of usage switch, defaulted to --version. */
private String versionSwitchName = DEFAULT_VERSION_SWITCH;
/** Default value of switch prefixes, defaulted to (--|-). */
private String switchPrefixesMask = DEFAULT_SWITCH_PREFIXES;
/** The original command line. */
private String originalCommandLine = null;
/** The original arguments array, as usually passed in the main() method. */
private String[] arguments = null;
/**
* The command line arguments that do not have a switch corresponding to
* them.
*/
private String[] switchlessArguments = null;
/**
* Has this object been properly initialized? This is false unless both
* setPossibleSwitches() and setArguments()/parse() has been called.
*/
private boolean isInitialized = false;
/** The set of possible switches. */
private Set<String> possibleSwitches = new LinkedHashSet<String>();
/**
* The default (implicit) switch to which all switchless arguments are
* attributed.
*
* @see CommandLineParser#setImplicitSwitch
*/
private String implicitSwitch = null;
/**
* Main structure to hold information about the parsed command line switches
* and their values and switchless arguments.
*/
private Map<String, List<String>> switchMap = new LinkedHashMap<String, List<String>>();
/** Command line switches as result of parsing. */
private Map<String, Switch> switches = new LinkedHashMap<String, Switch>();
/**
* Default constructor.
*/
public CommandLineParser()
{
originalCommandLine = null;
isInitialized = false;
}
/**
* Sets command line arguments form the array of arguments passed to Java
* program's main() method, and parses them into map of [switch-list of
* values] pairs by calling
* <p>
* According to Java documentation, the arguments that are passed to mail()
* method conform to the following rules:
* <p>
* <p>
* - On the command line, Java program arguments are separated by spaces and
* tabs, except when an argument is enclosed by double-quotes.
* <p>
* - Leading and trailing whitespace characters are removed from the values
* stored in the arguments array.
* <p>
* - The double-quoted argument is not broken into several separate
* arguments.
* <p>
* - For arguments enclosed with double-quotes, the double-quotes are
* removed.
* <p>
*
* @param args
* Command line arguments to parse.
*/
public final void setArguments(final String[] args)
{
forbid(args == null, "Args argument must not be null.");
StringBuffer commandLine = new StringBuffer();
String arg;
for (String arg2 : args)
{
forbid(arg2 == null, "Null value in argument array.");
arg = arg2.trim();
// If argument has spaces in it, enclose it in double quotes
if (arg.matches(".*\\s.*"))
{
arg = "\"" + arg + "\"";
}
if (commandLine.length() > 0 && arg.length() > 0)
{
commandLine.append(" ");
}
commandLine.append(arg);
}
originalCommandLine = commandLine.toString();
arguments = args;
switches.clear();
switchlessArguments = null;
// Uninitialize the object
isInitialized = false;
// Immediately parse
parse();
}
/**
* An alternative to <code>setArguments()</code> method, sets arguments
* using a command line string.
* <p>
* According to Java documentation, the arguments that are passed to mail()
* method conform to the following rules:
* <p>
* <p>
* - On the command line, Java program arguments are separated by spaces and
* tabs, except when an argument is enclosed by double-quotes.
* <p>
* - Leading and trailing whitespace characters are removed from the values
* stored in the arguments array.
* <p>
* - The double-quoted argument is not broken into several separate
* arguments.
* <p>
* - For arguments enclosed with double-quotes, the double-quotes are
* removed.
* <p>
*
* @param commandLine
* Command line to parse.
*/
public void setCommandLine(String commandLine)
{
forbidNullString(commandLine, "commandLine");
originalCommandLine = commandLine;
// String[] args = commandLine.split("\\s");
// Go over command line, splitting it into whitespace-separated
// arguments, preserving arguments enclosed with double quotes,
// treating them as a single argument, while also removing their
// enclosing double quotes.
boolean insideDoubleQuotes = false;
boolean inWord = false;
boolean isWhitespace = false;
StringBuffer arg = new StringBuffer();
List<String> args = new ArrayList<String>();
for (int i = 0; i < commandLine.length(); i++)
{
char c = commandLine.charAt(i);
boolean finalizeArgument = false;
boolean isClosingDoubleQuote = false;
if (c == '"')
{
if (!insideDoubleQuotes)
{
insideDoubleQuotes = true;
continue;
}
else
{
insideDoubleQuotes = false;
isClosingDoubleQuote = true;
finalizeArgument = true;
}
}
if (insideDoubleQuotes)
{
arg.append(c);
continue;
}
isWhitespace = Character.isWhitespace(c);
if (isWhitespace)
{
if (inWord)
{
inWord = false;
finalizeArgument = true;
}
}
else
{
if (!inWord && !isClosingDoubleQuote)
{
inWord = true;
}
}
if (inWord)
{
arg.append(c);
}
if (finalizeArgument)
{
// Finalize argument
args.add(arg.toString());
arg.setLength(0);
}
}
// Finalize last argument
if (arg.length() > 0)
{
args.add(arg.toString());
}
// Convert to String[]
String[] argArray = new String[args.size()];
for (int i = 0; i < args.size(); i++)
{
argArray[i] = args.get(i);
}
// Immediately parse
setArguments(argArray);
}
/**
* Gets command line arguments previously set by call to
* <code> setArguments()</code> or <code>setCommandLine()</code>.
* <p>
*
* @return String array of arguments previously set by a call to
* setArguments() or setCommandLine(). Throws IllegalStateException
* if user tries to get arguments that have not yet been set.
*/
public final String[] getArguments()
{
forbidState(arguments == null,
"You must call setArguments() or setCommandLine() before calling this method.");
return arguments;
}
/**
* Returns the set of possible command line switches previously specified
* with a call to setPossibleSwitches().
*
* @return The set of possible switches previously specified with a call to
* setPossibleSwitches().
*/
public Set<String> getPossibleSwitches()
{
forbidState(possibleSwitches.isEmpty(),
"You must call setPossibleSwitches() before calling this method.");
return possibleSwitches;
}
/**
* This method lets the CommandLineParser object know of the legitimate
* switches. You are required to call this method after you constructed the
* CommandLineParser object, and before proceeding with any further work
* with it.
* <p>
* Each switch is succeed by parenthesis indicating the potential number of
* it arguments, for example:
* <p>
* (0) - no arguments,
* <p>
* (1) - one argument,
* <p>
* (0-3) - from zero to three arguments,
* <p>
* (*) - any number of arguments,
* <p>
* (3-*) - from three to any greater number of arguments, and so on.
* <p>
* Example:
* <p>
* <code>setPossibleSwitches("--version(0) --usage(0)"
* + " --help(0-1) --multi(1-*)");</code>
*
* @param possibleSwitchesList
* Space-separated list of allowed command line switches, each of
* which is followed by parenthesized number of switch values.
* This can be either a precise number, a min-max expression, or
* expression involving an asterisk (*), which stands for any
* number of arguments.
*/
public final void setPossibleSwitches(String possibleSwitchesList)
{
forbidEmptyString(possibleSwitchesList, "possibleSwitchesList");
// Forget old switches that were specified earlier
possibleSwitches.clear();
// Parse comma-separated list into hash map
for (String switchSpecification : possibleSwitchesList.split(" "))
{
addPossibleSwitch(switchSpecification);
}
// The implicit switch, if specified, must be a part of possible
// switch collection. Since we have emptied the set of
// possible switches in the beginning of this method, we
// need to add the implicit switch again to it.
if (implicitSwitch != null)
{
setImplicitSwitch(implicitSwitch);
}
}
/**
* Sets the implicit switch, that is, the switch to which the argument value
* attributed of the first argument that is not preceded by any switch.
* Example:
* <p>
* If you call setImplicitSwitch("--file(1)") and process command line
* <p>
* <code>myprogram --vertical --split myfile.txt</code>
* <p>
* then CommandLineParser will attribute "myfile.txt" value to the "--file"
* switch, and you will be able to retrieve this value by calling
* <code>getSwitchValue("--file")</code>.
* <p>
* <p>
* Note: you don't have to specify the implicit switch in the call to
* <code>setPossibleSwitches()</code>.
*
* @param implicitSwitchSpecification
* Default (implicit) switch will correspond to command line
* parameter that is not preceded with any switch. If there are
* several such parameters, they are united in a list and this
* list is set a list of values that correspond to this implicit
* switch.
*/
public void setImplicitSwitch(String implicitSwitchSpecification)
{
assert possibleSwitches != null;
forbid(possibleSwitches.isEmpty(),
"You must call setPossibleSwitches() before calling this method.");
forbidEmptyString(implicitSwitchSpecification,
"implicitSwitchSpecification");
/*
* if (implicitSwitch != null &&
* isPossibleSwitch(removeCardinality(implicitSwitch))) {
* removePossibleSwitch(implicitSwitch); }
*/
implicitSwitch = implicitSwitchSpecification;
// addPossibleSwitch(implicitSwitch);
}
/**
* Returns the implicit command line switch, that is, the implicit switch
* (set earlier with a call to setImplicitSwitch()), which corresponds to
* command line parameter(s) that are not part of values of any other
* switch. Example:
*
* <pre>
* command line: myprogram --verbose file.txt
* ...
* setPossibleSwitches("--verbose(0)");
* setImplicitSwitch("--file");
* ...
* String filename = getSwitchValue(getImplicitSwitch());
* </pre>
* <p>
* A call to this method must be preceded by a call to setImplicitSwitch(),
* otherwise an IllegalStateException is thrown.
* <p>
* To get the implicit switch value(s), call <code>getSwitchValue()</code>
* or <code>getSwitchValues()</code> with the name of implicit switch.
*
* @return The name of the implicit switch, e.g. "--file".
* @throws IllegalArgumentException
* when this method is not preceded by a call to
* setImplicitSwitch()
*/
public String getImplicitSwitch() throws IllegalArgumentException
{
forbidState(
implicitSwitch == null,
"Null in implicit switch value. You need to call setImplicitSwitch() before calling this method.");
return removeCardinality(implicitSwitch);
}
/**
* Parses command line arguments specified in a call to setArguments() or
* setCommandLine() and prepares as a result the data that reflect which
* command line switches were specified and with which values. This data can
* be retrieved with calls to getSwitchValue(), getSwitchValues(),
* getSwitchValueCount(), isSwitchPresent().
*
* @throws IllegalArgumentException
* - when a violation of parsing rules is encountered, for
* example, when a switch is present on command line which was
* not specified in a call to setPossibleSwitches().
* @throws IllegalStateException
* when the CommandLineParser object is not initialized, for
* instance, when its the command line arguments have not been
* set yet. You must at a minimum call setPossibleSwitches(),
* setArguments() or setCommandLine() before calling this
* method.
*/
protected void parse() throws IllegalArgumentException,
IllegalStateException
{
forbidState(
originalCommandLine == null || arguments == null,
"You must call setArguments() or setCommandLine() method before calling parse() method without arguments.");
// Here happens the magic!
doParsing(arguments);
isInitialized = true;
// Make sure the object is now properly initialized
requireInitialization();
}
/**
* Is command line switch present?
*
* @param switchString
* The command line switch, e.g. --version.
* @return True or false depending on whether the switch is present on the
* command line.
*/
public boolean isSwitchPresent(String switchString)
{
requireInitialization();
forbidEmptyString(switchString, "switchString");
forbid(!looksLikeSwitch(switchString),
"Switch must start with one of switch prefixes defined by the switchPrefixesMask mask.");
boolean returnValue = switches.containsKey(switchString);
return returnValue;
}
/**
* Returns switch value.
* <p>
* Example:
* <p>
* For command line <code>myprogram --version</code>
* <p>
* <code>getSwitchValue("--version")</code> will return an empty string.
* <p>
* For command line <code>myprogram --version ver</code>
* <p>
* <code>getSwitchValue("--version")</code> will return string "ver".
* <p>
* <p>
* If the switch is not present, the <code>IllegalArgumentException</code>
* is thrown.
* <p>
* <p>
*
* @param switchString
* The name of command line switch.
* @return The value of command line switch, empty string if switch has no
* value (as, for example, in case with --version switch).
*/
public String getSwitchValue(String switchString)
{
String returnValue = null;
requireInitialization();
forbidEmptyString(switchString, "switchString");
forbid(!isSwitchPresent(switchString),
"Switch "
+ switchString
+ " is not present on the command line. Use isSwitchPresent() to check for a switch.");
List<String> valueList = getSwitchValues(switchString);
assert valueList != null;
if (valueList.size() == 0)
{
returnValue = "";
}
else
{
returnValue = valueList.get(0);
}
forbid(valueList.size() > 1,
"Switch "
+ switchString
+ " has several values. Use getSwitchValueCount() to check for number of switch values."
+ " Use getSwitchValues() to get the list of switch values.");
assert returnValue != null; // Make sure we didn't skip any branches in
// logic
return returnValue;
}
/**
* Returns the list of values of command switch. In the command line, the
* values are assigned to a switch until either the next switch is
* encountered. Next switch is not considered such if it appears inside a
* single or double quoted string.
*
* The arguments that
* <p>
* Example:
* <p>
* For command line <code>myprogram --version</code>
* <p>
* <code>getSwitchValue("--version")</code> will return an empty list.
* <p>
* For command line myprogram --version ver
* <p>
* <code>getSwitchValue("--version")</code> will return a list with one
* element, string "ver".
* <p>
* For command line <code>myprogram --version ver ber</code>
* <p>
* <code>getSwitchValue("--version")</code> will return a list with two
* elements: strings "ver" and "ber".
* <p>
* <p>
* If the switch is not present, the <code>IllegalArgumentException</code>
* is thrown.
* <p>
* <p>
*
* @param switchString
* The name of command line switch.
* @return The list of values of command line switch.
*/
public List<String> getSwitchValues(String switchString)
{
List<String> returnValue = null;
requireInitialization();
forbidEmptyString(switchString, "switchString");
forbid(!isSwitchPresent(switchString),
"Switch "
+ switchString
+ " is not present on the command line. Use isSwitchPresent() to check for a switch.");
List<String> valueList = switchMap.get(switchString);
assert valueList != null;
returnValue = valueList;
assert returnValue != null; // Make sure we didn't skip any branches in
// logic
return returnValue;
}
/**
* Returns value count of switch.
* <p>
* Example:
* <p>
* For command line <code>myprogram --version</code>
* <p>
* <code>getSwitchValueCount("--version")</code> will return 0.
* <p>
* For command line <code>myprogram --version ver</code>
* <p>
* <code>getSwitchValueCount("--version")</code> will return 1.
* <p>
* <p>
* If the switch is not present, the <code>IllegalArgumentException</code>
* is thrown.
* <p>
* <p>
*
* @param switchString
* The name of command line switch.
* @return The number of values of command line switch.
*/
public int getSwitchValueCount(String switchString)
{
Integer returnValue = null;
requireInitialization();
forbidEmptyString(switchString, "switchString");
forbid(!isSwitchPresent(switchString),
"Switch "
+ switchString
+ " is not present on the command line. Use isSwitchPresent() to check for a switch.");
List<String> valueList = switchMap.get(switchString);
assert valueList != null;
returnValue = new Integer(valueList.size());
assert returnValue != null; // Make sure we didn't skip any branches in
// logic
return returnValue.intValue();
}
/**
* Returns the original command line that was parsed.
*
* @return The command line that has been passed to setCommandLine() method,
* or to setArguments() as array of arguments.
*/
public String getOriginalCommandLine()
{
forbidState(originalCommandLine == null,
"You must call setCommandLine() or setArguments() before calling this method.");
return originalCommandLine;
}
/**
* Returns true if usageSwitchName (default --usage) is present on the
* command line.
*
* @return True if usageSwitchName is present on command line.
*/
public boolean isUsageRequested()
{
requireInitialization();
return isSwitchPresent(usageSwitchName);
}
/**
* Returns true if helpSwitchName (default --help) is present on the command
* line.
*
* @return True if helpSwitchName is present on command line.
*/
public boolean isHelpRequested()
{
requireInitialization();
return isSwitchPresent(helpSwitchName);
}
/**
* Returns true if versionSwitchName (default --version) is present on the
* command line.
*
* @return True if versionSwitchName is present on command line.
*/
public boolean isVersionRequested()
{
requireInitialization();
return isSwitchPresent(versionSwitchName);
}
/**
* Returns the default value of usageSwitchName (default --usage).
*
* @return The default value of usageSwitchName.
*/
public String getUsageSwitchName()
{
return usageSwitchName;
}
/**
* Sets the default value of usageSwitchName.
*
* @param usageSwitch
* New default value of usageSwitchName.
*/
public void setUsageSwitchName(String usageSwitch)
{
forbid(looksLikeSwitch(usageSwitch),
"Switch must start with an appropriate switch prefix.");
this.usageSwitchName = usageSwitch;
}
/**
* Returns the default value of helpSwitchName (default --help).
*
* @return The default value of helpSwitchName.
*/
public String getHelpSwitchName()
{
return helpSwitchName;
}
/**
* Sets the default value of helpSwitchName.
*
* @param helpSwitch
* New default value of helpSwitchName.
*/
public void setHelpSwitchName(String helpSwitch)
{
this.helpSwitchName = helpSwitch;
}
/**
* Returns the default value of versionSwitchName (default --version).
*
* @return The default value of versionSwitchName.
*/
public String getVersionSwitchName()
{
return versionSwitchName;
}
/**
* Sets the default value of versionSwitchName.
*
* @param versionSwitch
* New default value of versionSwitchName.
*/
public void setVersionSwitchName(String versionSwitch)
{
this.versionSwitchName = versionSwitch;
}
/**
* Checks if CommandLineParser has not been properly initialized, that is,
* the methods <code>setPossibleSwitches()</code> and
* <code>setArguments()</code> have been called, and therefore, the parsing
* process has been carried out.
*
* Throws <code>IllegalStateException</code> exception if the
* <code>CommandLineParser</code> has not been initialized, that is, the
* methods <code>setPossibleSwitches()</code> and
* <code>setArguments()</code> have not been called.
*
* @throws IllegalStateException
* if CommandLineParser has not been initialized.
*/
protected void requireInitialization() throws IllegalStateException
{
assert possibleSwitches != null;
if (possibleSwitches.isEmpty())
{
throw new IllegalStateException(
"This class must be initialized first by calling setPossibleSwitches() and setArguments() method.");
}
if (arguments == null)
{
throw new IllegalStateException(
"This class must be initialized first by calling setArguments() or setCommandLine() method.");
}
if (!isInitialized)
{
throw new IllegalStateException(
"This class must be initialized first by calling setArguments() or parse() method.");
}
}
/**
* Throws IllegalArgumentException if the specified string is null.
*
* @param s
* The string to check for nullability.
* @param parameterName
* The name of parameter of the caller method that we want to
* include in exception description if the tested string is null.
*
* @throws IllegalArgumentException
* if passed-in string is null.
*/
protected void forbidNullString(String s, String parameterName)
throws IllegalArgumentException
{
if (s == null)
{
throw new IllegalArgumentException(
"Non-null value required in parameter " + parameterName
+ ".");
}
}
/**
* Throws IllegalArgumentException if the specified string is a null or
* empty string.
*
* @param s
* The string to check for nullability or emptiness.
* @param parameterName
* The name of parameter of the caller method that we want to
* include in exception description if the tested string is null
* or empty.
*
* @throws IllegalArgumentException
* if passed-in string is null or empty.
*/
protected void forbidEmptyString(String s, String parameterName)
throws IllegalArgumentException
{
if (s == null)
{
throw new IllegalArgumentException(
"Non-null value required in parameter " + parameterName
+ ".");
}
if (s.length() == 0)
{
throw new IllegalArgumentException(
"Non-empty value required in parameter " + parameterName
+ ".");
}
}
/**
* Throws IllegalArgumentException if the specified boolean condition is
* false.
*
* @param condition
* The boolean condition string to check for falseness.
* @param message
* The message to set to the thrown IllegalArgumentException.
*
* @throws IllegalArgumentException
* if passed-in boolean condition is false.
*/
protected void forbid(boolean condition, String message)
throws IllegalArgumentException
{
if (condition)
{
throw new IllegalArgumentException(message);
}
}
/**
* Throws IllegalStateException if the specified boolean condition is false.
* Same as forbid() except an IllegalStateException is thrown rather than
* IllegalArgumentException.
*
* @param condition
* The boolean condition string to check for falseness.
* @param message
* The message to set to the thrown IllegalArgumentException.
*
* @throws IllegalStateException
* if passed-in string is null or empty.
*/
protected void forbidState(boolean condition, String message)
throws IllegalStateException
{
if (condition)
{
throw new IllegalStateException(message);
}
}
/**
* Returns command line arguments that do not have any switch corresponding
* to them.
* <p>
*
* Alternatively, you can use <code>getValues(getImlicitSwitchName())</code>.
*
* @return The string array consisting of values of switchless arguments in
* the order they appear in the command line.
*/
public String[] getSwitchlessArguments()
{
requireInitialization();
forbidState(switchlessArguments == null,
"Member switchlessArguments must not be null.");
// forbidState(switchlessArguments.length == 0,
// "Member switchlessArguments must not be empty.");
return switchlessArguments;
}
/*
* Deep implementation methods
*/
/**
* Implements the algorithm for parsing command line arguments into
* meaningful pairs of switch name - switch value(s).
* <p>
*
* This method considers the argument array to follow Java rules for the
* array of arguments passed into Java program's main() method:
* <p>
* - On the command line, Java program arguments are separated by spaces and
* tabs, except when an argument is enclosed in double quotes.
* <p>
* - Leading and trailing whitespace characters are removed from the values
* stored in the arguments array.
* <p>
* - The double-quoted argument is not broken into several separate
* arguments.
* <p>
* - For arguments enclosed with double quotes, the double quotes are
* removed.
* <p>
*
* @param args
* Command line arguments.
* @throws IllegalArgumentException
* when a violation of parsing rules is encountered, for
* example, when a switch is present on command line which was
* not specified in a call to setPossibleSwitches().
* @throws IllegalStateException
* when the CommandLineParser object is not initialized, for
* instance, when its the command line arguments have not been
* set yet. You must at a minimum call setPossibleSwitches(),
* setArguments() or setCommandLine() before calling this
* method.
*/
protected void doParsing(String[] args) throws IllegalArgumentException,
IllegalStateException
{
assert possibleSwitches != null;
if (possibleSwitches.isEmpty())
{
throw new IllegalStateException(
"This class must be initialized first by calling setPossibleSwitches() and setArguments() method.");
}
if (args == null)
{
throw new IllegalArgumentException(
"This arguments array must not be null.");
}
// Process array elements one by one, accumulating
// the found switches and their values into switch-to-value list map.
Switch curSwitch = null;
Map<String, Switch> switchByName = new LinkedHashMap<String, Switch>();
List<String> switchless = new ArrayList<String>();
// Implicit switch to hold the switchless arguments
Switch implicit = null;
if (implicitSwitch != null)
{
implicit = createSwitch(implicitSwitch);
implicit.setImplicit(true);
}
// MAIN LOOP: go over arguments one by one, making decisions
for (String arg : args)
{
String argument = arg;
// boolean isLastArgument = (i == args.length - 1);
boolean isSwitch = looksLikeSwitch(argument);
String switchName = isSwitch ? argument : null;
boolean needToFinalizePreviousSwitch = (curSwitch != null)
&& isSwitch;
boolean needToInilializeNewSwitch = isSwitch;
// Parsing rule 1: forbid unknown switches. All switches must be
// declared in a call to setPossibleSwitches().
forbid(isSwitch && !isPossibleSwitch(argument),
"Unknown switch found: "
+ argument
+ ". You must specify this switch in a call to setPossibleSwitches() first.");
if (needToFinalizePreviousSwitch)
{
// Finalize previous switch
if (curSwitch != null)
{
curSwitch = null;
}
}
if (needToInilializeNewSwitch)
{
// Initialize data structures for newly discovered switch
curSwitch = createSwitch(argument);
switchByName.put(switchName, curSwitch);
}
if (isSwitch)
{
// Next will come the switch value, if any
continue;
}
else
{
boolean tooManyArguments = false;
if (curSwitch != null)
{
tooManyArguments = (curSwitch.getValues().size() >= curSwitch
.getMaxValues());
}
if (curSwitch != null && !tooManyArguments)
{
curSwitch.addValue(argument);
}
else
{
// Assign argument to switchless arguments
switchless.add(argument);
// If there is an implicit switch, assign argument to it,
// too.
if (implicitSwitch != null)
{
implicit.addValue(argument);
}
}
}
}
// END OF MAIN LOOP
// Add implicit switch values, if any
if (implicitSwitch != null && implicit.getValues().size() > 0)
{
switchByName.put(getImplicitSwitch(), implicit);
}
// Copy accumulated switch map to CommandLineParser's switch
// collection.
switches.clear();
switchMap.clear();
try
{
for (String switchName : switchByName.keySet())
{
Switch sw = switchByName.get(switchName);
sw.validate();
switches.put(switchName, sw);
List<String> values = new ArrayList<String>(sw.getValues());
// Collections.copy(values, sw.getValues());
switchMap.put(switchName, values);
}
}
catch (IllegalStateException ise)
{
switches.clear();
switchMap.clear();
throw new IllegalArgumentException(ise.getMessage());
}
// Not to forget the rest of the arguments
switchlessArguments = new String[switchless.size()];
for (int i = 0; i < switchless.size(); i++)
{
switchlessArguments[i] = switchless.get(i);
}
// Considered all possible switches?
forbidState(switches == null,
"Post-condition failure: member 'switches' is null");
forbidState(switchMap == null,
"Post-condition failure: member 'switchMap' is null");
forbidState(
switchMap.size() != switches.size(),
"Post-condition failure: member 'switchMap' has different size from member 'switches'");
}
/**
* Returns true if CommandLineParser has been initialized, that is the
* setPossibleSwitches() and setArguments() methods have been called.
*
* @return True if CommandLineParser has been initialized.
*/
public boolean isInitialized()
{
return isInitialized;
}
/**
* Returns the mapping of switch names to list of their corresponding
* values. Example: for command line "--version major minor" a call to
* getSwitchMap() will return a map consisting of one element which maps
* switch "--version" to list of two values, "major" and "minor" (without
* quotes).
*
* @return The mapping of command line switch names to lists of their
* corresponding values.
*/
public Map<String, List<String>> getSwitchMap()
{
return switchMap;
}
/**
* Adds a switch to the required switches.
* <p>
* Example:
*
* <pre>
* addPossibleSwitch("--switch(0-*)");
* </pre>
*
* will allow for the command line switch <code>--switch</code> with zero to
* infinite number of arguments.
*
* @param switchSpecification
* Name and cardinality of switch, e.g. --file(1), meaning that
* the --file switch requires exactly one argument.
*/
protected void addPossibleSwitch(String switchSpecification)
{
forbidEmptyString(switchSpecification, "switchSpecification");
switchSpecification = switchSpecification.trim();
String switchName = removeCardinality(switchSpecification);
forbid(isPossibleSwitch(switchName), "The possible switch "
+ switchName + " is already defined for this object.");
if (!possibleSwitches.contains(switchSpecification))
{
if (!switchSpecification.endsWith(")"))
{
throw new IllegalArgumentException(
"Incorrect format of switch:"
+ " each switch must be followed by numbers in parenthesis specifying the required number"
+ " of its values, e.g. \"--version(0), --file(1) --twomore(2-*) --any(*)\"");
}
possibleSwitches.add(switchSpecification);
}
}
/**
* Removes a previously specified with a call to
* <code>addPossibleSwitch()</code> or <code>setPossibleSwitches()</code>
* switch from the set of required switches.
* <p>
* Example:
*
* <pre>
* removePossibleSwitch("--switch(0-*)");
* -or-
* removePossibleSwitch("--switch");
* </pre>
*
* will remove the switch <code>--switch</code> from the set of possible
* command line switches.
*
* @param switchSpecification
* Name and cardinality of switch, e.g. --file(1), specifying the
* --file switch which requires exactly one argument.
*/
protected void removePossibleSwitch(String switchSpecification)
{
forbidEmptyString(switchSpecification, "switchSpecification");
switchSpecification = switchSpecification.trim();
int firstBrace = switchSpecification.indexOf("(");
boolean bracePresent = firstBrace != -1;
String switchName = bracePresent ? switchSpecification.substring(0,
firstBrace).trim() : switchSpecification;
boolean found = false;
if (bracePresent && !switchSpecification.endsWith(")"))
{
throw new IllegalArgumentException(
"Incorrect format of switch:"
+ " each switch must be followed by numbers in parenthesis specifying the required number"
+ " of its values, e.g. \"--version(0), --file(1) --twomore(2-*) --any(*)\"");
}
for (String possibleSwitch : possibleSwitches)
{
int brace = possibleSwitch.indexOf("(");
assert brace != -1;
String name = possibleSwitch.substring(0, brace).trim();
if (bracePresent && possibleSwitch.equals(switchSpecification)
|| !bracePresent && switchName.equals(name))
{
possibleSwitches.remove(possibleSwitch);
found = true;
break;
}
}
if (!found)
{
throw new IllegalArgumentException(
"Switch "
+ switchSpecification
+ " is not found in the collection of possible switches. You must first call "
+ " setPossibleSwitches() or setImplicitSwitch(), or check the spelling of the argument.");
}
}
/**
* Creates a new Switch object for the specified switchName. Switch name
* must be one of possible switches (see
* {@link CommandLineParser#setPossibleSwitches}() method)
*
* @param switchName
* The name of switch, e.g. --file, or its specification, e.g.
* --file(1).
* @return The newly created Switch object.
*/
public Switch createSwitch(String switchName)
{
boolean switchSpecificationFound = false;
String name = null;
int minValues = -1;
int maxValues = -1;
Set<String> switchCandidates = new LinkedHashSet<String>(
possibleSwitches);
if (implicitSwitch != null)
{
switchCandidates.add(implicitSwitch);
}
for (String switchSpecification : switchCandidates)
{
assert switchSpecification.endsWith(")");
if (switchSpecification.startsWith(switchName))
{
boolean nameIsSpecification = switchName
.equals(switchSpecification);
int leftBrace = switchSpecification.lastIndexOf("(");
int rightBrace = switchSpecification.lastIndexOf(")");
forbid(leftBrace == -1,
"Malformed switch specification: missing left brace.");
forbid(rightBrace == -1,
"Malformed switch specification: missing right brace.");
forbid(leftBrace > rightBrace,
"Malformed switch specification: left brace found after right brace.");
forbid(leftBrace == rightBrace - 1,
"Malformed switch specification: a number or an interval"
+ " must be specified between left and right braces.");
name = switchSpecification.substring(0, leftBrace);
if (switchSpecificationFound)
{
break;
}
}
}
forbid(!switchSpecificationFound,
"Switch "
+ switchName
+ " is not found among the possible switches or implicit switch. "
+ " Check if this switch is specified in a call to setPossibleSwitches() or setImplicitSwitch()");
Switch newSwitch = new Switch(name, minValues, maxValues);
return newSwitch;
}
/**
* Returns true if switch name looks like a switch, that is, starts with one
* of the prefixes specified by the <code>switchPrefixesMask</code>.
*
* @param switchName
* Name of switch.
* @return True if switch name starts with one of the prefixes specified by
* the <code>switchPrefixesMask</code>.
*/
protected boolean looksLikeSwitch(String switchName)
{
boolean looksLikeSwitch = switchName.matches("^" + switchPrefixesMask
+ ".*");
return looksLikeSwitch;
}
/**
* Returns true if switch is among possible switches that are specified with
* a call to <code>setPossibleSwitches()</code> or
* <code>setImplicitSwitch()</code>.
*
* @param switchName
* Name of switch, e.g. --file
* @return True if switch is among possible switches.
*/
public boolean isPossibleSwitch(String switchName)
{
boolean result = false;
for (String possibleSwitch : possibleSwitches)
{
int leftBrace = possibleSwitch.indexOf("(");
forbid(leftBrace == -1, "Misformed possible switch element: "
+ possibleSwitch);
String name = possibleSwitch.substring(0, leftBrace);
if (switchName.equals(name))
{
result = true;
break;
}
}
return result;
}
/**
* Removes switch cardinality (the numbers in braces in the end of switch
* specification) from switch specification. If cardinality is not present,
* returns the same string as the passed-in argument.
* <p>
* Example: <code>removeCardinality("--file(0-*)")</code> will return string
* "--file"
*
* @param switchSpecification
* Name and cardinality of switch, e.g. --file(0-*)
* @return Name of switch without cardinality, e.g. --file.
*/
protected String removeCardinality(String switchSpecification)
{
forbidNullString(switchSpecification, "switchSpecification");
String result;
int leftBrace = switchSpecification.indexOf("(");
int rightBrace = switchSpecification.indexOf(")");
if (leftBrace != -1)
{
forbid(rightBrace == -1,
"Malformed switch specification: must have left and right braces");
forbid(rightBrace < leftBrace,
"Malformed switch specification: must have right brace after left brace");
result = switchSpecification.substring(0, leftBrace);
}
else
{
forbid(rightBrace != -1,
"Malformed switch specification: must have left and right braces or have neither");
result = switchSpecification;
}
return result;
}
}