package com.meidusa.amoeba.util;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringReader;
import java.text.NumberFormat;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
import java.util.Vector;
import java.util.Locale;
import com.meidusa.amoeba.config.ConfigUtil;
public class CmdLineParser {
/**
* Base class for exceptions that may be thrown when options are parsed
*/
public static abstract class OptionException extends Exception {
private static final long serialVersionUID = 1L;
OptionException(String msg) {
super(msg);
}
}
/**
* Thrown when the parsed command-line contains an option that is not
* recognised. <code>getMessage()</code> returns an error string suitable
* for reporting the error to the user (in English).
*/
public static class UnknownOptionException extends OptionException {
private static final long serialVersionUID = 1L;
UnknownOptionException(String optionName) {
this(optionName, "Unknown option '" + optionName + "'");
}
UnknownOptionException(String optionName, String msg) {
super(msg);
this.optionName = optionName;
}
/**
* @return the name of the option that was unknown (e.g. "-u")
*/
public String getOptionName() {
return this.optionName;
}
private String optionName = null;
}
/**
* Thrown when the parsed commandline contains multiple concatenated short
* options, such as -abcd, where one is unknown. <code>getMessage()</code>
* returns an english human-readable error string.
*
* @author Vidar Holen
*/
public static class UnknownSuboptionException extends
UnknownOptionException {
/**
*
*/
private static final long serialVersionUID = 1L;
private char suboption;
UnknownSuboptionException(String option, char suboption) {
super(option, "Illegal option: '" + suboption + "' in '" + option
+ "'");
this.suboption = suboption;
}
public char getSuboption() {
return suboption;
}
}
/**
* Thrown when the parsed commandline contains multiple concatenated short
* options, such as -abcd, where one or more requires a value.
* <code>getMessage()</code> returns an english human-readable error string.
*
* @author Vidar Holen
*/
public static class NotFlagException extends UnknownOptionException {
private static final long serialVersionUID = 1L;
private String notflag;
NotFlagException(String option, String unflaggish) {
super(option, "Illegal option: '" + option + "', '" + unflaggish
+ "' requires a value");
notflag = unflaggish;
}
/**
* @return the first character which wasn't a boolean (e.g 'c')
*/
public String getOptionChar() {
return notflag;
}
}
/**
* 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 {
private static final long serialVersionUID = 1L;
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<T> {
private String shortForm = null;
private String longForm = null;
private boolean wantsValue = true;
private boolean required = false;
private String description;
private T defaultValue = null;
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public T getDefaultValue() {
return defaultValue;
}
public void setDefaultValue(T defaultValue) {
this.defaultValue = defaultValue;
}
protected Option(String longForm, boolean wantsValue) {
this(null, longForm, wantsValue);
}
protected Option(char shortForm, String longForm, boolean wantsValue) {
this((shortForm>0?String.valueOf(shortForm):null), 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;
}
public boolean isRequired() {
return required;
}
public void setRequired(boolean required) {
this.required = required;
}
/**
* Tells whether or not this option wants a value
*/
public boolean wantsValue() {
return this.wantsValue;
}
public final T getValue(String arg, Locale locale)
throws IllegalOptionValueException {
if (this.wantsValue) {
if (arg == null) {
throw new IllegalOptionValueException(this, "");
}
}else{
if(arg == null){
return this.getDefaultValue();
}
}
if(arg != null){
arg = ConfigUtil.filter(arg);
}
return this.parseValue(arg, locale);
}
/**
* Override to extract and convert an option value passed on the
* command-line
*/
protected T parseValue(String arg, Locale locale)
throws IllegalOptionValueException {
return null;
}
}
public static class BooleanOption extends Option<Boolean> {
public BooleanOption(char shortForm, String longForm,boolean wantsValue,boolean requried,boolean defaultValue,String description) {
super(shortForm, longForm, wantsValue);
this.setDefaultValue(defaultValue);
this.setDescription(description);
this.setRequired(requried);
}
public BooleanOption(char shortForm, String longForm,boolean wantsValue,boolean requried,String description) {
this(shortForm, longForm, wantsValue,requried,false,description);
}
public BooleanOption(char shortForm, String longForm,boolean wantsValue,boolean requried){
this(shortForm,longForm,wantsValue, requried,null);
}
public BooleanOption(char shortForm, String longForm,boolean wantsValue){
this(shortForm,longForm, wantsValue,true);
}
public BooleanOption(String longForm) {
super(longForm, true);
}
protected Boolean parseValue(String arg, Locale locale)
throws IllegalOptionValueException {
return Boolean.parseBoolean(arg);
}
}
/**
* An option that expects an integer value
*/
public static class IntegerOption extends Option<Integer> {
public IntegerOption(char shortForm, String longForm,boolean wantsValue,boolean requried,Integer defaultValue,String description) {
super(shortForm, longForm, wantsValue);
this.setDefaultValue(defaultValue);
this.setDescription(description);
this.setRequired(requried);
}
public IntegerOption(char shortForm, String longForm,boolean wantsValue,boolean requried,String description) {
this(shortForm, longForm, wantsValue,requried,null,description);
}
public IntegerOption(char shortForm, String longForm,boolean wantsValue,boolean requried){
this(shortForm,longForm, wantsValue,requried,null);
}
public IntegerOption(char shortForm, String longForm,boolean wantsValue) {
this(shortForm, longForm, wantsValue,true);
}
public IntegerOption(String longForm) {
super(longForm, true);
}
protected Integer 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<Long> {
public LongOption(char shortForm, String longForm,boolean wantsValue,boolean required,Long defaultValue,String description) {
super(shortForm, longForm, wantsValue);
this.setDefaultValue(defaultValue);
this.setDescription(description);
this.setRequired(required);
}
public LongOption(char shortForm, String longForm,boolean wantsValue,boolean required,String description) {
this(shortForm, longForm, wantsValue,required,null,description);
}
public LongOption(char shortForm, String longForm,boolean wantsValue,boolean required){
this(shortForm,longForm, wantsValue,required,null);
}
public LongOption(char shortForm, String longForm,boolean wantsValue) {
this(shortForm, longForm,wantsValue,true);
}
public LongOption(String longForm) {
super(longForm, true);
}
protected Long 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<Double> {
public DoubleOption(char shortForm, String longForm,boolean wantsValue,boolean required,Double defaultValue,String description) {
super(shortForm, longForm, wantsValue);
this.setDefaultValue(defaultValue);
this.setDescription(description);
this.setRequired(required);
}
public DoubleOption(char shortForm, String longForm,boolean wantsValue,boolean required,String description) {
this(shortForm, longForm,wantsValue, required,null,description);
}
public DoubleOption(char shortForm, String longForm,boolean wantsValue,boolean required){
this(shortForm,longForm,wantsValue, required,null);
}
public DoubleOption(char shortForm, String longForm,boolean wantsValue) {
this(shortForm, longForm,wantsValue, true);
}
public DoubleOption(String longForm) {
super(longForm, true);
}
protected Double 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<String> {
/**
*
* @param shortForm
* @param longForm
* @param wantsValue -- must has an argument
* @param required -- must required
* @param defaultValue
* @param description
*/
public StringOption(char shortForm, String longForm,boolean wantsValue,boolean required,String defaultValue,String description) {
super(shortForm, longForm, wantsValue);
this.setDefaultValue(defaultValue);
this.setDescription(description);
this.setRequired(required);
}
public StringOption(char shortForm, String longForm,boolean wantsValue,boolean required,String description) {
this(shortForm, longForm, wantsValue,required,null,description);
}
public StringOption(char shortForm, String longForm,boolean wantsValue,boolean required){
this(shortForm,longForm,wantsValue, required,null);
}
public StringOption(char shortForm, String longForm,boolean wantsValue) {
this(shortForm, longForm,wantsValue, true);
}
public StringOption(String longForm) {
super(longForm, true);
}
public String getDefaultValue() {
if(super.getDefaultValue() != null){
return ConfigUtil.filter(super.getDefaultValue());
}else{
return super.getDefaultValue();
}
}
protected String parseValue(String arg, Locale locale) {
return arg;
}
}
String name;
public CmdLineParser(String applicationName) {
this.name = applicationName;
}
/**
* 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);
optionList.add(opt);
if (opt.longForm() != null) {
format.fullLenght = (format.fullLenght < opt.longForm().length() ? opt
.longForm().length()
: format.fullLenght);
}
if (opt.getDescription() != null) {
format.descriptionLenght = (format.descriptionLenght < opt
.getDescription().length() ? opt.getDescription().length()
: format.descriptionLenght);
}
return opt;
}
public List<Option<?>> getOptions() {
return optionList;
}
public Option<?> getOption(String name){
return this.options.get("--"+name);
}
public final Option addOption(OptionType type, char shortForm,
String longForm, boolean requried, String description) {
Option option = null;
switch (type) {
case String:
option = new StringOption(shortForm, longForm,true);
break;
case Int:
option = new IntegerOption(shortForm, longForm,true);
break;
case Long:
option = new LongOption(shortForm, longForm,true);
break;
case Double:
option = new DoubleOption(shortForm, longForm,true);
break;
case Boolean:
option = new BooleanOption(shortForm, longForm,true);
break;
}
option.setDescription(description);
option.wantsValue = requried;
return addOption(option);
}
public final Option addOption(OptionType type, char shortForm,
String longForm, boolean requried, String description,String defaultValue) {
Option option = addOption(type,shortForm,longForm,requried,description);
option.setDefaultValue(defaultValue);
return option;
}
/**
* Equivalent to {@link #getOptionValue(Option, Object) getOptionValue(o,
* null)}.
*/
public final Object getOptionValue(Option o) {
return getOptionValue(o, o.getDefaultValue());
}
/**
* @return the parsed value of the given Option, or null if the option was
* not set
*/
public Object getOptionValue(Option o, Object def) {
Vector v = (Vector) values.get(o.longForm());
if (v == null) {
return def;
} else if (v.isEmpty()) {
return o.getDefaultValue();
} else {
Object result = v.elementAt(0);
v.removeElementAt(0);
return result == null?def : 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,
UnknownOptionException {
// 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, UnknownOptionException {
// 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)
throw new UnknownSuboptionException(curArg, curArg
.charAt(i));
if (opt.wantsValue())
throw new NotFlagException(curArg, new String(""+curArg.charAt(i)));
addValue(opt, opt.getValue(null, locale));
}
position++;
continue;
}
Option opt = (Option) this.options.get(curArg);
if (opt == null) {
throw new UnknownOptionException(curArg);
}
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);
}
public void checkRequired() throws NotFlagException{
for(Option option : optionList){
if(option.isRequired()){
Object obj = values.get(option.longForm);
if(obj == null){
throw new NotFlagException(option.longForm,option.shortForm);
}
}
}
}
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);
}
class Format {
int fullLenght;
int descriptionLenght;
}
public void printUsage() {
try {
BufferedWriter writer = new BufferedWriter(new PrintWriter(
System.out));
writer.write(this.name);
writer.write(" [-option value/--option=value]");
writer.newLine();
for (Option option : optionList) {
writer.write(" ");
writer.write((!StringUtil.isEmpty(option.shortForm())?("-" + option.shortForm()+","):"") + "--"
+ option.longForm()
+ (option.wantsValue() ? "=value" :"") + (option.isRequired() ? " [required]" : "")
+ "");
if (option.getDescription() != null) {
BufferedReader read = new BufferedReader(new StringReader(
option.getDescription()));
String line = null;
while ((line = read.readLine()) != null) {
writer.newLine();
writer.write(" ");
writer.write(line);
}
}
writer.newLine();
writer.newLine();
}
writer.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
private Format format = new Format();
private String[] remainingArgs = null;
private List<Option<?>> optionList = new ArrayList<Option<?>>();
private Hashtable<String, Option> options = new Hashtable<String, Option>(
10);
private Hashtable values = new Hashtable(10);
}