package jargs.gnu;
import java.text.NumberFormat;
import java.text.ParseException;
import java.util.Hashtable;
import java.util.Vector;
import java.util.Enumeration;
import java.util.Locale;
/**
* Largely GNU-compatible command-line options parser. Has short (-v) and
* long-form (--verbose) option support, and also allows options with
* associated values (-d 2, --debug 2, --debug=2). Option processing
* can be explicitly terminated by the argument '--'.
*
* @author Steve Purcell
* @version $Revision: 1.10 $
* @see jargs.examples.gnu.OptionTest
*/
public class LenientCmdLineParser {
/**
* Base class for exceptions that may be thrown when options are parsed
*/
public static abstract class OptionException extends Exception {
OptionException(String msg) { super(msg); }
}
/**
* Thrown when an illegal or missing value is given by the user for
* an option that takes a value. <code>getMessage()</code> returns
* an error string suitable for reporting the error to the user (in
* English).
*/
public static class IllegalOptionValueException extends OptionException {
public IllegalOptionValueException( Option opt, String value ) {
super("Illegal value '" + value + "' for option " +
(opt.shortForm() != null ? "-" + opt.shortForm() + "/" : "") +
"--" + opt.longForm());
this.option = opt;
this.value = value;
}
/**
* @return the name of the option whose value was illegal (e.g. "-u")
*/
public Option getOption() { return this.option; }
/**
* @return the illegal value
*/
public String getValue() { return this.value; }
private Option option;
private String value;
}
/**
* Representation of a command-line option
*/
public static abstract class Option {
protected Option( String longForm, boolean wantsValue ) {
this(null, longForm, wantsValue);
}
protected Option( char shortForm, String longForm,
boolean wantsValue ) {
this(new String(new char[]{shortForm}), longForm, wantsValue);
}
private Option( String shortForm, String longForm, boolean wantsValue ) {
if ( longForm == null )
throw new IllegalArgumentException("Null longForm not allowed");
this.shortForm = shortForm;
this.longForm = longForm;
this.wantsValue = wantsValue;
}
public String shortForm() { return this.shortForm; }
public String longForm() { return this.longForm; }
/**
* Tells whether or not this option wants a value
*/
public boolean wantsValue() { return this.wantsValue; }
public final Object getValue( String arg, Locale locale )
throws IllegalOptionValueException {
if ( this.wantsValue ) {
if ( arg == null ) {
throw new IllegalOptionValueException(this, "");
}
return this.parseValue(arg, locale);
}
else {
return Boolean.TRUE;
}
}
/**
* Override to extract and convert an option value passed on the
* command-line
*/
protected Object parseValue( String arg, Locale locale )
throws IllegalOptionValueException {
return null;
}
private String shortForm = null;
private String longForm = null;
private boolean wantsValue = false;
public static class BooleanOption extends Option {
public BooleanOption( char shortForm, String longForm ) {
super(shortForm, longForm, false);
}
public BooleanOption( String longForm ) {
super(longForm, false);
}
}
/**
* An option that expects an integer value
*/
public static class IntegerOption extends Option {
public IntegerOption( char shortForm, String longForm ) {
super(shortForm, longForm, true);
}
public IntegerOption( String longForm ) {
super(longForm, true);
}
protected Object parseValue( String arg, Locale locale )
throws IllegalOptionValueException {
try {
return new Integer(arg);
}
catch (NumberFormatException e) {
throw new IllegalOptionValueException(this, arg);
}
}
}
/**
* An option that expects a long integer value
*/
public static class LongOption extends Option {
public LongOption( char shortForm, String longForm ) {
super(shortForm, longForm, true);
}
public LongOption( String longForm ) {
super(longForm, true);
}
protected Object parseValue( String arg, Locale locale )
throws IllegalOptionValueException {
try {
return new Long(arg);
}
catch (NumberFormatException e) {
throw new IllegalOptionValueException(this, arg);
}
}
}
/**
* An option that expects a floating-point value
*/
public static class DoubleOption extends Option {
public DoubleOption( char shortForm, String longForm ) {
super(shortForm, longForm, true);
}
public DoubleOption( String longForm ) {
super(longForm, true);
}
protected Object parseValue( String arg, Locale locale )
throws IllegalOptionValueException {
try {
NumberFormat format = NumberFormat.getNumberInstance(locale);
Number num = (Number)format.parse(arg);
return new Double(num.doubleValue());
}
catch (ParseException e) {
throw new IllegalOptionValueException(this, arg);
}
}
}
/**
* An option that expects a string value
*/
public static class StringOption extends Option {
public StringOption( char shortForm, String longForm ) {
super(shortForm, longForm, true);
}
public StringOption( String longForm ) {
super(longForm, true);
}
protected Object parseValue( String arg, Locale locale ) {
return arg;
}
}
}
/**
* Add the specified Option to the list of accepted options
*/
public final Option addOption( Option opt ) {
if ( opt.shortForm() != null )
this.options.put("-" + opt.shortForm(), opt);
this.options.put("--" + opt.longForm(), opt);
return opt;
}
/**
* Convenience method for adding a string option.
* @return the new Option
*/
public final Option addStringOption( char shortForm, String longForm ) {
return addOption(new Option.StringOption(shortForm, longForm));
}
/**
* Convenience method for adding a string option.
* @return the new Option
*/
public final Option addStringOption( String longForm ) {
return addOption(new Option.StringOption(longForm));
}
/**
* Convenience method for adding an integer option.
* @return the new Option
*/
public final Option addIntegerOption( char shortForm, String longForm ) {
return addOption(new Option.IntegerOption(shortForm, longForm));
}
/**
* Convenience method for adding an integer option.
* @return the new Option
*/
public final Option addIntegerOption( String longForm ) {
return addOption(new Option.IntegerOption(longForm));
}
/**
* Convenience method for adding a long integer option.
* @return the new Option
*/
public final Option addLongOption( char shortForm, String longForm ) {
return addOption(new Option.LongOption(shortForm, longForm));
}
/**
* Convenience method for adding a long integer option.
* @return the new Option
*/
public final Option addLongOption( String longForm ) {
return addOption(new Option.LongOption(longForm));
}
/**
* Convenience method for adding a double option.
* @return the new Option
*/
public final Option addDoubleOption( char shortForm, String longForm ) {
return addOption(new Option.DoubleOption(shortForm, longForm));
}
/**
* Convenience method for adding a double option.
* @return the new Option
*/
public final Option addDoubleOption( String longForm ) {
return addOption(new Option.DoubleOption(longForm));
}
/**
* Convenience method for adding a boolean option.
* @return the new Option
*/
public final Option addBooleanOption( char shortForm, String longForm ) {
return addOption(new Option.BooleanOption(shortForm, longForm));
}
/**
* Convenience method for adding a boolean option.
* @return the new Option
*/
public final Option addBooleanOption( String longForm ) {
return addOption(new Option.BooleanOption(longForm));
}
/**
* Equivalent to {@link #getOptionValue(Option, Object) getOptionValue(o,
* null)}.
*/
public final Object getOptionValue( Option o ) {
return getOptionValue(o, null);
}
/**
* @return the parsed value of the given Option, or null if the
* option was not set
*/
public final Object getOptionValue( Option o, Object def ) {
Vector v = (Vector)values.get(o.longForm());
if (v == null) {
return def;
}
else if (v.isEmpty()) {
return null;
}
else {
Object result = v.elementAt(0);
v.removeElementAt(0);
return result;
}
}
/**
* @return A Vector giving the parsed values of all the occurrences of the
* given Option, or an empty Vector if the option was not set.
*/
public final Vector getOptionValues( Option option ) {
Vector result = new Vector();
while (true) {
Object o = getOptionValue(option, null);
if (o == null) {
return result;
}
else {
result.addElement(o);
}
}
}
/**
* @return the non-option arguments
*/
public final String[] getRemainingArgs() {
return this.remainingArgs;
}
/**
* Extract the options and non-option arguments from the given
* list of command-line arguments. The default locale is used for
* parsing options whose values might be locale-specific.
*/
public final void parse( String[] argv )
throws IllegalOptionValueException {
// It would be best if this method only threw OptionException, but for
// backwards compatibility with old user code we throw the two
// exceptions above instead.
parse(argv, Locale.getDefault());
}
/**
* Extract the options and non-option arguments from the given
* list of command-line arguments. The specified locale is used for
* parsing options whose values might be locale-specific.
*/
public final void parse( String[] argv, Locale locale )
throws IllegalOptionValueException {
// It would be best if this method only threw OptionException, but for
// backwards compatibility with old user code we throw the two
// exceptions above instead.
Vector otherArgs = new Vector();
int position = 0;
this.values = new Hashtable(10);
while ( position < argv.length ) {
String curArg = argv[position];
if ( curArg.startsWith("-") ) {
if ( curArg.equals("--") ) { // end of options
position += 1;
break;
}
String valueArg = null;
if ( curArg.startsWith("--") ) { // handle --arg=value
int equalsPos = curArg.indexOf("=");
if ( equalsPos != -1 ) {
valueArg = curArg.substring(equalsPos+1);
curArg = curArg.substring(0,equalsPos);
}
} else if(curArg.length() > 2) { // handle -abcd
for(int i=1; i<curArg.length(); i++) {
Option opt=(Option)this.options.get
("-"+curArg.charAt(i));
if(opt==null) continue;
if(opt.wantsValue()) continue;
addValue(opt, opt.getValue(null,locale));
}
position++;
continue;
}
Option opt = (Option)this.options.get(curArg);
if ( opt == null ) {
position++;
continue;
}
Object value = null;
if ( opt.wantsValue() ) {
if ( valueArg == null ) {
position += 1;
if ( position < argv.length ) {
valueArg = argv[position];
}
}
value = opt.getValue(valueArg, locale);
}
else {
value = opt.getValue(null, locale);
}
addValue(opt, value);
position += 1;
}
else {
otherArgs.addElement(curArg);
position += 1;
}
}
for ( ; position < argv.length; ++position ) {
otherArgs.addElement(argv[position]);
}
this.remainingArgs = new String[otherArgs.size()];
otherArgs.copyInto(remainingArgs);
}
private void addValue(Option opt, Object value) {
String lf = opt.longForm();
Vector v = (Vector)values.get(lf);
if (v == null) {
v = new Vector();
values.put(lf, v);
}
v.addElement(value);
}
private String[] remainingArgs = null;
private Hashtable options = new Hashtable(10);
private Hashtable values = new Hashtable(10);
}