package water;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import water.util.Log;
/**
* Utility for processing command
*
* Simple command line processing. This class provides functionality for parsing
* command line arguments that is coded over and over again in main methods. The
* model is that command line arguments have the form:
*
* <pre>
* option_args* free_form*
* </pre>
*
* where each element in option_args is an option starting with a '-' character
* and each element in free_form is a string. Option arguments have the syntax:
*
* <pre>
* '-'NAME[=VALUE]
* </pre>
*
* where NAME is the option identifier and VALUE is the string argument for that
* option.
* <p>
* An example use of the class is as follows:
*
* <pre>
* static void main(String[] args) {
* Arguments cl = new Arguments();
* cl.parse(args);
* if (cl.getOption("verbose") != null) ... ;
* String file = cl.getArgument(0);
* String path = cl.getOption("classpath");
* </pre>
*
* @author Jan Vitek
*/
public class Arguments {
static public abstract class Arg {
abstract public String usage();
abstract public boolean validate();
@Override public String toString() {
Field[] fields = getFields(this);
String r="";
for( Field field : fields ){
String name = field.getName();
Class cl = field.getType();
try{
if( cl.isPrimitive() ){
if( cl == Boolean.TYPE ){
boolean curval = field.getBoolean(this);
if( curval ) r += " -"+name;
}
else if( cl == Integer.TYPE ) r+=" -"+name+"="+field.getInt(this);
else if( cl == Float.TYPE ) r+=" -"+name+"="+field.getFloat(this);
else if( cl == Double.TYPE ) r+=" -"+name+"="+field.getDouble(this);
else if( cl == Long.TYPE ) r+=" -"+name+"="+field.getLong(this);
else continue;
} else if( cl == String.class )
if (field.get(this)!=null) r+=" -"+name+"="+field.get(this);
} catch( Exception e ) { Log.err("Argument failed with ",e); }
}
return r;
}
}
static public class MissingArgumentError extends Error {
final String m;
MissingArgumentError(String s) { m = s; }
public String toString() { return ( m != null ) ? m : super.toString(); }
}
/**
* Optional arguments. The instance fields of this class are treated as
* optional arguments, if they appear on the command line they will be
* extracted and the corresponding field will be set to the extracted value.
* If not found the field is left untouched (the orginal value is not
* modified).
*/
static public class Opt extends Arg {
public String usage() { return ""; }
public boolean validate() { return true; }
}
/**
* Required arguments. The instance fields of this class are treated as
* required arguments, arguments with keywords matching each one of the
* primitive and string fields of the object must appear on the command line.
* If they all do they will extracted and the corresponding field will be set
* to the extracted value. If any one of the fields is missing
*/
static public class Req extends Arg {
public String usage() { return ""; }
public boolean validate() { return true; }
}
/** Current argument list. The list may grow and shrink as arguments are processed.
*/
private Entry[] commandLineArgs;
/** Create a new CommandLine object with an initial argument array.
* @param args
* array of options and argument that will be parsed.
*/
public Arguments(String[] args) { parse(args); }
/** Create a new CommandLine object with no arguments. */
public Arguments() { parse(new String[0]); }
/**
* Returns the number of remaining command line arguments.
*/
public int size() { return commandLineArgs.length; }
public String get(int i) { return commandLineArgs[i].val; }
/**
* Add a new argument to this command line. The argument will be parsed and
* add at the end of the list. Bindings have the following format
* "-name=value" if value is empty, the binding is treated as an option.
* Options have the form "-name". All other strings are treated as values.
*
* @param str
* a string
*/
public int addArgument(String str, String next) {
int i = commandLineArgs.length;
int consumed = 1;
commandLineArgs = Arrays.copyOf(commandLineArgs, i + 1);
/*
* Flags have a null string as val and flag of true; Binding have non-empty
* name, a non-null val (possibly ""), and a flag of false; Plain strings
* have an empty name, "", a non-null, non-empty val, and a flag of true;
*/
if( str.startsWith("-") ){
int startOffset = (str.startsWith("--"))? 2 : 1;
String arg = "";
String opt;
boolean flag = false;
int eqPos = str.indexOf("=");
if( eqPos > 0 || (next!=null && !next.startsWith("-"))){
if( eqPos > 0 ){
opt = str.substring(startOffset, eqPos);
arg = str.substring(eqPos + 1);
}else{
opt = str.substring(startOffset);
arg = next;
consumed = 2;
}
}else{
flag = true;
opt = str.substring(startOffset);
}
commandLineArgs[i] = new Entry(opt, arg, flag, i);
return consumed;
}else{
commandLineArgs[i] = new Entry("", str, true, i);
return consumed;
}
}
public <TArg extends Arg> TArg extract(TArg arg) throws MissingArgumentError {
Field[] fields = getFields(arg);
int count = extract(arg, fields);
if( arg instanceof Req && count != fields.length )
throw new MissingArgumentError(arg.usage());
return arg;
}
/**
* Extracts bindings and options; and sets appropriate fields in the
* CommandLineArgument object.
*/
private int extract(Arg arg, Field[] fields) {
int count = 0;
for( Field field : fields ){
String name = field.getName();
Class cl = field.getType();
String opt = getValue(name); // optional value
try{
if( cl.isPrimitive() ){
if( cl == Boolean.TYPE ){
boolean curval = field.getBoolean(arg);
boolean xval = curval;
if( opt != null ) xval = !curval;
if( "1".equals(opt) || "true" .equals(opt) ) xval = true;
if( "0".equals(opt) || "false".equals(opt) ) xval = false;
if( opt != null ) field.setBoolean(arg, xval);
}else if( opt == null || opt.length()==0 ) continue;
else if( cl == Integer.TYPE ) field.setInt(arg, Integer.parseInt(opt));
else if( cl == Float.TYPE ) field.setFloat(arg, Float.parseFloat(opt));
else if( cl == Double.TYPE ) field.setDouble(arg, Double.parseDouble(opt));
else if( cl == Long.TYPE ) field.setLong(arg, Long.parseLong(opt));
else continue;
count++;
}else if( cl == String.class ){
if( opt != null ){
field.set(arg, opt);
count++;
}
}
} catch( Exception e ) { Log.err("Argument failed with ",e); }
}
Arrays.sort(commandLineArgs);
for( int i = 0; i < commandLineArgs.length; i++ )
commandLineArgs[i].position = i;
return count;
}
/**
* Return the value of a binding (e.g. "value" for "-name=value") and the
* empty string "" for an option ("-name" or "-name="). A null value is
* returned if no binding or option is found.
*
* @param name string name of the option or binding
*/
public String getValue(String name) {
for( Entry e : commandLineArgs )
if( name.equals(e.name) ) return e.val;
return System.getProperty("h2o.arg."+name);
}
/**
* Parse the command line arguments and extracts options. The current
* implementation allows the same command line instance to parse several
* argument lists, the results will be merged.
*
* @param s the array of arguments to be parsed
*/
private void parse(String[] s) {
commandLineArgs = new Entry[0];
for( int i = 0; i < s.length; ) {
String next = (i+1<s.length)? s[i+1]: null;
i += addArgument(s[i],next);
}
}
public String toString() {
String[] ss = toStringArray();
String result = "";
for( String s : ss ) result += s+" ";
return result;
}
public String[] toStringArray() {
String[] result = new String[commandLineArgs.length];
for( int i = 0; i < commandLineArgs.length; i++ )
result[i] = commandLineArgs[i].toString();
return result;
}
/**
* Keep only the fields which are either primitive or strings.
*/
static private Field[] getFields(Arg arg) {
Class target_ = arg.getClass();
Field[] fields = new Field[0];
while( target_ != null ){
int flen = fields.length;
Field[] f2 = target_.getDeclaredFields();
fields = Arrays.copyOf(fields,flen+f2.length);
System.arraycopy(f2,0,fields,flen,f2.length);
target_ = target_.getSuperclass();
}
Field[] keep = new Field[fields.length];
int num = 0;
for( Field field : fields ){
field.setAccessible(true);
if( Modifier.isStatic(field.getModifiers()) ) continue;
if( field.getType().isPrimitive() || field.getType() == String.class ) keep[num++] = field;
}
Field[] res = new Field[num];
for( int i = 0; i < num; i++ )
res[i] = keep[i];
return res;
}
/**
* Private class for holding arguments. There are three cases: a flag, a
* binding, or a plain string. - Flags have a null string as val and flag of
* true; - Binding have non-empty name, a non-null val (possibly ""), and a
* flag of false; - Plain strings have an empty name, "", a non-null,
* non-empty val, and a flag of true;
*/
private static class Entry implements Comparable {
//true if this is a flag, i.e. ("-name" or "-name=")
boolean flag;
// option name, -name=value
String name;
// position in the argument list
int position;
// option value, -name=value
String val;
Entry(String _name, String _val, boolean _flag, int _position) {
assert !_name.startsWith("-") && !_name.contains("=");
name = _name; val = _val; flag = _flag; position = _position;
}
public int compareTo(Object o) { return position - ((Entry) o).position; }
public String toString() {
String result = " ";
if( !name.equals("") ) result += "-";
result += name;
if( !flag ) result += "=";
if( val != null ) result += val;
return result;
}
}
}