/* Copyright (C) 2002 Univ. of Massachusetts Amherst, Computer Science Dept. This file is part of "MALLET" (MAchine Learning for LanguagE Toolkit). http://www.cs.umass.edu/~mccallum/mallet This software is provided under the terms of the Common Public License, version 1.0, as published by http://www.opensource.org. For further information, see the file `LICENSE' included with this distribution. */ /** @author Andrew McCallum <a href="mailto:mccallum@cs.umass.edu">mccallum@cs.umass.edu</a> */ package cc.mallet.util; import java.util.*; import java.io.*; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import cc.mallet.util.BshInterpreter; public abstract class CommandOption { static BshInterpreter interpreter; /** Maps a Java class to the array of CommandOption objects that are owned by it. */ static HashMap class2options = new HashMap (); Class owner; /** The name of the argument, eg "input" */ java.lang.String name; /** The display name of the argument type, eg "[TRUE|FALSE]" or "FILE" */ java.lang.String argName; /** The type of the argument, if present */ Class argType; boolean argRequired; java.lang.String shortdoc; java.lang.String longdoc; java.lang.String fullName; /** did this command option get processed, or do we just have default value */ boolean invoked = false; public CommandOption (Class owner, java.lang.String name, java.lang.String argName, Class argType, boolean argRequired, java.lang.String shortdoc, java.lang.String longdoc) { this.owner = owner; this.name = name; this.argName = argName; this.argType = argType; this.argRequired = argRequired; this.shortdoc = shortdoc; this.longdoc = longdoc; Package p = owner.getPackage(); this.fullName = (p != null ? p.toString() : "") + name; if (interpreter == null) interpreter = new BshInterpreter (); if (owner != CommandOption.class) { CommandOption.List options = (CommandOption.List) class2options.get (owner); if (options == null) { options = new CommandOption.List (""); class2options.put (owner, options); } options.add (this); } } /** @deprecated */ public CommandOption (Class owner, java.lang.String name, java.lang.String argName, Class argType, boolean argRequired, java.lang.String shortdoc) { this (owner, name, argName, argType, argRequired, shortdoc, null); } /** Give this CommandOption the opportunity to process the index'th argument in args. Return the next unprocessed index. */ public int process (java.lang.String[] args, int index) { //System.out.println (name + " processing arg " + args[index]); if (args.length == 0) return index; // System.out.println(index + ": " + args[index]); // Is there anything to process? if (index >= args.length || args[index] == null || args[index].length() < 2 || args[index].charAt(0) != '-' || args[index].charAt(1) != '-') return index; // Determine what the command name is java.lang.String optFullName = args[index].substring(2); int dotIndex = optFullName.lastIndexOf('.'); java.lang.String optName = optFullName; // Commands may have a package prefix if (dotIndex != -1) { java.lang.String optPackageName = optFullName.substring (0, dotIndex); if (owner.getPackage() != null && ! owner.getPackage().toString().endsWith(optPackageName)) return index; optName = optFullName.substring (dotIndex+1); } // Does the option name match the name of this CommandOption? if (! name.equals(optName)) return index; // We have now determined that this CommandOption is the correct one this.invoked = true; index++; if (args.length > index && (args[index].length() < 2 || (args[index].charAt(0) != '-' && args[index].charAt(1) != '-'))) { index = parseArg (args, index); } else { if (argRequired) { throw new IllegalArgumentException ("Missing argument for option " + optName); } else { // xxx This is not parallel behavior to the above parseArg(String[],int) method. parseArg (args, -index); // xxx was "" } //index++; } return index; } public static BshInterpreter getInterpreter () { return interpreter; } public static java.lang.String[] process (java.lang.Class owner, java.lang.String[] args) { CommandOption.List options = (CommandOption.List) class2options.get (owner); if (options == null) throw new IllegalArgumentException ("No CommandOptions registered for class "+owner); return options.process (args); } public static List getList (java.lang.Class owner) { CommandOption.List options = (CommandOption.List) class2options.get (owner); if (options == null) throw new IllegalArgumentException ("No CommandOptions registered for class "+owner); return options; } public static void setSummary (java.lang.Class owner, java.lang.String summary) { CommandOption.List options = (CommandOption.List) class2options.get (owner); if (options == null) throw new IllegalArgumentException ("No CommandOption.List registered for class "+owner); options.setSummary (summary); } public java.lang.String getFullName () { return fullName; } public java.lang.String getName () { return name; } public abstract java.lang.String defaultValueToString (); public abstract java.lang.String valueToString (); /** Return true is this CommandOption was matched by one of the processed arguments. */ public boolean wasInvoked () { return invoked; } /** Called after this CommandOption matches an argument. The default implementation simply calls parseArg(String), and returns index+1; unless index is negative, in which case it calls parseArg((String)null) and returns index. */ public int parseArg (java.lang.String args[], int index) { if (index < 0) { parseArg ((java.lang.String)null); return index; } else { parseArg (args[index]); return index+1; } } public void parseArg (java.lang.String arg) {} /** To be overridden by subclasses; "list" is the the CommandOption.List that called this option */ public void postParsing (CommandOption.List list) {} /** For objects that can provide CommandOption.List's (which can be merged into other lists. */ public static interface ListProviding { public CommandOption.List getCommandOptionList (); } public static void printOptionValues(Class owner) { CommandOption.List options = (CommandOption.List) class2options.get (owner); for (int i=0; i < options.size(); i++) { CommandOption option = options.getCommandOption(i); System.out.println(option.getName() + "\t=\t" + option.valueToString()); } } public static class List { ArrayList options; HashMap map; java.lang.String summary; private List (java.lang.String summary) { this.options = new ArrayList (); this.map = new HashMap (); this.summary = summary; add (new Boolean (CommandOption.class, "help", "TRUE|FALSE", false, false, "Print this command line option usage information. "+ "Give argument of TRUE for longer documentation", null) { public void postParsing(CommandOption.List list) { printUsage(value); System.exit(-1); } }); add (new Object (CommandOption.class, "prefix-code", "'JAVA CODE'", true, null, "Java code you want run before any other interpreted code. Note that the text "+ "is interpreted without modification, so unlike some other Java code options, "+ "you need to include any necessary 'new's when creating objects.", null)); add (new File (CommandOption.class, "config", "FILE", false, null, "Read command option values from a file", null) { public void postParsing(CommandOption.List list) { readFromFile(value); } } ); } public List (java.lang.String summary, CommandOption[] options) { this(summary); add(options); } public void setSummary (java.lang.String s) { this.summary = s; } public int size () { return options.size(); } public CommandOption getCommandOption (int index) { return (CommandOption) options.get(index); } public void add (CommandOption opt) { options.add (opt); map.put (opt.getFullName(), opt); } public void add (CommandOption[] opts) { for (int i = 0; i < opts.length; i++) add (opts[i]); } public void add (CommandOption.List opts) { for (int i = 0; i < opts.size(); i++) add (opts.getCommandOption(i)); } public void add (Class owner) { CommandOption.List options = (CommandOption.List) class2options.get (owner); if (options == null) throw new IllegalArgumentException ("No CommandOptions registered for class "+owner); add (options); } /** * Load configuration information from a file. If the filename ends * with ".xml", the file is interpreted as a Java XML configuration file. * Otherwise it is interpreted as a text config file (eg "key = value" on each line). * Note that text files can only use Latin 1 (en-us) characters, while * XML files can be UTF-8. */ public void readFromFile(java.io.File configurationFile) { try { Properties properties = new Properties(); if (configurationFile.getName().endsWith(".xml")) { properties.loadFromXML(new FileInputStream(configurationFile)); } else { properties.load(new FileInputStream(configurationFile)); } Enumeration keys = properties.propertyNames(); while (keys.hasMoreElements()) { java.lang.String key = (java.lang.String) keys.nextElement(); java.lang.String[] values = properties.getProperty(key).split("\\s+"); boolean foundValue = false; for (int i = 0; i < options.size(); i++) { CommandOption option = (CommandOption) options.get(i); if (option.name.equals(key)) { foundValue = true; option.parseArg(values, 0); break; } } } } catch (Exception e) { System.err.println("Unable to process configuration file: " + e.getMessage()); } } /** Parse and process command-line options in args. Return sub-array of args occurring after first non-recognized arg that doesn't begin with a dash. */ public java.lang.String[] process (java.lang.String[] args) { int index = 0; while (index < args.length) { int newIndex = index; for (int i = 0; i < options.size(); i++) { CommandOption o = (CommandOption)options.get(i); newIndex = o.process (args, index); if (newIndex != index) { o.postParsing(this); break; } } if (newIndex == index) { // All of the CommandOptions had their chance to claim the index'th option,, // but none of them did. printUsage(false); throw new IllegalArgumentException ("Unrecognized option " + index + ": " +args[index]); } index = newIndex; } return new java.lang.String[0]; } public int processOptions (java.lang.String[] args) { for (int index = 0; index < args.length;) { int newIndex = index; for (int i = 0; i < options.size(); i++) { CommandOption o = (CommandOption)options.get(i); newIndex = o.process (args, index); if (newIndex != index) { o.postParsing(this); break; } } if (newIndex == index) { if (index < args.length && args[index].length() > 1 && args[index].charAt(0) == '-' && args[index].charAt(1) == '-') { printUsage(false); throw new IllegalArgumentException ("Unrecognized option "+args[index]); } return index; } index = newIndex; } return args.length; } public void printUsage (boolean printLongDoc) { // xxx Fix this to have nicer formatting later. System.err.println (summary); for (int i = 0; i < options.size(); i++) { CommandOption o = (CommandOption) options.get(i); System.err.println ("--"+ o.name + " " + o.argName + "\n " + o.shortdoc); if (o.longdoc != null && printLongDoc) System.err.println (" "+o.longdoc); System.err.println (" Default is "+o.defaultValueToString()); } } public void logOptions (java.util.logging.Logger logger) { for (int i = 0; i < options.size(); i++) { CommandOption o = (CommandOption) options.get(i); logger.info (o.name+" = "+o.valueToString ()); } } } public static class Boolean extends CommandOption { public boolean value, defaultValue;; public Boolean (Class owner, java.lang.String name, java.lang.String argName, boolean argRequired, boolean defaultValue, java.lang.String shortdoc, java.lang.String longdoc) { super (owner, name, argName, Boolean.class, argRequired, shortdoc, longdoc); this.defaultValue = value = defaultValue; } public boolean value () { return value; } public void parseArg (java.lang.String arg) { if (arg == null || arg.equalsIgnoreCase("true") || arg.equals("1")) value = true; else if (arg.equalsIgnoreCase("false") || arg.equals("0")) value = false; else throw new IllegalArgumentException ("Boolean option should be true|false|0|1. Instead found "+arg); } public java.lang.String defaultValueToString() { return java.lang.Boolean.toString(defaultValue); } public java.lang.String valueToString () { return java.lang.Boolean.toString (value); } } public static class Integer extends CommandOption { public int value, defaultValue; public Integer (Class owner, java.lang.String name, java.lang.String argName, boolean argRequired, int defaultValue, java.lang.String shortdoc, java.lang.String longdoc) { super (owner, name, argName, Integer.class, argRequired, shortdoc, longdoc); this.defaultValue = value = defaultValue; } public int value () { return value; } public void parseArg (java.lang.String arg) { value = java.lang.Integer.parseInt(arg); } public java.lang.String defaultValueToString() { return java.lang.Integer.toString(defaultValue); } public java.lang.String valueToString () { return java.lang.Integer.toString (value); } } public static class IntegerArray extends CommandOption { public int[] value, defaultValue; public IntegerArray (Class owner, java.lang.String name, java.lang.String argName, boolean argRequired, int[] defaultValue, java.lang.String shortdoc, java.lang.String longdoc) { super (owner, name, argName, IntegerArray.class, argRequired, shortdoc, longdoc); this.defaultValue = value = defaultValue; } public int[] value () { return value; } public void parseArg (java.lang.String arg) { java.lang.String elts[] = arg.split(","); value = new int[elts.length]; for (int i = 0; i < elts.length; i++) value[i] = java.lang.Integer.parseInt(elts[i]); } public java.lang.String defaultValueToString() { StringBuffer b = new StringBuffer(); java.lang.String sep = ""; for (int i = 0; i < defaultValue.length; i++) { b.append(sep).append(java.lang.Integer.toString(defaultValue[i])); sep = ","; } return b.toString(); } public java.lang.String valueToString() { StringBuffer b = new StringBuffer(); java.lang.String sep = ""; for (int i = 0; i < value.length; i++) { b.append(sep).append(java.lang.Integer.toString(value[i])); sep = ","; } return b.toString(); } } public static class Double extends CommandOption { public double value, defaultValue; public Double (Class owner, java.lang.String name, java.lang.String argName, boolean argRequired, double defaultValue, java.lang.String shortdoc, java.lang.String longdoc) { super (owner, name, argName, Double.class, argRequired, shortdoc, longdoc); this.defaultValue = value = defaultValue; } public double value () { return value; } public void parseArg (java.lang.String arg) { value = java.lang.Double.parseDouble(arg); } public java.lang.String defaultValueToString() { return java.lang.Double.toString(defaultValue); } public java.lang.String valueToString () { return java.lang.Double.toString (value); } } public static class DoubleArray extends CommandOption { public double[] value, defaultValue; public DoubleArray (Class owner, java.lang.String name, java.lang.String argName, boolean argRequired, double[] defaultValue, java.lang.String shortdoc, java.lang.String longdoc) { super (owner, name, argName, IntegerArray.class, argRequired, shortdoc, longdoc); this.defaultValue = value = defaultValue; } public double[] value () { return value; } public void parseArg (java.lang.String arg) { java.lang.String elts[] = arg.split(","); value = new double[elts.length]; for (int i = 0; i < elts.length; i++) value[i] = java.lang.Double.parseDouble(elts[i]); } public java.lang.String defaultValueToString() { StringBuffer b = new StringBuffer(); java.lang.String sep = ""; for (int i = 0; i < defaultValue.length; i++) { b.append(sep).append(java.lang.Double.toString(defaultValue[i])); sep = ","; } return b.toString(); } public java.lang.String valueToString() { StringBuffer b = new StringBuffer(); java.lang.String sep = ""; for (int i = 0; i < value.length; i++) { b.append(sep).append(java.lang.Double.toString(value[i])); sep = ","; } return b.toString(); } } public static class String extends CommandOption { public java.lang.String value, defaultValue; public String (Class owner, java.lang.String name, java.lang.String argName, boolean argRequired, java.lang.String defaultValue, java.lang.String shortdoc, java.lang.String longdoc) { super (owner, name, argName, java.lang.String.class, argRequired, shortdoc, longdoc); this.defaultValue = value = defaultValue; } public java.lang.String value () { return value; } public void parseArg (java.lang.String arg) { value = arg; } public java.lang.String defaultValueToString() { return defaultValue; } public java.lang.String valueToString() { return value; } } public static class SpacedStrings extends CommandOption { public java.lang.String[] value, defaultValue; public SpacedStrings (Class owner, java.lang.String name, java.lang.String argName, boolean argRequired, java.lang.String[] defaultValue, java.lang.String shortdoc, java.lang.String longdoc) { super (owner, name, argName, java.lang.String.class, argRequired, shortdoc, longdoc); this.defaultValue = value = defaultValue; } public java.lang.String[] value () { return value; } public int parseArg (java.lang.String args[], int index) { int count = 0; this.value = null; while (index < args.length && (args[index].length() < 2 || (args[index].charAt(0) != '-' && args[index].charAt(1) != '-'))) { count++; java.lang.String[] oldValue = value; value = new java.lang.String[count]; if (oldValue != null) System.arraycopy (oldValue, 0, value, 0, oldValue.length); value[count-1] = args[index]; index++; } return index; } public java.lang.String defaultValueToString() { if (defaultValue == null) return "(null)"; StringBuffer sb = new StringBuffer(); for (int i = 0; i < defaultValue.length; i++) { sb.append (defaultValue[i]); if (i < defaultValue.length-1) sb.append (" "); } return sb.toString(); } public java.lang.String valueToString () { if (value == null) return "(null)"; java.lang.String val = ""; for (int i = 0; i < value.length; i++) { val += value [i] + " "; } return val; } } public static class File extends CommandOption { public java.io.File value, defaultValue; public File (Class owner, java.lang.String name, java.lang.String argName, boolean argRequired, java.io.File defaultValue, java.lang.String shortdoc, java.lang.String longdoc) { super (owner, name, argName, java.io.File.class, argRequired, shortdoc, longdoc); this.defaultValue = value = defaultValue; } public java.io.File value () { return value; } public void parseArg (java.lang.String arg) { value = new java.io.File(arg); } public java.lang.String defaultValueToString() { return defaultValue == null ? null : defaultValue.toString(); } public java.lang.String valueToString () { return value == null ? null : value.toString(); }; } // Value is a string that can take on only a limited set of values public static class Set extends CommandOption { public java.lang.String value, defaultValue; java.lang.String[] setContents; java.lang.String contentsString; public Set (Class owner, java.lang.String name, java.lang.String argName, boolean argRequired, java.lang.String[] setContents, int defaultIndex, java.lang.String shortdoc, java.lang.String longdoc) { super (owner, name, argName, java.io.File.class, argRequired, shortdoc, longdoc); this.defaultValue = this.value = setContents[defaultIndex]; this.setContents = setContents; StringBuffer sb = new StringBuffer (); for (int i = 0; i < setContents.length; i++) { sb.append (setContents[i]); sb.append (","); } this.contentsString = sb.toString(); } public java.lang.String value () { return value; } public void parseArg (java.lang.String arg) { value = null; for (int i = 0; i < setContents.length; i++) if (setContents[i].equals(arg)) value = setContents[i]; if (value == null) throw new IllegalArgumentException ("Unrecognized option argument \""+arg+"\" not in set "+contentsString); } public java.lang.String defaultValueToString() { return defaultValue; } public java.lang.String valueToString() { return value; } } public static class Object extends CommandOption { public java.lang.Object value, defaultValue; public Object (Class owner, java.lang.String name, java.lang.String argName, boolean argRequired, java.lang.Object defaultValue, java.lang.String shortdoc, java.lang.String longdoc) { super (owner, name, argName, java.lang.Object.class, argRequired, shortdoc, longdoc); this.defaultValue = value = defaultValue; } public java.lang.Object value () { return value; } public void parseArg (java.lang.String arg) { try { value = interpreter.eval (arg); } catch (bsh.EvalError e) { throw new IllegalArgumentException ("Java interpreter eval error\n"+e); } } public java.lang.String defaultValueToString() { return defaultValue == null ? null : defaultValue.toString(); } public java.lang.String valueToString() { return value == null ? null : value.toString(); } } public static class ObjectFromBean extends Object { public ObjectFromBean (Class owner, java.lang.String name, java.lang.String argName, boolean argRequired, java.lang.Object defValue, java.lang.String shortdoc, java.lang.String longdoc) { super (owner, name, argName, argRequired, java.lang.Object.class, shortdoc, longdoc); defaultValue = value = defValue; } public java.lang.Object value () { return value; } public void parseArg (java.lang.String arg) { // parse something like MaxEntTrainer,gaussianPriorVariance=10,numIterations=20 //System.out.println("Arg = " + arg); // First, split the argument at commas. java.lang.String fields[] = arg.split(","); //Massage constructor name, so that MaxEnt, MaxEntTrainer, new MaxEntTrainer() // all call new MaxEntTrainer() java.lang.String constructorName = fields[0]; if (constructorName.contains("(") || constructorName.contains(";")) // if contains ( or a ;, pass it though super.parseArg(arg); else { super.parseArg("new " + constructorName + "()"); // use default constructor to make the object } // Call the appropriate set... methods that appeared comma-separated // find methods associated with the class we just built Method methods[] = this.value.getClass().getMethods(); // find setters corresponding to parameter names. for (int i=1; i<fields.length; i++){ java.lang.String nameValuePair[] = fields[i].split("="); java.lang.String parameterName = nameValuePair[0]; java.lang.String parameterValue = nameValuePair[1]; //todo: check for val present! java.lang.Object parameterValueObject; try { parameterValueObject = getInterpreter().eval(parameterValue); } catch (bsh.EvalError e) { throw new IllegalArgumentException ("Java interpreter eval error on parameter "+ parameterName + "\n"+e); } boolean foundSetter = false; for (int j=0; j<methods.length; j++){ // System.out.println("method " + j + " name is " + methods[j].getName()); // System.out.println("set" + Character.toUpperCase(parameterName.charAt(0)) + parameterName.substring(1)); if ( ("set" + Character.toUpperCase(parameterName.charAt(0)) + parameterName.substring(1)).equals(methods[j].getName()) && methods[j].getParameterTypes().length == 1){ // System.out.println("Matched method " + methods[j].getName()); // Class[] ptypes = methods[j].getParameterTypes(); // System.out.println("Parameter types:"); // for (int k=0; k<ptypes.length; k++){ // System.out.println("class " + k + " = " + ptypes[k].getName()); // } try { java.lang.Object[] parameterList = new java.lang.Object[]{parameterValueObject}; // System.out.println("Argument types:"); // for (int k=0; k<parameterList.length; k++){ // System.out.println("class " + k + " = " + parameterList[k].getClass().getName()); // } methods[j].invoke(this.value, parameterList); } catch ( IllegalAccessException e) { System.out.println("IllegalAccessException " + e); throw new IllegalArgumentException ("Java access error calling setter\n"+e); } catch ( InvocationTargetException e) { System.out.println("IllegalTargetException " + e); throw new IllegalArgumentException ("Java target error calling setter\n"+e); } foundSetter = true; break; } } if (!foundSetter){ System.out.println("Parameter " + parameterName + " not found on trainer " + constructorName); System.out.println("Available parameters for " + constructorName); for (int j=0; j<methods.length; j++){ if ( methods[j].getName().startsWith("set") && methods[j].getParameterTypes().length == 1){ System.out.println(Character.toLowerCase(methods[j].getName().charAt(3)) + methods[j].getName().substring(4)); } } throw new IllegalArgumentException ("no setter found for parameter " + parameterName); } } } public java.lang.String defaultValueToString() { return defaultValue == null ? null : defaultValue.toString(); } public java.lang.String valueToString() { return value == null ? null : value.toString(); } } }