/* *****************************************************************************

 * $Header: /cm/ldap/src/javaldap/samples/arguments/Argument.java,v 1.4 2003/01/28 16:52:10 vtag Exp $

 * 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.util.Enumeration;

import java.util.NoSuchElementException;

import java.text.ParseException;



/**

 * The argument class represents a single command line argument.

 * The actual type of argument represented depends on the

 * constructor used when instantiating the class.  The following

 * variations are allowed for kinds of Arguments.

 * <p>

 * Argument values are type Boolean, String, or Integer.

 * <p>

 * Argument values are multi-valued or single-valued.  A single-valued

 * argument is limited to a single value, whereas a multi-valued 

 * argument may have many values.

 * <p>

 * Arguments values are determined an option letter or a by position

 * on the command line.

 *

 * An option letter is represented on the command line by a dash followed

 * by an option letter, i.e. -a is the option a.  Integer and

 * String option arguments have a value associated with the option,

 * that follows the option, i.e. for the String option -s sub,

 * sub is the String value for option s.

 * Option letters are case sensitive, i.e. -N is not the same as -n.

 *<p>

 * A positional parameter has no option letter associated with it.

 * Its meaning depends on its position in relation to other positional

 * parameters.  For instance, a.txt, b.txt, c.txt could be a multivalued

 * positional parameter representing filenames.  The parameters "user"

 * "password" might represent a user-name and a password used for

 * authentication.

 * Positional parameters are either of type String or Integer.

 *<p>

 * Boolean arguments are represented by a single option letter.

 * The presence of the option generates a value opposite to the

 * the default, i.e. if the default is 'false' for option 'b', then

 * the value for a '-b' on the command line is 'true'  Boolean

 * options may be strung together, i.e. -abc is the same as -a -b -c

 * <p>

 * Integer and string arguments must have a value following

 * the option letter. Since Integer or String options are

 * not allowed to be strung together, the value may follow immediately

 * after the option or may be separated by a space, i.e. -d stop or -dstop.

 * If the option is multivalued, then each additional value is also

 * preceded by the option letter, i.e for -a dog -a cat -a pig,

 * dog, cat, & pig are values for the 'a' option.

 * 

 * Positional parameters are order dependent, i.e. the order on the command

 * line determines which parameter is associated with the value.

 */

public class Argument

{

    private char argumentLetter;

    private Object defaultValue;

    private String description;

    private String name;

    private boolean required;

    private boolean multiValued;

    /*protected*/ ArrayList argValues = new ArrayList();



    /**

     * Specified whether the argument is required or optional

     */    final private static boolean REQUIRED = true;

    final private static boolean OPTIONAL = false;

    /**

     * Specified on required positional parameters to

     * indicate whether they are of type Integer or String

     */

    final public static boolean REQUIRED_INTEGER = true;

    final public static boolean REQUIRED_STRING = false;

    /**

     * Specified on Integer or String values to indicate

     * whether they are single or multi-valued.

     */

    final public static boolean MULTI_VALUED = true;

    final public static boolean SINGLE_VALUED = false;



    /**

     * Creates a Boolean argument

     *

     * @param argumentLetter a single character representing the argument letter

     *                       used on the command line.

     * @param description    A string specifying the complete meaning of the

     *                       argument with possible values.  It may contain

     *                       multiple lines.  The description of the default

     *                       value is be appended to this description when

     *                       the usage string is formed.

     * @param defaultValue   The value to use if no argument on the command

     *                       line is specified.

     */

    public

    Argument(  char argumentLetter,

               String description,

               boolean defaultValue)

    {

        setParams( argumentLetter, "", description, OPTIONAL, SINGLE_VALUED);

        this.defaultValue = new Boolean(defaultValue);

        return;

    }

    

