// ********************************************************************** // // <copyright> // // BBN Technologies // 10 Moulton Street // Cambridge, MA 02138 // (617) 873-8000 // // Copyright (C) BBNT Solutions LLC. All rights reserved. // // </copyright> // ********************************************************************** // // $Source: /cvs/distapps/openmap/src/openmap/com/bbn/openmap/util/ArgParser.java,v $ // $RCSfile: ArgParser.java,v $ // $Revision: 1.6 $ // $Date: 2005/08/24 20:17:48 $ // $Author: dietrick $ // // ********************************************************************** package com.bbn.openmap.util; import java.util.Vector; /** * A simple class to manage the line arguments of a program. Takes the String[] * argv that is provided to the main method of a class, and separates them out, * depending on the options given to the ArgParser. After you create the parser, * add your options that you want. */ public class ArgParser { /** * The length to submit if you want a variable length list at the end of the * command line, like all the arguments left over. */ public final static int TO_END = -1; /** The program name that's using the parser. */ protected String programName; /** The Args that the parser is looking for. */ protected Vector args; /** The String array that holds all of the leftover argvs. */ protected String[] rest = new String[0]; /** The character flag for an option. */ protected char option = '-'; /** * Tells the Args to accept the first letter of their name for argv options * specified with one letter. */ protected boolean allowAbbr = true; /** * Create a parser for the named program. Automatically adds the -help * option. * * @param pName the program name. */ public ArgParser(String pName) { programName = pName; args = new Vector(); args.add(new HelpArg()); } /** * Add a argument to the parser. Don't include the '-' in the argName, * that's added automatically. Assumes that the option expects no arguments. * * @param argName the command line option * @param desc a help line description. */ public void add(String argName, String desc) { add(argName, desc, 0); } /** * Add a argument to the parser. Don't include the '-' in the argName, * that's added automatically. * * @param argName the command line option * @param desc a help line description. * @param expectedNumberOfArguments the number of option parameters expected * for this option. */ public void add(String argName, String desc, int expectedNumberOfArguments) { add(argName, desc, expectedNumberOfArguments, false); } /** * Add a argument to the parser. Don't include the '-' in the argName, * that's added automatically. * * @param argName the command line option * @param desc a help line description. * @param expectedNumberOfArguments the number of option parameters expected * for this option. * @param expectDashedArguments tell the parser that this option may have * arguments that may start with dashes, for instance, a negative * number. False by default. */ public void add(String argName, String desc, int expectedNumberOfArguments, boolean expectDashedArguments) { Arg newArg = new Arg(argName, desc, expectedNumberOfArguments, expectDashedArguments); args.add(newArg); if (Debug.debugging("parse")) { Debug.output("ArgParser: adding " + argName); } } /** * Parse and organize the array of Strings. If something goes wrong, bail() * may be called. * * @param argv arguments to be parsed * @return true if everything goes well, false if not */ public boolean parse(String[] argv) { try { if (argv == null || argv.length == 0) { return false; } for (int i = 0; i < argv.length; i++) { boolean hit = false; if (argv[i].charAt(0) == option) { String eval = argv[i].substring(1); for (int j = 0; j < args.size(); j++) { Arg curArg = (Arg) args.elementAt(j); if (curArg.is(eval, allowAbbr)) { if (Debug.debugging("parse")) { Debug.output("ArgParser: arg " + curArg.name + " reading values."); } if (!curArg.readArgs(argv, ++i)) { // Something's wrong with the // arguments. bail("ArgParser: Unexpected arguments with option " + curArg.name + ".", true); } hit = true; if (curArg.numExpectedValues != TO_END) { i += (curArg.numExpectedValues - 1); } else { i = argv.length; } } } if (hit == false) { // option flagged, but option unknown. bail(programName + ": unknown option " + argv[i], false); } } if (hit == false) { if (i == 0) { rest = argv; } else { int diff = argv.length - i; rest = new String[diff]; for (int k = 0; k < diff; k++) { rest[k] = argv[i + k]; if (rest[k].charAt(0) == option) { bail("ArgParser: Not expecting option in list of arguments.", true); } } } if (Debug.debugging("parse")) { Debug.output("ArgParser: adding " + rest.length + " strings to the leftover list."); } return true; } } } catch (ArrayIndexOutOfBoundsException aioobe) { bail("Expecting more arguments for option", true); } catch (NegativeArraySizeException nase) { return false; } return true; } /** * Called if something is messed up. Prints a message, and the usage * statement, if desired. * * @param message a message to display. * @param printUsageStatement true to display a list of available options */ public void bail(String message, boolean printUsageStatement) { Debug.output(message); if (printUsageStatement) printUsage(); System.exit(0); } /** * Tell the parser to accept first-letter representations of the options. * * @param set true to accept */ public void setAllowAbbr(boolean set) { allowAbbr = set; } /** * @return true the parser accepts first-letter representations of the * options */ public boolean getAllowAbbr() { return allowAbbr; } /** * @return a Vector of Arg objects */ public Vector getArgs() { return args; } /** * Return a Arg object with a particular name. This method shouldn't be used * to figure out if values have been passed in to an application. It's to * find out if an option is available to be chosen, not if it has. * * @param name of Arg * @return Arg with name */ public Arg getArg(String name) { for (int i = 0; i < args.size(); i++) { ArgParser.Arg arg = (ArgParser.Arg) args.elementAt(i); if (name.equalsIgnoreCase(arg.name)) { return arg; } } return null; } /** * Given an Arg name, return the values. Returns a zero length array * (non-null) value for options that don't require arguments. Returns null * if the option name wasn't found in the list, or if the option wasn't * chosen in the parsed array of Strings. * * @param name key string for values * @return values for name */ public String[] getArgValues(String name) { for (int i = 0; i < args.size(); i++) { ArgParser.Arg arg = (ArgParser.Arg) args.elementAt(i); if (name.equalsIgnoreCase(arg.name)) { if (arg.flagged) { return arg.values; } } } return null; } /** * @return the String[] that makes up the trailing Strings after the options * were parsed */ public String[] getRest() { return rest; } /** * Print a list of options added to the parser. */ public void printUsage() { Debug.output(programName + " Arguments:"); for (int i = 0; i < args.size(); i++) { ArgParser.Arg arg = (ArgParser.Arg) args.elementAt(i); StringBuffer sb = new StringBuffer(); String filler = arg.name.length() < 6 ? "\t\t" : "\t"; sb.append(" -").append(arg.name).append(filler).append(arg.description); if (arg.numExpectedValues == TO_END) { sb.append(" (Variable number of arguments expected)"); } else if (arg.numExpectedValues == 1) { sb.append(" (1 argument expected)"); } else { sb.append(" (").append(arg.numExpectedValues).append(" arguments expected)"); } Debug.output(sb.toString()); } } public static void main(String[] argv) { Debug.init(); ArgParser ap = new ArgParser("ArgParser"); ap.add("first", "First test argument, no parameters expected"); ap.add("second", "Second test argument, two parameters expected", 2); ap.add("third", "Third test argument, no parameters expected"); ap.add("fourth", "Fourth test argument, one parameter expected", 1); if (!ap.parse(argv)) { ap.printUsage(); System.exit(0); } int i; Vector args = ap.getArgs(); for (i = 0; i < args.size(); i++) { ArgParser.Arg a = (ArgParser.Arg) args.elementAt(i); Debug.output(a.toString()); } String[] rest = ap.getRest(); Debug.output("Rest:"); for (i = 0; i < rest.length; i++) { Debug.output(rest[i]); } } /** * A default version of the Arg class used to represent options for the * ArgParser to use. */ public class Arg { public String name; public String description; public int numExpectedValues; public String[] values = null; public char c; public boolean flagged = false; public boolean dashedArguments = false; /** * Create an Arg with a name and help line description. * * @param aName name of arg * @param desc description of arg, used in usage statement */ public Arg(String aName, String desc) { this(aName, desc, 0); } /** * Create an Arg with a name and help line description, along with a * number of expected arguments to follow this option. * * @param aName name of arg * @param desc description of arg, used in usage statement * @param expectedNumberOfArguments number of values expected for * argument */ public Arg(String aName, String desc, int expectedNumberOfArguments) { this(aName, desc, expectedNumberOfArguments, false); } /** * Create an Arg with a name and help line description, along with a * number of expected arguments to follow this option. Has an argument * to not check for arguments that may start with dashes, in case one of * the arguments may be a negative number. * * @param aName name of arg * @param desc description of arg, used in usage statement * @param expectedNumberOfArguments number of values expected for * argument * @param expectDashedArguments if they could be dashed (like a negative * number) */ public Arg(String aName, String desc, int expectedNumberOfArguments, boolean expectDashedArguments) { name = aName; description = desc; numExpectedValues = expectedNumberOfArguments; c = name.charAt(0); dashedArguments = expectDashedArguments; } /** * Returns true if the atg string matches the name of the Arg, or, if * allowAbbr is true, returns true if the arg length is one and it * matches the first letter of the arg name. * * @param arg string to test * @param allowAbbr ok to check first letter * @return true if a match */ public boolean is(String arg, boolean allowAbbr) { if (name.equalsIgnoreCase(arg)) { return true; } if (allowAbbr && arg.length() == 1) { if (arg.charAt(0) == c) { return true; } } return false; } /** * Runs through the array of Strings, starting at the argIndex, and * creates the values array from it. Uses the expected number of * arguments to tell when it's done. Returns true if everything happens * as expected. * * @param argv the entire array passed to the parser. * @param argIndex the index of the first option argument value. * @return true if what was read was what was expected. */ public boolean readArgs(String[] argv, int argIndex) throws ArrayIndexOutOfBoundsException, NegativeArraySizeException { if (numExpectedValues != TO_END) { values = new String[numExpectedValues]; } else { values = new String[argv.length - argIndex]; } for (int i = 0; i < values.length; i++) { values[i] = argv[argIndex + i]; if (values[i].charAt(0) == option && !dashedArguments) { if (numExpectedValues != TO_END) { Debug.output("ArgParser: Option " + name + " expects " + numExpectedValues + (numExpectedValues == 1 ? " argument." : " arguments.")); } else { Debug.output("ArgParser: Option " + name + " not expecting options after its values."); } return false; // Unexpected argument. } } flagged = true; return true; } public String toString() { StringBuffer sb = new StringBuffer(); sb.append("Arg: ").append(name).append(" expects ").append(numExpectedValues) .append((numExpectedValues == 1 ? " value.\n" : " values.\n")); if (values != null) { sb.append("Values: "); for (int i = 0; i < values.length; i++) { sb.append("[").append(values[i]).append("]"); } sb.append("\n"); } return sb.toString(); } } /** * A Arg class to spur off help messages. Gets added automatically to the * parser. */ public class HelpArg extends ArgParser.Arg { public HelpArg() { super("help", "Print usage statement, with arguments.", 0); } public boolean is(String arg, boolean allowAbbr) { boolean askingForHelp = super.is(arg, allowAbbr); if (askingForHelp) { ArgParser.this.bail("", true); } return false; } } }