/* ***************************************************************************** * $Novell: ApplicationArguments.java,v 1.5 2003/01/28 16:52:10 $ * Copyright (c) 2001 Novell, Inc. All Rights Reserved. * * THIS WORK IS SUBJECT TO U.S. AND INTERNATIONAL COPYRIGHT LAWS AND * TREATIES. USE AND REDISTRIBUTION OF THIS WORK IS SUBJECT TO THE LICENSE * AGREEMENT ACCOMPANYING THE SOFTWARE DEVELOPMENT KIT (SDK) THAT CONTAINS * THIS WORK. PURSUANT TO THE SDK LICENSE AGREEMENT, NOVELL HEREBY GRANTS TO * DEVELOPER A ROYALTY-FREE, NON-EXCLUSIVE LICENSE TO INCLUDE NOVELL'S SAMPLE * CODE IN ITS PRODUCT. NOVELL GRANTS DEVELOPER WORLDWIDE DISTRIBUTION RIGHTS * TO MARKET, DISTRIBUTE, OR SELL NOVELL'S SAMPLE CODE AS A COMPONENT OF * DEVELOPER'S PRODUCTS. NOVELL SHALL HAVE NO OBLIGATIONS TO DEVELOPER OR * DEVELOPER'S CUSTOMERS WITH RESPECT TO THIS CODE. ******************************************************************************/ package arguments; import java.util.ArrayList; import java.text.ParseException; /** * This class represents a group of command-line arguments and determines * the semantics by which they are parsed. * <p> * Arguments are of two types, option arguments - i.e., the meaning of the * argument is derermined by an option letter, and positional arguments - i.e., * for arguments that have no option letter, the meaning is determined by * by its position on the commannd line. * <p> * Arguments are added to an instance of this class one at a time. * The order they are added determines the order in which they are displayed * on the usage string, and also determines the meaning when parsing positional * arguments. *<p> * The characteristics of each Argument added determines how it is parsed. * The following characteristics determine parsing semantics: *<br> * - Meaning determined by option or position - an option requires an option * letter. Arguments that are options can be specified in any order on the * command line. * If no option letter is given, meaning, i.e., its value, is determined by * its position on the command line. *<br> * - argument type - whether the argument type is Boolean, Integer, or String. *<br> * - required - whether the argument is optional or or required. Required * arguments must be present. *<br> * - single or multi-valued - whether the argument can have more than one value. * <p> * The parsing of command line arguments obeys the following rules: * <br> * - Options begin with a dash (-) and must be followed by at least one * option letter. A dash with no option letter is parsed as the value "-". *<p> * Boolean options can be strung together, i.e. -abc is the same as -a -b -c. *<p> * String and Integer options must be followed by a value. The value may * be specified on the command line immediately following the option or * be separated from the option by a space, i.e. -ddog is the same as -d dog. * A String or Integer option can be strung together with a boolean option, * as long as it is the last option in the grouping, i.e. -abcddog or -abcd dog * is the same as -a -b -c -d dog. *<p> * Boolean option have a single value, and are always optional arguments. *<p> * Multiple values for String and Integer options are specified by repeating * the option followed by the value, i.e. -d dog -d cat -dchick give the * option d the three values for the -d option of dog, cat, and chick. *<br> * Values for positional arguments are identified as a String or Integer without * a leading dash and where no option value is expected. For example, using * the previously defined Boolean options of -a, -b, & -c which have default * values of false, as well as the String option -d, * a command line specifying the following: *<br><br> * "-a please -b help -ddog me -d cat - -dchick now *<br><br. * is parsed as follows: * <br> * -a option true * <br> * -b option true * <br> * -c option false (default) * <br> * -d option - three values, dog, cat, & chick * <br> * positional parameter 1, please * <br> * positional parameter 2, help * <br> * positional parameter 3, me * <br> * positional parameter 4, - * <br> * positional parameter 5, now *<p> * Optional parameters (including Boolean) must have a default value specified * when added to this object. Required parameters have no default value. * The default value is used when the argument is not specified on the * command line. */ public class ApplicationArguments { private ArrayList appArguments; private String applicationName = "program"; /** * Constructor to create an ApplicationArguments class. */ public ApplicationArguments() { appArguments = new ArrayList(); return; } /** * Constructor to create an ApplicationArguments class with application * name. * * @param appName the application name for the usage String */ public ApplicationArguments(String appName) { applicationName = appName; appArguments = new ArrayList(); return; } /** * Constructor to create an ApplicationArguments class with initial * capacity. * * @param initialCapacity the initial size of the array that holds the * commandline arguments added. */ public ApplicationArguments( int initialCapacity) { appArguments = new ArrayList(initialCapacity); return; } /** * Constructor to create an ApplicationArguments class with * application name and initial capacity. * * @param appName the application name for the usage String * * @param initialCapacity the initial size of the array that holds the * commandline arguments added. */ public ApplicationArguments( String appName, int initialCapacity) { applicationName = appName; appArguments = new ArrayList(initialCapacity); return; } /** * Add an Argument to this application */ public boolean add( Argument argument) { return appArguments.add( argument); } /** * Returns true if the named argument option was specified on the * command line */ public boolean hasArgument( char argumentLetter) throws NoSuchFieldException { return getArgument(argumentLetter).getValueCount() > 0; } /** * Return the number of arguments registered for this applicaton. */ public int getNumberOfArguments() { return appArguments.size(); } /** * Return the number of arguments registered for this applicaton. */ public int getNumberOfPositionalArguments() { int count = 0; Argument arg = null; for( int i = 0; i < appArguments.size(); i++) { arg = (Argument)appArguments.get(i); if( arg.isPositional()) { count += arg.getValueCount();// Count number of values } } return count; } /** * Return the argument class representing this option letter * * @param optionLetter the option letter * * @return the Argument class for the specified option letter */ public Argument getArgument( char optionLetter) throws NoSuchFieldException { Argument arg = null; for( int i = 0; i < appArguments.size(); i++) { arg = (Argument)appArguments.get(i); if( arg.getArgumentLetter() == optionLetter) { break; } arg = null; } if( arg == null) { throw new NoSuchFieldException("Argument \"" + optionLetter + "\" is not defined in this application"); } return arg; } /** * Return the argument class for the Nth positional argument * where getArgument(1) will return the first one. * * @param position the positional argument number * * @return the Argument class representing the positional argument */ public Argument getArgument( int position) throws NoSuchFieldException { int count = 1; Argument arg = null; for( int i = 0; i < appArguments.size(); i++) { arg = (Argument)appArguments.get(i); if( arg.isPositional()) { if( count == position) { break; } count++; } arg = null; } if( arg == null) { throw new NoSuchFieldException("Argument at position " + position + " is not defined in this application"); } return arg; } /** * returns a String with the default value for the given Argument class, * formatted for the usage string. * * @param arg the Argument class */ private String defString( Argument arg) { if( arg.isRequired()) { return " - Required Argument"; } else if( arg.isString() ) { return " - Default=" + "\"" + (String)arg.getDefaultValue() + "\""; } else if( arg.isInteger() ) { return " - Default=" + ((Integer)arg.getDefaultValue()).intValue(); } else { return " - Default=" + ((Boolean)arg.getDefaultValue()).booleanValue(); } } /** * Returns the usage string including the error message supplied. */ public String usage( String message) { String option; Argument arg = null; String usageString = ""; // Print error string if( message != null) { usageString = usageString + "Error: " + message + "\n\n"; } // Display usage line usageString = usageString + "Usage: " + applicationName; // usage line: Display any required boolean options String dash = " -"; for( int i = 0; i < appArguments.size(); i++) { arg = (Argument)appArguments.get(i); if( arg.isBoolean() && arg.isRequired()) { usageString = usageString + dash + arg.getArgumentLetter(); dash = ""; } } if( dash == "") { usageString = usageString + " "; } // usage line: Display any optional boolean options dash = " [ -"; for( int i = 0; i < appArguments.size(); i++) { arg = (Argument)appArguments.get(i); if( arg.isBoolean() && ! arg.isRequired()) { usageString = usageString + dash + arg.getArgumentLetter(); dash = ""; } } if( dash == "") { usageString = usageString + " ] "; } // usage line: Display the rest of the non positional arguments for( int i = 0; i < appArguments.size(); i++) { arg = (Argument)appArguments.get(i); if( ! arg.isBoolean() && ! arg.isPositional()) { if( ! arg.isRequired()) { usageString = usageString + "[ "; } usageString = usageString + "-" + arg.getArgumentLetter(); usageString = usageString + " <" + arg.getName() + ">"; if( ! arg.isRequired()) { usageString = usageString + " ] "; } else { usageString = usageString + " "; } } } // usage line: Display the positional arguments String endPositional = ""; for( int i = 0; i < appArguments.size(); i++) { arg = (Argument)appArguments.get(i); if( arg.isPositional()) { if( ! arg.isRequired()) { usageString = usageString + " [ "; } usageString = usageString + " <" + arg.getName() + ">"; if( ! arg.isRequired()) { endPositional = endPositional + " ]"; } else { endPositional = endPositional + " "; } } } usageString = usageString + endPositional; // Now display a line for each argument with its meaning usageString = usageString + "\n"; for( int i = 0; i < appArguments.size(); i++) { arg = (Argument)appArguments.get(i); if( arg.isPositional()) { if( arg.getName().length() < 7) { option = "<" + arg.getName() + ">" + " ".substring(1,9-arg.getName().length()); } else { option = "<" + arg.getName() + "> "; } } else { option = " -" + arg.getArgumentLetter() + " "; } usageString = usageString + "\n" + option + arg.getDescription(); usageString = usageString + defString(arg); } return usageString; } /** * Parse the specified arguments using the arguments added * to this class to determine the parsing rules. * * @param arguments an array containing the command arguments, one * token per String in the array. */ public void parse(String[] arguments) throws ParseException { int position = 0; // Positional argument number - 1 is the first boolean typeBool = false; // true option is boolean, otherwise false char letter; // The option letter being processed String value; // Value of the argument Argument arg; // Argument class representing option int idx = 0; // Position of index in argument for( int i = 0; i < arguments.length; i++) { idx = 0; value = arguments[i]; // - options must be at least two chars in length // - if only '-' is specified, just send it through as positional if((value.length() > 1) && (value.charAt(0) == '-')) { idx = 1; // Found an option - begins with dash for( ; idx < value.length(); idx++) { letter = value.charAt(idx); try { arg = getArgument(letter); } catch( NoSuchFieldException e) { throw new ParseException("Argument " + (i+1) + ": '-" + letter + "' is an unknown option", i+1); } if( arg.isBoolean()) { if( idx == 1) { arg.add( new Boolean( true), i); typeBool = true;// processing boolean arguments continue; } else { if( typeBool) { arg.add( new Boolean( true),i); continue; } else { // Allow non boolean if last arg in the string if( idx != (value.length() -1)) { throw new ParseException("Argument " + (i+1) + ": '-" + letter + "' must be " + "specified separately", i+1); } } } } // Don't do else, we process fall through from the above if( ! arg.isBoolean()) { // if option at the end of the string, value is next arg if( idx == (value.length() - 1)) { // Check if any arguments left if( (i+1) == arguments.length) { throw new ParseException("Argument " + (i+1) + ": '-" + letter + "' must have " + "a value", i+1); } value = arguments[++i]; } else { // Value is the rest of the string value = arguments[i].substring( ++idx); } if( arg.isString()) { arg.add(value, i); } else { // it is an integer try { arg.add( Integer.decode(value), i); } catch( NumberFormatException e) { throw new ParseException("Argument " + (i+1) + ": '-" + letter + "' does not have" + "a valid integer value", i+1); } } break; } } } else { // Process positional argument position += 1; try { arg = getArgument(position); } catch( NoSuchFieldException e) { throw new ParseException("Argument " + (i+1) + " Unknown positional argument \"" + value + "\"", i+1); } arg.add( value, i); // If not multivalued, we are done if( ! arg.isMultivalued()) { continue; } // Multivalued, read rest of values while( true) { // Multi-valued argument // stop if this is the last argument, i.e. no next argument if( (i+1) == arguments.length) { break; } // if next arg is an option, break // An option must be at least two characters if((arguments[i+1].length() > 1) && (arguments[i+1].charAt(0) == '-')) { // Go back to outer loop to process option break; } arg.add( arguments[++i], i); } } } // Verify that all required arguments have a value for( int i = 0; i < appArguments.size(); i++) { int count = 0; arg = (Argument)appArguments.get(i); // Count positional arguments if( arg.isPositional()) { count++; } if( arg.isRequired() && (arg.getValueCount() == 0)) { if( arg.isPositional()) { throw new ParseException("Positional argument " + count + " (" + arg.getDescription() + ") not specified", i+1); } else { throw new ParseException("Required argument '-" + arg.getArgumentLetter() + "' not specified", i+1); } } } return; } }