    /**

     * Creates required positional argument

     *

     * @param type           determines the the type of this required

     *                       argument #REQUIRED_INTEGER or #REQUIRED_STRING

     * @param name           A one-word argument name.  It is displayed on

     *                       the command line usage.

     *                       line description, to included in the usage string.

     * @param description    A string specifying the complete meaning of the

     *                       argument with possible values.  It may contain

     *                       multiple lines.  The description of the default

     *                       value is be appended to this description when

     *                       the usage string is formed.

     * @param multiValued    Determines if this Argument is to be multi-valued

     *                       or single valued, #MULTI_VALUED or #SINGLE_VALUED. 

     *

     * @see #MULTI_VALUED

     * @see #SINGLE_VALUED

     */

    public

    Argument(  boolean type,

               String name,

               String description,

               boolean multiValued)

    {

        if( type == REQUIRED_INTEGER) {

            this.defaultValue = new Integer(0);

        } else {

            this.defaultValue = ""; // Empty String

        }

        setParams( '\0', name, description, REQUIRED, multiValued);

        return;

    }

    

    

    /**

     * Creates required argument specified by an option letter

     *

     * @param type           determines the the type of this required

     *                       argument #REQUIRED_INTEGER or #REQUIRED_STRING

     * @param argumentLetter a single character representing the argument letter

     *                       used on the command line.

     * @param name           A one-word argument name.  It is displayed on

     *                       the command line usage.

     *                       line description, to included in the usage string.

     * @param description    A string specifying the complete meaning of the

     *                       argument with possible values.  It may contain

     *                       multiple lines.  The description of the default

     *                       value is be appended to this description when

     *                       the usage string is formed.

     * @param multiValued    Determines if this Argument is to be MULTI_VALUED

     *                       or SINGLE_VALUED.

     *

     * @see #MULTI_VALUED

     * @see #SINGLE_VALUED

     * @see #REQUIRED_INTEGER

     * @see #REQUIRED_STRING

     */

    public

    Argument(  boolean type,

               char argumentLetter,

               String name,

               String description,

               boolean multiValued)

    {

        if( type == REQUIRED_INTEGER) {

            this.defaultValue = new Integer(0);

        } else {

            this.defaultValue = ""; // Empty String

        }

        setParams( argumentLetter, name, description, REQUIRED, multiValued);

        return;

    }

    

    /**

     * Creates optional String positional argument

     *

     * @param name           A one-word argument name.  It is displayed on

     *                       the command line usage.

     *                       line description, to included in the usage string.

     * @param description    A string specifying the complete meaning of the

     *                       argument with possible values.  It may contain

     *                       multiple lines.  The description of the default

     *                       value is be appended to this description when

     *                       the usage string is formed.

     * @param defaultValue   The value to use if no argument on the command

     *                       line is specified. The type of the parameter

     *                       determines the type of argument value, i.e.

     *                       String, or Integer.

     * @param multiValued    Determines if this Argument is to be multi-valued

     *                       or single valued, #MULTI_VALUED or #SINGLE_VALUED. 

     *

     * @see #MULTI_VALUED

     * @see #SINGLE_VALUED

     */

    public

    Argument(  String name,

               String description,

               String defaultValue,

               boolean multiValued)

    {

        setParams( '\0', name, description, OPTIONAL, multiValued);

        if( defaultValue == null) {

            defaultValue = "";

        }

        this.defaultValue = defaultValue;

        return;

    }



    /**

     * Creates optional Integer positional argument

     *

     * @param name           A one-word argument name.  It is displayed on

     *                       the command line usage.

     *                       line description, to included in the usage string.

     * @param description    A string specifying the complete meaning of the

     *                       argument with possible values.  It may contain

     *                       multiple lines.  The description of the default

     *                       value is be appended to this description when

     *                       the usage string is formed.

     * @param defaultValue   The value to use if no argument on the command

     *                       line is specified. The type of the parameter

     *                       determines the type of argument value, i.e.

     *                       String, or Integer.

     * @param multiValued    Determines if this Argument is to be multi-valued

     *                       or single valued, #MULTI_VALUED or #SINGLE_VALUED. 

     *

     * @see #MULTI_VALUED

     * @see #SINGLE_VALUED

     */

