/*
* @(#)GetOptions.java
*/
package jas.util;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;
/**
* GetOptions is used to help parse command line arguments. It is loosely
* based on Paul Raines' GetOpt module with modifications to handle multiple
* classes wanting access to the same arguments. The main design difference
* is that instead of doing all the work in the constructor we do it with
* addOption() and parseArgs(). Furthermore, the Hashtables are different, etc.
*
* @author Peter Armstrong
*
*/
public class GetOptions extends Object {
/**
* Creates an empty GetOptions object.
*
* Options are added to the object using the addOption method, unlike
* GetOpt which adds them in the constructor.
*/
public GetOptions()
{
shortToLongMap = new Hashtable();
longToIsFlagMap = new Hashtable();
longToOptionsMap = new Hashtable();
longToComment = new Hashtable();
params = new String[0];
}
/**
* Adds an option to be recognized by parseArgs.
*
* @param longName the full name of the option
* @param flag true if the option is a flag (takes 0 parameters), false otherwise
*/
public void addOption(String longName, boolean flag)
{
longToIsFlagMap.put(longName, new Boolean(flag));
}
/**
* Adds an option to be recognized by parseArgs.
*
* @param longName the full name of the option
* @param flag true if the option is a flag (takes 0 parameters), false otherwise
* @param comment description of the arguments to the option
*/
public void addOption(String longName, boolean flag, String comment)
{
longToIsFlagMap.put(longName, new Boolean(flag));
longToComment.put(longName, comment);
}
/**
* Adds an option to be recognized by parseArgs.
*
* @param shortName the single character name of the option
* @param flag true if the option is a flag (takes 0 parameters), false otherwise
*/
public void addOption(char shortName, boolean flag)
{
addOption((new Character(shortName)).toString(), shortName, flag);
}
/**
* Adds an option to be recognized by parseArgs.
*
* @param shortName the single character name of the option
* @param flag true if the option is a flag (takes 0 parameters), false otherwise
* @param comment description of the arguments to the option
*/
public void addOption(char shortName, boolean flag, String comment)
{
addOption((new Character(shortName)).toString(), shortName, flag, comment);
}
/**
* Adds an option to be recognized by parseArgs.
*
* @param longName the full name of the option
* @param shortName the single character name of the option
* @param flag true if the option is a flag (takes 0 parameters), false otherwise
*/
public void addOption(String longName, char shortName, boolean flag)
{
addOption(longName, shortName, flag, null);
}
/**
* Adds an option to be recognized by parseArgs.
*
* @param longName the full name of the option
* @param shortName the single character name of the option
* @param flag true if the option is a flag (takes 0 parameters), false otherwise
* @param comment description of the arguments to the option
*/
public void addOption(String longName, char shortName, boolean flag, String comment)
{
shortToLongMap.put((new Character(shortName)).toString(), longName);
longToIsFlagMap.put(longName, new Boolean(flag));
if (comment != null) {
longToComment.put(longName, comment);
}
}
/**
* Returns true or false depending on if the option with the long name longName
* is in fact a real option.
*
* @param longName a string describing the option to query for.
* @return true or false.
*/
public boolean isLegalOption(String longName)
{
return longToIsFlagMap.containsKey(longName);
}
/**
* Returns true or false depending on if the option with the long name longName was given
* in either the short (if one exists) or the long form.
*
* @param longName a string describing the option to query for.
* @return true or false.
*/
public boolean hasOption(String longName)
{
return longToOptionsMap.containsKey(longName);
}
/**
* Returns the value given for the option with the long name longName.
* Returns "FLAG" if the option was given but is a flag (takes no arguments).
*
* @param longName a string describing the option to query for.
* @return value of option, "FLAG" or null.
*/
public String getOption(String longName)
{
return longToOptionsMap.get(longName).toString();
}
/**
* Returns the comment given for the option with the long name longName.
*
* @param longName a string describing the option to query for.
* @return value of comment or null if no comment exists.
*/
public String getComment(String longName)
{
if (longToComment.containsKey(longName))
{
return longToComment.get(longName).toString();
}
return null;
}
/**
* Exception to throw when an invalid argument is encountered.
*/
public static class BadArguments extends Exception
{
public BadArguments() { super(); }
public BadArguments(String s) { super(s); }
}
public void dumpOptions()
{
System.out.println("Options:");
System.out.println();
System.out.println("Short Long Value Comment");
System.out.println("Name Name Required");
System.out.println("----- ------------ -------- ---------------------------");
Enumeration vo = getValidOptions();
while (vo.hasMoreElements()) System.out.println(vo.nextElement());
}
/**
* Return an enumeration of String representations of <shortName, longName, isFlag> tuples.
* This is used to generate the -help message so it's not just frivolous debug code.
*/
private Enumeration getValidOptions()
{
//first we get "normal" options which have all 3 attributes
Enumeration shortNames = shortToLongMap.keys();
Vector v = new Vector();
String shortName = "";
String longName = "";
while (shortNames.hasMoreElements())
{
shortName = shortNames.nextElement().toString();
longName = shortToLongMap.get(shortName).toString();
v.addElement(formElement(shortName, longName));
}
//now we need to check if we missed any options with no shortNames
Enumeration longNames = longToIsFlagMap.keys();
while (longNames.hasMoreElements())
{
longName = longNames.nextElement().toString();
if (!shortToLongMap.containsKey(shortName)) {
v.addElement(formElement(" ", longName));//no short name so fill in the 1 char space
}
}
return v.elements();
}
private String formElement(String shortName, String longName)
{
String valueReqd = ((Boolean)longToIsFlagMap.get(longName)).booleanValue() ? "no" : "yes";
return (pad(shortName,5)+" "+pad(longName,12)+" "+pad(valueReqd,8)+" "+getComment(longName));
}
private String pad(String value, int length)
{
String padding = " ";
int l = value.length();
if (l == length) return value;
else if (l < length) return value + padding.substring(0,length-l);
else return value.substring(length);
}
/**
* Parses the command line arguments.
*
* @param argv the array of arguments (eg. args as given to main())
* @exception GetOptions.BadArguments an invalid arg or wrong # of args is encountered
*/
public void parseArgs(String[] argv) throws BadArguments {
int i, j;
String shortName, longName, exceptionMsg;
try {
for (i = 0; i < argv.length; i++) {
//if we have an empty option then the rest is considered to be parameters not options
if (argv[i] == "-" || argv[i] == "--") {
i++;
break;
}
//any option will begin with at least one -
if (argv[i].charAt(0) == '-') {
if (argv[i].charAt(1) == '-') {
// longName option
longName = argv[i].substring(2);
try {
Boolean isFlag = (Boolean)longToIsFlagMap.get(longName);
if (!isFlag.booleanValue()) {
//it's not a flag, so put the next arg in as its arguments
//PROGRAMMER'S NOTE: WE ARE INCREMENTING THE LOOP INDEX HERE!!!
longToOptionsMap.put(longName, argv[++i]);
} else {
longToOptionsMap.put(longName, "FLAG");
}
} catch (Exception e1) {
if (isLegalOption(longName)) {
exceptionMsg = "The " + longName + " option needs a different number of arguments than you provided.";
} else {
exceptionMsg = "There is no long option called \"" + longName + "\".";
}
throw new BadArguments(exceptionMsg);
}
} else {
// array of character options (only the last one can have args)
for (j = 1; j < argv[i].length(); j++) {
//process each character option (start at one since argv[0] == -
//NOTE: The loop will be executed (argv[i].length() - 1) times IFF
// all the options are flags. If an option takes arguments
// then it is defined to be the last option and we break out
// of the loop immediately.
shortName = argv[i].substring(j,j+1);
Boolean isFlag;
try {
longName = shortToLongMap.get(shortName).toString();
isFlag = (Boolean)longToIsFlagMap.get(longName);
} catch (Exception e) {
throw new BadArguments("There is no short option called \"" + shortName + "\".");
}
if (isFlag == null) {
throw new BadArguments("There is no short option called \"" + shortName + "\".");
} else if (isFlag.booleanValue()) {
//it is a flag so put the longName in along with the String value of the
//boolean argument
longToOptionsMap.put(longName,isFlag.toString());
} else {
//it's not a flag, so if the rest of this string is non-null
//then it is the argument else the next arg is the argument
//PROGRAMMER'S NOTE: WE MIGHT INCREMENT THE LOOP INDEX HERE!!!
if (j == (argv[i].length() - 1)) {
//the rest of this string is null (this is the last character in
//the argument, so the next argument is the argument to this option)
longToOptionsMap.put(longName, argv[++i]);
//now we need to break out of this inner for loop before we do
//something stupid (even though this *was* the last option
//we've now messed with i)
break;
} else {
//the rest of this string is the argument
longToOptionsMap.put(longName, argv[i].substring(j+1));
//now we need to skip to the next argument since this means
//the end of the single character options
break;
}//if
}//if
}//for
}//if
} else {
// we have reached the non-option arguments
break;
}//if
}//for
// fill in params[]
params = new String[(argv.length - i)];
for (j = 0; i < argv.length; i++, j++) {
params[j] = argv[i];
}
} catch (java.lang.ArrayIndexOutOfBoundsException eek) {
//The only way this could have happened is if the last option needed an argument
//and none was given, causing the method to reference the argument blindly and overstep
//the array bounds.
throw new BadArguments("The last option you specified requires a value and you did not provide one.");
}
}//parseArgs
/**
* Returns list of parameters
*/
public String[] getParams() {
if (pVector != null) {
params = new String[pVector.size()];
pVector.copyInto(params);
pVector = null;
}
return params;
}
/**
* Returns a specific parameter
*/
public String getParam(int pNum) {
if (pVector != null) {
params = new String[pVector.size()];
pVector.copyInto(params);
pVector = null;
}
return params[pNum];
}
/**
* Returns number of parameters
*/
public int numParams() {
if (pVector == null) {
return params.length;
} else {
return pVector.size();
}
}
/**
* Add a parmeter
*/
public void addParam(String param) {
if (pVector == null) {
int capacity = (params.length > 0 ? params.length : 5);
pVector = new Vector(capacity,5);
for (int i = 1; i < params.length; i++)
pVector.addElement(params[i]);
}
pVector.addElement(param);
}
/** The list of arguments that followed the options. */
protected String[] params;
/** Vector used to dynamically build options. */
protected Vector pVector = null;
/** Maps a short option to its equivalent long one. */
protected Hashtable shortToLongMap; //(shortName, longName)*
/** Maps a long option to whether it is a flag (takes no parameters). */
protected Hashtable longToIsFlagMap; //(longName, Boolean)*
/** Maps a long option to its comment. */
protected Hashtable longToComment; //(longName, comment)*
/**
* The storage of options. An option can have either no value or a String value of
* colon-delimited arguments.
*/
protected Hashtable longToOptionsMap; //(longName, arguments)*
}