// Copyright 2000, CERN, Geneva, Switzerland.
package org.freehep.util.commandline;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.TreeMap;
import java.util.Vector;
import java.util.Map.Entry;
/**
* CommandLine parses a command line for parameters and options.
*
* @author Mark Donszelmann (CommandLine)
* @version $Id: CommandLine.java 8584 2006-08-10 23:06:37Z duns $
*/
public class CommandLine {
// requested
private String name;
private String description;
private int numberOfParameters;
private Hashtable options; // (name or shortName, Option)
private TreeMap sortedOptions; // (name, Option);
private Vector parameters;
private CommandLineException exception;
private boolean multiLevel;
private boolean check = true;
// supplied
private Vector arguments = new Vector();
private Hashtable flags = new Hashtable();
/**
* Creates a CommandLine object to be used for parsing a set of arguments
*
* @param name name of the command
* @param description description of the command
* @param numberOfParameters minumum number of parameters
* @param multiLevel forces the command line to be multi-level (i.e. does not check for
* max number of parameters
*/
public CommandLine(String name, String description, int numberOfParameters, boolean multiLevel) {
this.name = name;
this.description = description;
this.numberOfParameters = numberOfParameters;
this.multiLevel = multiLevel;
this.parameters = new Vector();
this.exception = null;
this.options = new Hashtable();
this.sortedOptions = new TreeMap(new Comparator() {
public int compare(Object obj1, Object obj2) {
return ((String)obj1).compareTo((String)obj2);
}
});
}
/**
* Creates a CommandLine object to be used for parsing a set of arguments
*
* @param name name of the command
* @param description description of the command
* @param numberOfParameters minumum number of parameters
*/
public CommandLine(String name, String description, int numberOfParameters) {
this(name, description, numberOfParameters, false);
}
/**
* enables checking of the options and parameters
*
* @param check sets checking
*/
public void enableChecking(boolean check) {
this.check = check;
}
/**
* Adds an option to be recognized by parse.
*
* @param longName the full name of the option
* @param shortName the short name of the option (may be set to null)
* @param comment description of the arguments to the option
*/
public void addOption(String longName, String shortName, String comment) {
addOption(new Option(longName, shortName, comment));
}
/**
* Adds an option to be recognized by parse.
*
* @param longName the full name of the option
* @param shortName the short name of the option (may be set to null)
* @param qualifiers gives a list of qualifiers you can use
* @param comment description of the arguments to the option
*/
public void addOption(String longName, String shortName, String[] qualifiers, String comment) {
addOption(new Option(longName, shortName, qualifiers, comment));
}
/**
* Adds an option to be recognized by parse.
*
* @param longName the full name of the option
* @param shortName the short name of the option (may be set to null)
* @param valueDescription description of the value
* @param comment description of the arguments to the option
*/
public void addOption(String longName, String shortName, String valueDescription, String comment) {
addOption(new Option(longName, shortName, valueDescription, comment));
}
/**
* Adds an option to be recognized by parse, without doing parameter or option checking.
* This is normally used for options such as -? -help and -version
*
* @param longName the full name of the option
* @param shortName the short name of the option (may be set to null)
* @param comment description of the arguments to the option
*/
public void addBailOutOption(String longName, String shortName, String comment) {
addOption(new Option(longName, shortName, comment, true));
}
/**
* Adds an option to be recognized by parse. This option can be specified multiple times
* on the commandline, and is immediately followed by its value. Usage (-Iincludedir)
*
* @param name the full name of the option (preferably one character)
* @param valueDescription what the value after the option means
* @param comment description of the argument to the option
*/
public void addMultiOption(String name, String valueDescription, String comment) {
addOption(new Option(name, name, valueDescription, comment, true));
}
private void addOption(Option option) {
options.put(option.getName(), option);
sortedOptions.put(option.getName(), option);
if (option.getShortName() != null) options.put(option.getShortName(), option);
}
/**
* Adds a parameter to be recognized by parse.
*
* @param name name of the parameter
* @param comment comment describing the parameter
*/
public void addParameter(String name, String comment) {
Parameter par = new Parameter(name, comment, (parameters.size() >= numberOfParameters));
parameters.addElement(par);
}
/**
* @return name of the command
*/
public String getName() {
return name;
}
/**
* @return description of the command
*/
public String getDescription() {
return description;
}
/**
* @return an enumeration of possible options (CommandLine.Option)
*/
public Enumeration getOptions() {
return options.keys();
}
/**
* @return an enumeration of mandatory and optional parameters (CommandLine.Parameter)
*/
public Enumeration getParameters() {
return parameters.elements();
}
/**
* @return true if the specified option is legal
*/
public boolean isLegalOption(String name) {
return options.containsKey(name);
}
/**
* Parses the supplied arguments.
*
* @param args arguments for the command, including all options
* @return true if the arguments were parsed correctly. False if a bailOut option was specified.
* @throws CommandLineException in case the parsing failed.
*/
public boolean parse(String[] args) throws CommandLineException {
boolean bailOut = false;
int i = 0;
while (i < args.length) {
if (args[i].charAt(0) == '-') {
// option
int j = (args[i].charAt(1) != '-') ? 1 : 2;
StringBuffer flag = new StringBuffer();
boolean qualified = false;
while (j < args[i].length()) {
switch(args[i].charAt(j)) {
case '-':
// multiple options in one argument (never the last)
checkflag(flag.toString(), null, qualified);
bailOut |= ((Option)options.get(flag.toString())).isBailOut();
flag = new StringBuffer();
qualified = false;
break;
case ':':
qualified = true;
String qualifier = args[i].substring(j+1);
j = args[i].length() - 1;
checkflag(flag.toString(), qualifier, qualified);
flag = new StringBuffer();
qualified = false;
break;
case '=':
String value = args[i].substring(j+1);
j = args[i].length() - 1;
checkflag(flag.toString()+"="+value, value, qualified);
flag = new StringBuffer();
qualified = false;
break;
default:
flag.append(args[i].charAt(j));
break;
}
j++;
}
// (last) option could be set, but value may follow...
if (flag.length() > 0) {
if (checkflag(flag.toString(), (i+1<args.length) ? args[i+1] : null, qualified)) {
i++;
}
Option option = (Option)options.get(flag.toString());
bailOut |= (option == null) ? false : option.isBailOut();
}
} else {
// arguments from here on
while (i < args.length) {
arguments.addElement(args[i]);
i++;
}
}
i++;
}
if (!bailOut) {
// check for too few arguments here...
if (arguments.size() < numberOfParameters) {
exception = new MissingArgumentException("Too few arguments: "+arguments.size()+
", while "+numberOfParameters+" "+
((parameters.size() == 1) ? "was" : "were")+" expected.");
throw exception;
}
if ((check) && (!multiLevel)) {
// check for too many arguments here ...
if ((parameters.size() == 0) ||
!((Parameter)parameters.elementAt(parameters.size()-1)).getName().endsWith("...")) {
if (arguments.size() > parameters.size()) {
exception = new TooManyArgumentsException("Too many arguments: "+arguments.size()+
", while "+parameters.size()+" "+
((parameters.size() == 1) ? "was" : "were")+" expected.");
throw exception;
}
}
}
}
exception = null;
return !bailOut;
}
private boolean checkflag(String flag, String value, boolean qualified) throws CommandLineException {
if ((check) && (flag.length() <= 0)) {
exception = new NoSuchOptionException("Option has zero length");
throw exception;
}
Option option;
if ((option = ((Option)options.get(flag)))!= null) {
if (qualified) {
// check qualifiers
String[] qualifiers = option.getQualifiers();
for (int j=0; j<qualifiers.length; j++) {
if (value.equals(qualifiers[j])) {
setFlag(option, value);
return false;
}
}
exception = new NoSuchQualifierException("Qualifier '"+value+"' for option '"+flag+"' does not exist.");
throw exception;
} else {
// did we expect a value
if (option.getValue() == null) {
setFlag(option, "true");
return false;
} else {
if (value == null) {
exception = new MissingArgumentException("Missing argument <"+option.getValue()+"> for option '"+flag+"'.");
throw exception;
}
setFlag(option, value);
return true;
}
}
}
// check for special multi options
for (Enumeration e = options.elements(); e.hasMoreElements(); ) {
Option opt = (Option)e.nextElement();
if (opt.isMulti() && flag.startsWith(opt.getName())) {
if (opt.getName().length() < flag.length()) {
setFlag(opt, flag.substring(opt.getName().length()));
return false;
} else {
exception = new MissingArgumentException("Missing argument <"+opt.getValue()+"> for option '"+opt.getName()+"'.");
throw exception;
}
}
}
// option does not exist
if (check) {
exception = new NoSuchOptionException("Option '"+flag+"' does not exist.");
throw exception;
}
return false;
}
private void setFlag(Option option, String value) {
if (option.isMulti()) {
if (flags.get(option.getName()) == null) {
flags.put(option.getName(), new Vector());
}
Vector values = (Vector)flags.get(option.getName());
values.addElement(value);
} else {
flags.put(option.getName(), value);
}
if (option.getShortName() != null) flags.put(option.getShortName(), value);
}
/**
* @return the last thrown exception
*/
public CommandLineException getException() {
return exception;
}
/**
* @return a textual representation of the parsed arguments
*/
public String toString() {
StringBuffer s = new StringBuffer("CommandLine [\n");
// line 1
s.append(" ");
s.append(name);
s.append(" - ");
s.append(description);
s.append("\n");
// line 2
for (Enumeration e = flags.keys(); e.hasMoreElements(); ) {
String key = (String)e.nextElement();
s.append(" option[");
s.append(key);
s.append("] = ");
Option option = (Option)options.get(key);
if (option.isMulti()) {
Vector values = (Vector)flags.get(key);
for (int i=0; i<values.size(); i++) {
s.append((String)values.elementAt(i)+", ");
}
} else {
s.append(flags.get(key));
}
s.append("\n");
}
// line 3
for (Enumeration e = arguments.elements(); e.hasMoreElements(); ) {
String arg = (String)e.nextElement();
s.append(" arg = ");
s.append(arg);
s.append("\n");
}
// line 4
if (exception != null) {
s.append(exception.getMessage());
s.append("\n");
}
s.append("\n]");
return s.toString();
}
/**
* @param name name of the option
* @return the value of a specified option. null is returned in case the option was not specified.
* true if this option was a flag.
*/
public String getOption(String name) {
return getOption(name, null);
}
/**
* @param name name of the option
* @return the value of a specified option. An empty vector is returned in case the option was not specified.
*/
public Vector getMultiOption(String name) {
Object flag = flags.get(name);
return (flag == null) ? new Vector() : (Vector)flag;
}
/**
* @param name name of the option
* @param defaultValue default value if option was not set
* @return the value of a specified option. null is returned in case the option was not specified.
* true if this option was a flag.
*/
public String getOption(String name, String defaultValue) {
Object flag = flags.get(name);
return (flag == null) ? defaultValue : flag.toString();
}
/**
* @param name name of the option
* @return true if the option was specified.
*/
public boolean hasOption(String name) {
return flags.containsKey(name);
}
/**
* @param name name of the argument
* @return a named argument. null in case the named argument was not supplied.
*/
public String getArgument(String name) {
for (int i=0; i<parameters.size(); i++) {
Parameter parameter = (Parameter)parameters.elementAt(i);
if (name.equals(parameter.getName())) {
return (String)arguments.elementAt(i);
}
}
return null;
}
/**
* @return string array of parameters
*/
public String[] getArguments() {
String[] s = new String[arguments.size()];
arguments.copyInto(s);
return s;
}
/**
* @return straing array of parameters which were not used by the command line
*/
public String[] getUnparsedArguments() {
String[] s = new String[arguments.size() - parameters.size()];
int j = 0;
for (int i=parameters.size(); i<arguments.size(); i++) {
s[j] = (String)arguments.elementAt(i);
}
return s;
}
private String pad(String str, int length) {
String padding = " ";
int l = str.length();
if (l == length) return str;
else if (l < length) return str + padding.substring(0,length-l);
else return str.substring(length);
}
/**
* Get the help message, suitable for printing in response to a -help option
*/
public String getHelp() {
StringBuffer s = new StringBuffer();
// line 1
s.append(name);
s.append(" - ");
s.append(description);
s.append("\n");
// line 2
s.append("Usage: ");
s.append(name);
if (options.size() > 0) {
s.append(" [-options]");
}
int len = 0;
for (int i=0; i<parameters.size(); i++) {
Parameter parameter = (Parameter)parameters.elementAt(i);
len = Math.max(len, parameter.getName().length());
if (i<numberOfParameters) {
s.append(" <");
s.append(parameter.getName());
s.append(">");
} else {
s.append(" [");
s.append(parameter.getName());
}
}
for (int i=numberOfParameters; i<parameters.size(); i++) {
s.append("]");
}
s.append("\n\n");
if (!parameters.isEmpty())
{
// line 3
s.append("where:\n");
// line 4
for (int i=0; i<parameters.size(); i++) {
Parameter parameter = (Parameter)parameters.elementAt(i);
s.append(" ");
s.append(pad(parameter.getName(), len));
s.append(" ");
s.append(parameter.getDescription());
s.append("\n");
}
s.append("\n");
}
if (!sortedOptions.isEmpty())
{
// line 5
s.append("where options include:\n");
// line 6
len = 0;
int slen = 0;
for(Iterator iterator = sortedOptions.entrySet().iterator(); iterator.hasNext(); ) {
Option option = (Option)((Entry)iterator.next()).getValue();
len = Math.max(len, option.getString().length());
slen = Math.max(slen, (option.getShortName() == null) ? 0 : option.getShortName().length());
}
boolean multi = false;
for(Iterator iterator = sortedOptions.keySet().iterator(); iterator.hasNext(); ) {
String key = (String)iterator.next();
Option option = (Option)sortedOptions.get(key);
s.append(" ");
if (slen > 0) {
if (option.getShortName() == null) {
s.append(option.isMulti() ? "*" : " ");
multi |= option.isMulti();
s.append(pad("", slen));
} else {
s.append("-");
s.append(pad(option.getShortName(), slen));
}
s.append(" ");
}
s.append(pad(option.getString(), len));
s.append(" ");
s.append(option.getDescription());
s.append("\n");
}
s.append("\n");
if (multi) {
s.append("(* option can be specified multiple times).\n");
s.append("\n");
}
}
return s.toString();
}
/**
* Keeps mandatory and optional parameter names.
*/
public static class Parameter {
private String name;
private String description;
private boolean optional;
public Parameter(String name, String description, boolean optional) {
this.name = name;
this.description = description;
this.optional = optional;
}
public String getName() {
return name;
}
public String getDescription() {
return description;
}
public boolean isOptional() {
return optional;
}
}
/**
* Keeps option names and types.
*/
public static class Option {
private String name;
private String shortName;
private String description;
private String[] qualifiers = null;
private String value = null;
private boolean multi = false;
private boolean bailOut;
public Option(String name, String shortName, String description) {
this(name, shortName, description, false);
}
public Option(String name, String shortName, String description, boolean bailOut) {
this.name = name;
this.shortName = shortName;
this.description = description;
this.bailOut = bailOut;
}
public Option(String name, String shortName, String[] qualifiers, String description) {
this(name, shortName, description);
this.qualifiers = qualifiers;
}
public Option(String name, String shortName, String value, String description) {
this(name, shortName, description);
this.value = value;
}
public Option(String name, String shortName, String value, String description, boolean multi) {
this(name, (multi) ? null : shortName, description);
this.value = value;
this.multi = multi;
}
public String getName() {
return name;
}
public String getShortName() {
return shortName;
}
public String getDescription() {
return description;
}
public boolean isBailOut() {
return bailOut;
}
public String[] getQualifiers() {
return qualifiers;
}
public String getValue() {
return value;
}
public boolean isMulti() {
return multi;
}
public String getString() {
StringBuffer s = new StringBuffer("-");
s.append(name);
if (value != null) {
if (!isMulti()) s.append(" ");
s.append("<");
s.append(value);
s.append(">");
} else if (qualifiers != null) {
s.append(":{");
for (int i=0; i<qualifiers.length; i++) {
if (i > 0) {
s.append("|");
}
s.append(qualifiers[i]);
}
s.append("}");
}
return s.toString();
}
}
}