    public

    Argument(  String name,

               String description,

               int    defaultValue,

               boolean multiValued)

    {

        setParams( '\0', name, description, OPTIONAL, multiValued);

        this.defaultValue = new Integer(defaultValue);

        return;

    }



    /**

     * Creates a optional String argument

     *

     * @param argumentLetter a single character representing the argument letter

     *                       used on the command line.

     * @param name           A one-word argument name.  It is displayed on

     *                       the command line usage.

     *                       line description, to included in the usage string.

     * @param description    A string specifying the complete meaning of the

     *                       argument with possible values.  It may contain

     *                       multiple lines.  The description of the default

     *                       value is be appended to this description when

     *                       the usage string is formed.

     * @param defaultValue   The value to use if no argument on the command

     *                       line is specified. The type of the parameter

     *                       determines the type of argument value, i.e.

     *                       String, or Integer.

     * @param multiValued    Determines if this Argument is to be multi-valued

     *                       or single valued, #MULTI_VALUED or #SINGLE_VALUED. 

     *

     * @see #MULTI_VALUED

     * @see #SINGLE_VALUED

     */

    public

    Argument(  char argumentLetter,

               String name,

               String description,

               String defaultValue,

               boolean multiValued)

    {

        setParams( argumentLetter, name, description, OPTIONAL, multiValued);

        if( defaultValue == null) {

            defaultValue = "";      // Make sure we have a String type here

        }

        this.defaultValue = defaultValue;

        return;

    }



    /**

     * Creates an optional Integer argument

     *

     * @param argumentLetter a single character representing the argument letter

     *                       used on the command line.

     * @param name           A one-word argument name.  It is displayed on

     *                       the command line usage.

     *                       line description, to included in the usage string.

     * @param description    A string specifying the complete meaning of the

     *                       argument with possible values.  It may contain

     *                       multiple lines.  The description of the default

     *                       value is be appended to this description when

     *                       the usage string is formed.

     * @param defaultValue   The value to use if no argument on the command

     *                       line is specified. The type of the parameter

     *                       determines the type of argument value, i.e.

     *                       String, or Integer.

     * @param multiValued    Determines if this Argument is to be multi-valued

     *                       or single valued, #MULTI_VALUED or #SINGLE_VALUED. 

     *

     * @see #MULTI_VALUED

     * @see #SINGLE_VALUED

     */

    public

    Argument(  char argumentLetter,

               String name,

               String description,

               int defaultValue,

               boolean multiValued)

    {

        setParams( argumentLetter, name, description, OPTIONAL, multiValued);

        this.defaultValue = new Integer(defaultValue);

        return;

    }



    /**

     * Set parameters common to all constructors

     *

     * @param argumentLetter a single character representing the argument letter

     *                       used on the command line.

     * @param name           A one-word argument name.  It is displayed on

     *                       the command line usage.

     *                       line description, to included in the usage string.

     * @param description    A string specifying the complete meaning of the

     *                       argument with possible values.  It may contain

     *                       multiple lines.  The description of the default

     *                       value is be appended to this description when

     *                       the usage string is formed.

     * @param required       Determines if this Argument is to be REQUIRED

     *                       or OPTIONAL.  REQUIRED Arguments must be

     *                       present on the command line.

     * @param multiValued    Determines if this Argument is to be multi-valued

     *                       or single valued, #MULTI_VALUED or #SINGLE_VALUED. 

     *

     * @see #MULTI_VALUED

     * @see #SINGLE_VALUED

     * @see #REQUIRED_INTEGER

     * @see #REQUIRED_STRING

     */

    private

    void setParams( char argumentLetter,

                    String name,

                    String description,

                    boolean required,

                    boolean multiValued)

