package edu.cmu.minorthird.util; import java.io.File; import java.io.FileOutputStream; import java.io.FileReader; import java.io.IOException; import java.io.LineNumberReader; import java.io.PrintStream; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Properties; import java.util.Set; import org.apache.log4j.Logger; /** * A lightweight command-line processing tool. * * @author William Cohen */ public abstract class BasicCommandLineProcessor implements CommandLineProcessor,Saveable{ private static Logger log=Logger.getLogger(BasicCommandLineProcessor.class); // cache args last processed for saving private String[] processedArgs=null; // cache values associated with args private Properties argValues=new Properties(); @Override public final void processArguments(String[] args){ processedArgs=args; int k=consumeArguments(args,0); if (k<args.length) throw new IllegalArgumentException("illegal argument "+args[k]); } @Override public boolean shouldTerminate(){ return false; } @Override public final int consumeArguments(String[] args,int startPos){ try{ int pos=startPos; while(pos<args.length){ String arg=args[pos]; if(arg.startsWith("--")){ arg=arg.substring(2); }else if(arg.startsWith("-")){ arg=arg.substring(1); }else{ return pos-startPos; } try{ log.debug("Looking up method '"+arg+"' in "+getClass()); Method m=getClass().getMethod(arg,new Class[]{}); log.debug("Consuming '-"+arg+"' in "+getClass()); System.out.println("Option: "+arg); Object result=m.invoke(this,new Object[]{}); pos+=1; if(result instanceof CommandLineProcessor&&result!=null){ pos+=((CommandLineProcessor)result).consumeArguments(args,pos); } //For when a label follows the argument }catch(NoSuchMethodException ex){ try{ Method ms=getClass().getMethod(arg,new Class[]{String.class}); if(pos+1<args.length){ log.debug("Consuming '-"+arg+"' '"+args[pos+1]+"' in "+getClass()); System.out.println("Option: "+arg+"="+args[pos+1]); Object result=ms.invoke(this,new Object[]{args[pos+1]}); pos+=2; if(result instanceof CommandLineProcessor){ pos+=((CommandLineProcessor)result).consumeArguments(args,pos); } }else{ throw new IllegalArgumentException( "no argument found to option '-"+arg+"'"); } }catch(NoSuchMethodException ex2){ return pos-startPos; } } } return pos-startPos; }catch(IllegalAccessException iax){ iax.printStackTrace(); throw new IllegalArgumentException("error: "+iax); }catch(InvocationTargetException itx){ itx.printStackTrace(); throw new IllegalArgumentException("error: "+itx); } } /** Implements the -config option. */ public void config(String fileName){ config(fileName,this); } /** Implements the -config option for the given clp. This is static * so that JointCommandLineProcessor can use it also. */ static public void config(String fileName,CommandLineProcessor clp){ try{ String[] fileOptions=loadOptionsInPropertiesFormat(new File(fileName)); clp.processArguments(fileOptions); }catch(IOException ex){ throw new IllegalArgumentException("error opening "+fileName+": "+ex); } } @Override public void usage(String errorMessage){ System.out.println(errorMessage); usage(); } /** Override this to print a meaningful usage error. * Default will list all commands other than 'usage', * 'help', 'getX', and 'setX'. */ @Override public void usage(){ Method[] genericMethods=Object.class.getMethods(); Set<String> stopList=new HashSet<String>(); stopList.add("usage"); stopList.add("help"); for(int i=0;i<genericMethods.length;i++){ Class<?>[] params=genericMethods[i].getParameterTypes(); if(params.length==0||params.length==1&¶ms[0].equals(String.class)){ stopList.add(genericMethods[i].getName()); } } log.info("usage for "+getClass()); Method[] methods=getClass().getMethods(); for(int i=0;i<methods.length;i++){ Class<?>[] params=methods[i].getParameterTypes(); if(!(stopList.contains(methods[i].getName())|| methods[i].getName().startsWith("get")||methods[i].getName() .startsWith("set"))){ if(params.length==0){ System.out.print(" [-"+methods[i].getName()+"]"); }else if(params.length==1&¶ms[0].equals(String.class)){ System.out.print(" [-"+methods[i].getName()+" foo]"); } } } System.out.println(); } /** Override this to make --help do something other than call usage */ public void help(){ usage(); } /** * A list of arguments from the command line, in order. * For instance, if the command line includes -foo bar, * then "foo" will appear on the propertyList. */ protected List<String> propertyList(){ List<String> result=new ArrayList<String>(); argValues.clear(); int k=0; while(k<processedArgs.length){ String opt=processedArgs[k]; String val=""; int delta=1; if(k+1<processedArgs.length&&!processedArgs[k+1].startsWith("-")){ val=processedArgs[k+1]; delta=2; } String prop=opt.substring(1); result.add(prop); argValues.setProperty(prop,val); k+=delta; } return result; } /** * The value assigned to a property from the command line. * For instance, if the command line includes -foo bar, * then propertyValue("foo") will return "bar". * This can only be called after propertyList has been * called. */ protected String propertyValue(String property){ return argValues.getProperty(property); } // // implements Saveable // private static final String CONFIG_FORMAT_NAME="Configuration file"; private static final String CONFIG_FORMAT_EXT=".config"; @Override public String[] getFormatNames(){ return new String[]{CONFIG_FORMAT_NAME}; }; @Override public String getExtensionFor(String format){ return CONFIG_FORMAT_EXT; } @Override public void saveAs(File file,String format) throws IOException{ if(!format.equals(CONFIG_FORMAT_NAME)) throw new IllegalArgumentException("illegal format "+format); PrintStream s=new PrintStream(new FileOutputStream(file)); for(Iterator<String> i=propertyList().iterator();i.hasNext();){ String prop=i.next(); s.println(prop+"="+propertyValue(prop)); } s.close(); //Properties props = new Properties(); //props.store(new FileOutputStream(file), "auto-saved configuration file"); } @Override public Object restore(File file) throws IOException{ throw new UnsupportedOperationException( "Can't restore a command line processor"); } // /** // * Test and/or example code. // * For sample output, invoke this with arguments -scoff you -laff -family -mom gerbil -taunt // */ // static public void main(String[] args){ // CommandLineProcessor p=new BasicCommandLineProcessor(){ // // String mother="hamster"; // // String father="elderberries"; // // String laffter="bwa ha ha ha ha ha!"; // // public void laff(){ // System.out.println("bwa ha ha ha ha ha!"); // } // // public void scoff(String atWhat){ // System.out.println("I scoff derisively at "+atWhat+"!"); // } // // // test that usage doesn't show getters/setters // public String getLaff(){ // return laffter; // } // // public void setLaff(String s){ // laffter=s; // } // // public CommandLineProcessor family(){ // return new BasicCommandLineProcessor(){ // // public void mom(String s){ // mother=s; // } // // public void dad(String s){ // father=s; // } // }; // } // // public void taunt(){ // System.out.println("Your mother was a "+mother+ // " and your father smelled of "+father+"!!!"); // } // //public void usage() { System.out.println("usage: [-laff] [-scoff foo]"); } // }; // p.processArguments(args); // } protected CommandLineProcessor tryToGetCLP(Object o){ if(o instanceof CommandLineProcessor.Configurable){ return ((CommandLineProcessor.Configurable)o).getCLP(); } throw new IllegalArgumentException(o+ " can't be configured from the command line"); } static private String[] loadOptionsInPropertiesFormat(File file) throws IOException{ List<String> accum=new ArrayList<String>(); LineNumberReader in=new LineNumberReader(new FileReader(file)); String line; while((line=in.readLine())!=null){ if(line.trim().length()>0&&line.charAt(0)!='#'){ int eqPos=line.indexOf('='); if(eqPos<0) inputError(in,line,file,"no equal-sign (=) in line"); else{ String option=line.substring(0,eqPos).trim(); String value=line.substring(eqPos+1).trim(); accum.add("-"+option); if(value.length()>0) accum.add(value); } } } return accum.toArray(new String[accum.size()]); } static private void inputError(LineNumberReader in,String line,File file, String msg){ log.warn(file+", line "+in.getLineNumber()+": "+msg); log.warn(" - incorrect line is '"+line+"'"); } }