    {

        this.argumentLetter = argumentLetter;

        this.name = name;

        this.description = description;

        this.required = required; 

        this.multiValued = multiValued;

        return;

    }



    /**

     * Returns the number of values specified for this argument on the

     * command line.

     */

    final public int getValueCount()

    {

        return argValues.size();

    }



    /**

     * Add a value to this arguments values.  The value must be compatible

     * with the default value specified on the constructor.

     *

     * @param value option value to add

     *

     * @param position parameter position, used for error message

     *

     * @return true

     */

    final public boolean add( Object value, int position)

            throws ParseException

    {

        if( (argValues.size() == 1)  &&  ! multiValued) {

            throw new ParseException("Parameter " + position +

               ": '-" + argumentLetter +

               "' cannot have more than one value", position);

        }

        argValues.add(value);

        return true;



    }



    /**

     * Returns the option letter associated with this argument

     */

    final public char getArgumentLetter()

    {

        return argumentLetter;

    }



    /**

     * Returns the name of this argument

     */

    final public String getName()

    {

        return name;

    }



    /**

     * Returns the description of this argument

     */

    final public String getDescription()

    {

        return description;

    }



    /**

     * Returns true if this argument is required to be on the command line

     */

    final public boolean isRequired()

    {

        return required;

    }



    /**

     * Returns true if this argument is of type Boolean

     */

    final public boolean isBoolean()

    {

        return defaultValue instanceof Boolean;

    }

    

    /**

     * Returns true if this argument is of type Integer

     */

    final public boolean isInteger()

    {

        return defaultValue instanceof Integer;

    }

    

    /**

     * Returns true if this argument is of type String

     */

    final public boolean isString()

    {

        return defaultValue instanceof String;

    }

    

    /**

     * Returns true if this argument is a positional parameter

     */

    final public boolean isPositional()

    {

        return argumentLetter == '\0';

    }

    

    /**

     * Returns true if this argument can have more than one value

     */

    final public boolean isMultivalued()

    {

        return multiValued;

    }



    /**

     * Returns the default value for this argument.  The type of the default

     * object will be Boolean, Integer, or String.

     */

    final public Object getDefaultValue()

    {

        return defaultValue;

    }

    

    /**

     * Returns the first value if the argument, or if the argument is single-

     * valued, the only argument value.

     */

    final public Object getValue()

    {

        if( argValues.size() == 0) {

            return defaultValue;

        }

        return argValues.get(0);

    }

    

    /**

     * Returns an enumeration containing all the values of the argument

     * in the order specified on the command line.  If no values were

     * specified, it returns an enumeration containing the default value.

     */

    final public Enumeration getValues()

    {

        if( argValues.size() == 0) {

            if( isBoolean()) {

                return new ArgEnumeration(

                        new Boolean[] { (Boolean)defaultValue });

            } else

            if( isInteger()) {

                return new ArgEnumeration(

                        new Integer[] { (Integer)defaultValue });

            } else

            if( isString()) {

                return new ArgEnumeration(

                        new String[] { (String)defaultValue });

            }

        }

        return new ArgEnumeration( argValues.toArray());

    }

    

    /**

     * Clears all the values of this argument.

     */

    final public void clearValues()

    {

        argValues = new ArrayList();

        return;

    }

    

    /**

     * A class to return the elements of an array as an Enumeration

     */

    public class ArgEnumeration implements Enumeration

    {

        private Object[] eArray; 

        private int index = 0;

        /**

         * Constructor to create the Enumeration

         *

         * @param eArray the array to use for the Enumeration

         */

        public ArgEnumeration( Object[] eArray)

        {

            this.eArray = eArray;

            return;

        }



        public boolean hasMoreElements()

        {

            return (index < eArray.length);

        }



        public Object nextElement() throws NoSuchElementException

        {

            if( index >= eArray.length) {

               throw new NoSuchElementException("No more elements for argument " +

                    argumentLetter);

            }

            return eArray[index++];

        }

    }

}

