package com.netscape.pkisilent.argparser;

// --- BEGIN COPYRIGHT BLOCK ---
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; version 2 of the License.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//
// (C) 2007 Red Hat, Inc.
// All rights reserved.
// --- END COPYRIGHT BLOCK ---

/**
 * Copyright John E. Lloyd, 2004. All rights reserved. Permission to use,
 * copy, modify and redistribute is granted, provided that this copyright
 * notice is retained and the author is given credit whenever appropriate.
 *
 * This  software is distributed "as is", without any warranty, including
 * any implied warranty of merchantability or fitness for a particular
 * use. The author assumes no responsibility for, and shall not be liable
 * for, any special, indirect, or consequential damages, or any damages
 * whatsoever, arising out of or in connection with the use of this
 * software.
 */

import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.LineNumberReader;
import java.io.PrintStream;
import java.io.Reader;
import java.lang.reflect.Array;
import java.util.Vector;

/**
 * ArgParser is used to parse the command line arguments for a java
 * application program. It provides a compact way to specify options and match
 * them against command line arguments, with support for
 * <a href=#rangespec>range checking</a>,
 * <a href=#multipleOptionNames>multiple option names</a> (aliases),
 * <a href=#singleWordOptions>single word options</a>,
 * <a href=#multipleOptionValues>multiple values associated with an option</a>,
 * <a href=#multipleOptionInvocation>multiple option invocation</a>,
 * <a href=#helpInfo>generating help information</a>,
 * <a href=#customArgParsing>custom argument parsing</a>, and
 * <a href=#argsFromAFile>reading arguments from a file</a>. The
 * last feature is particularly useful and makes it
 * easy to create ad-hoc configuration files for an application.
 *
 * <h3><a name="example">Basic Example</a></h3>
 *
 * <p>
 * Here is a simple example in which an application has three command line options: <code>-theta</code> (followed by a
 * floating point value), <code>-file</code> (followed by a string value), and <code>-debug</code>, which causes a
 * boolean value to be set.
 *
 * <pre>
 *
 * static public void main(String[] args) {
 *     // create holder objects for storing results ...
 *
 *     DoubleHolder theta = new DoubleHolder();
 *     StringHolder fileName = new StringHolder();
 *     BooleanHolder debug = new BooleanHolder();
 *
 *     // create the parser and specify the allowed options ...
 *
 *     ArgParser parser = new ArgParser(&quot;java argparser.SimpleExample&quot;);
 *     parser.addOption(&quot;-theta %f #theta value (in degrees)&quot;, theta);
 *     parser.addOption(&quot;-file %s #name of the operating file&quot;, fileName);
 *     parser.addOption(&quot;-debug %v #enables display of debugging info&quot;, debug);
 *
 *     // match the arguments ...
 *
 *     parser.matchAllArgs(args);
 *
 *     // and print out the values
 *
 *     System.out.println(&quot;theta=&quot; + theta.value);
 *     System.out.println(&quot;fileName=&quot; + fileName.value);
 *     System.out.println(&quot;debug=&quot; + debug.value);
 * }
 * </pre>
 * <p>
 * A command line specifying all three options might look like this:
 *
 * <pre>
 * java argparser.SimpleExample -theta 7.8 -debug -file /ai/lloyd/bar
 * </pre>
 *
 * <p>
 * The application creates an instance of ArgParser and then adds descriptions of the allowed options using
 * {@link #addOption addOption}. The method {@link #matchAllArgs(String[]) matchAllArgs} is then used to match these
 * options against the command line arguments. Values associated with each option are returned in the <code>value</code>
 * field of special ``holder'' classes (e.g., {@link argparser.DoubleHolder DoubleHolder},
 * {@link argparser.StringHolder StringHolder}, etc.).
 *
 * <p>
 * The first argument to {@link #addOption addOption} is a string that specifies (1) the option's name, (2) a conversion
 * code for its associated value (e.g., <code>%f</code> for floating point, <code>%s</code> for a string,
 * <code>%v</code> for a boolean flag), and (3) an optional description (following the <code>#</code> character) which
 * is used for generating help messages. The second argument is the holder object through which the value is returned.
 * This may be either a type-specific object (such as {@link argparser.DoubleHolder DoubleHolder} or
 * {@link argparser.StringHolder
 * StringHolder}), an array of the appropriate type, or <a href=#multipleOptionInvocation> an instance of
 * <code>java.util.Vector</code></a>.
 *
 * <p>
 * By default, arguments that don't match the specified options, are <a href=#rangespec>out of range</a>, or are
 * otherwise formatted incorrectly, will cause <code>matchAllArgs</code> to print a message and exit the program.
 * Alternatively, an application can use {@link #matchAllArgs(String[],int,int) matchAllArgs(args,idx,exitFlags)} to
 * obtain an array of unmatched arguments which can then be <a href=#customArgParsing>processed separately</a>
 *
 * <h3><a name="rangespec">Range Specification</a></h3>
 *
 * The values associated with options can also be given range specifications. A range specification appears in curly
 * braces immediately following the conversion code. In the code fragment below, we show how to specify an option
 * <code>-name</code> that expects to be provided with one of three string values (<code>john</code>, <code>mary</code>,
 * or <code>jane</code>), an option <code>-index</code> that expects to be supplied with a integer value in the range 1
 * to 256, an option <code>-size</code> that expects to be supplied with integer values of either 1, 2, 4, 8, or 16, and
 * an option <code>-foo</code> that expects to be supplied with floating point values in the ranges -99 < foo <= -50, or
 * 50 <= foo < 99.
 *
 * <pre>
 * StringHolder name = new StringHolder();
 * IntHolder index = new IntHolder();
 * IntHolder size = new IntHolder();
 * DoubleHolder foo = new DoubleHolder();
 *
 * parser.addOption(&quot;-name %s {john,mary,jane}&quot;, name);
 * parser.addOption(&quot;-index %d {[1,256]}&quot;, index);
 * parser.addOption(&quot;-size %d {1,2,4,8,16}&quot;, size);
 * parser.addOption(&quot;-foo %f {(-99,-50],[50,99)}&quot;, foo);
 * </pre>
 *
 * If an argument value does not lie within a specified range, an error is generated.
 *
 * <h3><a name="multipleOptionNames">Multiple Option Names</a></h3>
 *
 * An option may be given several names, or aliases, in the form of a comma seperated list:
 *
 * <pre>
 * parser.addOption(&quot;-v,--verbose %v #print lots of info&quot;);
 * parser.addOption(&quot;-of,-outfile,-outputFile %s #output file&quot;);
 * </pre>
 *
 * <h3><a name="singleWordOptions">Single Word Options</a></h3>
 *
 * Normally, options are assumed to be "multi-word", meaning that any associated value must follow the option as a
 * separate argument string. For example,
 *
 * <pre>
 * parser.addOption(&quot;-file %s #file name&quot;);
 * </pre>
 *
 * will cause the parser to look for two strings in the argument list of the form
 *
 * <pre>
 *    -file someFileName
 * </pre>
 *
 * However, if there is no white space separting the option's name from it's conversion code, then values associated
 * with that option will be assumed to be part of the same argument string as the option itself. For example,
 *
 * <pre>
 * parser.addOption(&quot;-file=%s #file name&quot;);
 * </pre>
 *
 * will cause the parser to look for a single string in the argument list of the form
 *
 * <pre>
 *    -file=someFileName
 * </pre>
 *
 * Such an option is called a "single word" option.
 *
 * <p>
 * In cases where an option has multiple names, then this single word behavior is invoked if there is no white space
 * between the last indicated name and the conversion code. However, previous names in the list will still be given
 * multi-word behavior if there is white space between the name and the following comma. For example,
 *
 * <pre>
 * parser.addOption(&quot;-nb=,-number ,-n%d #number of blocks&quot;);
 * </pre>
 *
 * will cause the parser to look for one, two, and one word constructions of the forms
 *
 * <pre>
 *    -nb=N
 *    -number N
 *    -nN
 * </pre>
 *
 * <h3><a name="multipleOptionValues">Multiple Option Values</a></h3>
 *
 * If may be useful for an option to be followed by several values. For instance, we might have an option
 * <code>-velocity</code> which should be followed by three numbers denoting the x, y, and z components of a velocity
 * vector. We can require multiple values for an option by placing a <i>multiplier</i> specification, of the form
 * <code>X</code>N, where N is an integer, after the conversion code (or range specification, if present). For example,
 *
 * <pre>
 * double[] pos = new double[3];
 *
 * addOption(&quot;-position %fX3 #position of the object&quot;, pos);
 * </pre>
 *
 * will cause the parser to look for
 *
 * <pre>
 *    -position xx yy zz
 * </pre>
 *
 * in the argument list, where <code>xx</code>, <code>yy</code>, and <code>zz</code> are numbers. The values are stored
 * in the array <code>pos</code>.
 *
 * Options requiring multiple values must use arrays to return their values, and cannot be used in single word format.
 *
 * <h3><a name="multipleOptionInvocation">Multiple Option Invocation</a></h3>
 *
 * Normally, if an option appears twice in the command list, the value associated with the second instance simply
 * overwrites the value associated with the first instance.
 *
 * However, the application can instead arrange for the storage of <i>all</i> values associated with multiple option
 * invocation, by supplying a instance of <code>java.util.Vector</code> to serve as the value holder. Then every time
 * the option appears in the argument list, the parser will create a value holder of appropriate type, set it to the
 * current value, and store the holder in the vector. For example, the construction
 *
 * <pre>
 * Vector vec = new Vector(10);
 *
 * parser.addOption(&quot;-foo %f&quot;, vec);
 * parser.matchAllArgs(args);
 * </pre>
 *
 * when supplied with an argument list that contains
 *
 * <pre>
 *    -foo 1.2 -foo 1000 -foo -78
 * </pre>
 *
 * will create three instances of {@link argparser.DoubleHolder DoubleHolder}, initialized to <code>1.2</code>,
 * <code>1000</code>, and <code>-78</code>, and store them in <code>vec</code>.
 *
 * <h3><a name="helpInfo">Generating help information</a></h3>
 *
 * ArgParser automatically generates help information for the options, and this information may be printed in response
 * to a <i>help</i> option, or may be queried by the application using {@link #getHelpMessage getHelpMessage}. The
 * information for each option consists of the option's name(s), it's required value(s), and an application-supplied
 * description. Value information is generated automaticlly from the conversion code, range, and multiplier
 * specifications (although this can be overriden, as <a href=#valueInfo>described below</a>). The application-supplied
 * description is whatever appears in the specification string after the optional <code>#</code> character. The string
 * returned by {@link #getHelpMessage getHelpMessage} for the <a href=#example>first example above</a> would be
 *
 * <pre>
 * Usage: java argparser.SimpleExample
 * Options include:
 *
 * -help,-?                displays help information
 * -theta &lt;float&gt;          theta value (in degrees)
 * -file &lt;string&gt;          name of the operating file
 * -debug                  enables display of debugging info
 * </pre>
 *
 * The options <code>-help</code> and <code>-?</code> are including in the parser by default as help options, and they
 * automatically cause the help message to be printed. To exclude these options, one should use the constructor
 * {@link #ArgParser(String,boolean)
 * ArgParser(synopsis,false)}. Help options can also be specified by the application using {@link #addOption addOption}
 * and the conversion code <code>%h</code>. Help options can be disabled using {@link #setHelpOptionsEnabled
 * setHelpOptionsEnabled(false)}.
 *
 * <p>
 * <a name=valueInfo> A description of the required values for an option can be specified explicitly by placing a second
 * <code>#</code> character in the specification string. Everything between the first and second <code>#</code>
 * characters then becomes the value description, and everything after the second <code>#</code> character becomes the
 * option description. For example, if the <code>-theta</code> option above was specified with
 *
 * <pre>
 * parser.addOption(&quot;-theta %f #NUMBER#theta value (in degrees)&quot;, theta);
 * </pre>
 *
 * instead of
 *
 * <pre>
 * parser.addOption(&quot;-theta %f #theta value (in degrees)&quot;, theta);
 * </pre>
 *
 * then the corresponding entry in the help message would look like
 *
 * <pre>
 * -theta NUMBER          theta value (in degrees)
 * </pre>
 *
 * <h3><a name="customArgParsing">Custom Argument Parsing</a></h3>
 *
 * An application may find it necessary to handle arguments that don't fit into the framework of this class. There are a
 * couple of ways to do this.
 *
 * <p>
 * First, the method {@link #matchAllArgs(String[],int,int)
 * matchAllArgs(args,idx,exitFlags)} returns an array of all unmatched arguments, which can then be handled specially:
 *
 * <pre>
 *    String[] unmatched =
 *       parser.matchAllArgs (args, 0, parser.EXIT_ON_ERROR);
 *    for (int i = 0; i < unmatched.length; i++)
 *     { ... handle unmatched arguments ...
 *     }
 * </pre>
 *
 * For instance, this would be useful for an applicatoon that accepts an arbitrary number of input file names. The
 * options can be parsed using <code>matchAllArgs</code>, and the remaining unmatched arguments give the file names.
 *
 * <p>
 * If we need more control over the parsing, we can parse arguments one at a time using {@link #matchArg matchArg}:
 *
 * <pre>
 *    int idx = 0;
 *    while (idx < args.length)
 *     { try
 *        { idx = parser.matchArg (args, idx);
 *          if (parser.getUnmatchedArgument() != null)
 *           {
 *             ... handle this unmatched argument ourselves ...
 *           }
 *        }
 *       catch (ArgParserException e)
 *        { // malformed or erroneous argument
 *          parser.printErrorAndExit (e.getMessage());
 *        }
 *     }
 * </pre>
 *
 * {@link #matchArg matchArg(args,idx)} matches one option at location <code>idx</code> in the argument list, and then
 * returns the location value that should be used for the next match. If an argument does not match any option,
 * {@link #getUnmatchedArgument getUnmatchedArgument} will return a copy of the unmatched argument.
 *
 * <h3><a name="argsFromAFile">Reading Arguments From a File</a></h3>
 *
 * The method {@link #prependArgs prependArgs} can be used to automatically read in a set of arguments from a file and
 * prepend them onto an existing argument list. Argument words correspond to white-space-delimited strings, and the file
 * may contain the comment character <code>#</code> (which comments out everything to the end of the current line). A
 * typical usage looks like this:
 *
 * <pre>
 *    ... create parser and add options ...
 *
 *    args = parser.prependArgs (new File(".configFile"), args);
 *
 *    parser.matchAllArgs (args);
 * </pre>
 *
 * This makes it easy to generate simple configuration files for an application.
 *
 * @author John E. Lloyd, Fall 2004
 */
public class ArgParser {
    Vector<Record> matchList;
    //	int tabSpacing = 8;
    String synopsisString;
    boolean helpOptionsEnabled = true;
    Record defaultHelpOption = null;
    Record firstHelpOption = null;
    PrintStream printStream = System.out;
    int helpIndent = 24;
    String errMsg = null;
    String unmatchedArg = null;

    static String validConversionCodes = "iodxcbfsvh";

    /**
     * Indicates that the program should exit with an appropriate message
     * in the event of an erroneous or malformed argument.
     */
    public static int EXIT_ON_ERROR = 1;

    /**
     * Indicates that the program should exit with an appropriate message
     * in the event of an unmatched argument.
     */
    public static int EXIT_ON_UNMATCHED = 2;

    /**
     * Returns a string containing the valid conversion codes. These
     * are the characters which may follow the <code>%</code> character in
     * the specification string of {@link #addOption addOption}.
     *
     * @return Valid conversion codes
     * @see #addOption
     */
    public static String getValidConversionCodes() {
        return validConversionCodes;
    }

    static class NameDesc {
        String name;
        // oneWord implies that any value associated with
        // option is concatenated onto the argument string itself
        boolean oneWord;
        NameDesc next = null;
    }

    static class RangePnt {
        double dval = 0;
        long lval = 0;
        String sval = null;
        boolean bval = true;
        boolean closed = true;

        RangePnt(String s, boolean closed) {
            sval = s;
            this.closed = closed;
        }

        RangePnt(double d, boolean closed) {
            dval = d;
            this.closed = closed;
        }

        RangePnt(long l, boolean closed) {
            lval = l;
            this.closed = closed;
        }

        RangePnt(boolean b, boolean closed) {
            bval = b;
            this.closed = closed;
        }

        RangePnt(StringScanner scanner, int type)
                throws IllegalArgumentException {
            String typeName = null;
            try {
                switch (type) {
                case Record.CHAR: {
                    typeName = "character";
                    lval = scanner.scanChar();
                    break;
                }
                case Record.INT:
                case Record.LONG: {
                    typeName = "integer";
                    lval = scanner.scanInt();
                    break;
                }
                case Record.FLOAT:
                case Record.DOUBLE: {
                    typeName = "float";
                    dval = scanner.scanDouble();
                    break;
                }
                case Record.STRING: {
                    typeName = "string";
                    sval = scanner.scanString();
                    break;
                }
                case Record.BOOLEAN: {
                    typeName = "boolean";
                    bval = scanner.scanBoolean();
                    break;
                }
                }
            } catch (StringScanException e) {
                throw new IllegalArgumentException(
                        "Malformed " + typeName + " '" +
                                scanner.substring(scanner.getIndex(),
                                        e.getFailIndex() + 1) +
                                "' in range spec");
            }
            //	      this.closed = closed;
        }

        void setClosed(boolean closed) {
            this.closed = closed;
        }

        boolean getClosed() {
            return closed;
        }

        int compareTo(double d) {
            if (dval < d) {
                return -1;
            } else if (d == dval) {
                return 0;
            } else {
                return 1;
            }
        }

        int compareTo(long l) {
            if (lval < l) {
                return -1;
            } else if (l == lval) {
                return 0;
            } else {
                return 1;
            }
        }

        int compareTo(String s) {
            return sval.compareTo(s);
        }

        int compareTo(boolean b) {
            if (b == bval) {
                return 0;
            } else {
                return 1;
            }
        }

        public String toString() {
            return "{ dval=" + dval + ", lval=" + lval +
                    ", sval=" + sval + ", bval=" + bval +
                    ", closed=" + closed + "}";
        }
    }

    class RangeAtom {
        RangePnt low = null;
        RangePnt high = null;
        RangeAtom next = null;

        RangeAtom(RangePnt p0, RangePnt p1, int type)
                throws IllegalArgumentException {
            int cmp = 0;
            switch (type) {
            case Record.CHAR:
            case Record.INT:
            case Record.LONG: {
                cmp = p0.compareTo(p1.lval);
                break;
            }
            case Record.FLOAT:
            case Record.DOUBLE: {
                cmp = p0.compareTo(p1.dval);
                break;
            }
            case Record.STRING: {
                cmp = p0.compareTo(p1.sval);
                break;
            }
            }
            if (cmp > 0) { // then switch high and low
                low = p1;
                high = p0;
            } else {
                low = p0;
                high = p1;
            }
        }

        RangeAtom(RangePnt p0)
                throws IllegalArgumentException {
            low = p0;
        }

        boolean match(double d) {
            int lc = low.compareTo(d);
            if (high != null) {
                int hc = high.compareTo(d);
                return (lc * hc < 0 ||
                        (low.closed && lc == 0) || (high.closed && hc == 0));
            } else {
                return lc == 0;
            }
        }

        boolean match(long l) {
            int lc = low.compareTo(l);
            if (high != null) {
                int hc = high.compareTo(l);
                return (lc * hc < 0 ||
                        (low.closed && lc == 0) || (high.closed && hc == 0));
            } else {
                return lc == 0;
            }
        }

        boolean match(String s) {
            int lc = low.compareTo(s);
            if (high != null) {
                int hc = high.compareTo(s);
                return (lc * hc < 0 ||
                        (low.closed && lc == 0) || (high.closed && hc == 0));
            } else {
                return lc == 0;
            }
        }

        boolean match(boolean b) {
            return low.compareTo(b) == 0;
        }

        public String toString() {
            return "low=" + (low == null ? "null" : low.toString()) +
                    ", high=" + (high == null ? "null" : high.toString());
        }
    }

    class Record {
        NameDesc nameList;
        static final int NOTYPE = 0;
        static final int BOOLEAN = 1;
        static final int CHAR = 2;
        static final int INT = 3;
        static final int LONG = 4;
        static final int FLOAT = 5;
        static final int DOUBLE = 6;
        static final int STRING = 7;
        int type;
        int numValues;
        boolean vectorResult = false;
        boolean required = true;

        String helpMsg = null;
        String valueDesc = null;
        String rangeDesc = null;
        Object resHolder = null;
        RangeAtom rangeList = null;
        RangeAtom rangeTail = null;
        char convertCode;
        boolean vval = true; // default value for now

        NameDesc firstNameDesc() {
            return nameList;
        }

        RangeAtom firstRangeAtom() {
            return rangeList;
        }

        int numRangeAtoms() {
            int cnt = 0;
            for (RangeAtom ra = rangeList; ra != null; ra = ra.next) {
                cnt++;
            }
            return cnt;
        }

        void addRangeAtom(RangeAtom ra) {
            if (rangeList == null) {
                rangeList = ra;
            } else {
                rangeTail.next = ra;
            }
            rangeTail = ra;
        }

        boolean withinRange(double d) {
            if (rangeList == null) {
                return true;
            }
            for (RangeAtom ra = rangeList; ra != null; ra = ra.next) {
                if (ra.match(d)) {
                    return true;
                }
            }
            return false;
        }

        boolean withinRange(long l) {
            if (rangeList == null) {
                return true;
            }
            for (RangeAtom ra = rangeList; ra != null; ra = ra.next) {
                if (ra.match(l)) {
                    return true;
                }
            }
            return false;
        }

        boolean withinRange(String s) {
            if (rangeList == null) {
                return true;
            }
            for (RangeAtom ra = rangeList; ra != null; ra = ra.next) {
                if (ra.match(s)) {
                    return true;
                }
            }
            return false;
        }

        boolean withinRange(boolean b) {
            if (rangeList == null) {
                return true;
            }
            for (RangeAtom ra = rangeList; ra != null; ra = ra.next) {
                if (ra.match(b)) {
                    return true;
                }
            }
            return false;
        }

        String valTypeName() {
            switch (convertCode) {
            case 'i': {
                return ("integer");
            }
            case 'o': {
                return ("octal integer");
            }
            case 'd': {
                return ("decimal integer");
            }
            case 'x': {
                return ("hex integer");
            }
            case 'c': {
                return ("char");
            }
            case 'b': {
                return ("boolean");
            }
            case 'f': {
                return ("float");
            }
            case 's': {
                return ("string");
            }
            }
            return ("unknown");
        }

        void scanValue(Object result, String name, String s, int resultIdx)
                throws ArgParseException {
            double dval = 0;
            String sval = null;
            long lval = 0;
            boolean bval = false;

            if (s.length() == 0) {
                throw new ArgParseException(name, "requires a contiguous value");
            }
            StringScanner scanner = new StringScanner(s);
            try {
                switch (convertCode) {
                case 'i': {
                    lval = scanner.scanInt();
                    break;
                }
                case 'o': {
                    lval = scanner.scanInt(8, false);
                    break;
                }
                case 'd': {
                    lval = scanner.scanInt(10, false);
                    break;
                }
                case 'x': {
                    lval = scanner.scanInt(16, false);
                    break;
                }
                case 'c': {
                    lval = scanner.scanChar();
                    break;
                }
                case 'b': {
                    bval = scanner.scanBoolean();
                    break;
                }
                case 'f': {
                    dval = scanner.scanDouble();
                    break;
                }
                case 's': {
                    sval = scanner.getString();
                    break;
                }
                }
            } catch (StringScanException e) {
                throw new ArgParseException(
                        name, "malformed " + valTypeName() + " '" + s + "'");
            }
            scanner.skipWhiteSpace();
            if (!scanner.atEnd()) {
                throw new ArgParseException(
                        name, "malformed " + valTypeName() + " '" + s + "'");
            }
            boolean outOfRange = false;
            switch (type) {
            case CHAR:
            case INT:
            case LONG: {
                outOfRange = !withinRange(lval);
                break;
            }
            case FLOAT:
            case DOUBLE: {
                outOfRange = !withinRange(dval);
                break;
            }
            case STRING: {
                outOfRange = !withinRange(sval);
                break;
            }
            case BOOLEAN: {
                outOfRange = !withinRange(bval);
                break;
            }
            }
            if (outOfRange) {
                throw new ArgParseException(
                        name, "value '" + s + "' not in range " + rangeDesc);
            }
            if (result.getClass().isArray()) {
                switch (type) {
                case BOOLEAN: {
                    ((boolean[]) result)[resultIdx] = bval;
                    break;
                }
                case CHAR: {
                    ((char[]) result)[resultIdx] = (char) lval;
                    break;
                }
                case INT: {
                    ((int[]) result)[resultIdx] = (int) lval;
                    break;
                }
                case LONG: {
                    ((long[]) result)[resultIdx] = lval;
                    break;
                }
                case FLOAT: {
                    ((float[]) result)[resultIdx] = (float) dval;
                    break;
                }
                case DOUBLE: {
                    ((double[]) result)[resultIdx] = dval;
                    break;
                }
                case STRING: {
                    ((String[]) result)[resultIdx] = sval;
                    break;
                }
                }
            } else {
                switch (type) {
                case BOOLEAN: {
                    ((BooleanHolder) result).value = bval;
                    break;
                }
                case CHAR: {
                    ((CharHolder) result).value = (char) lval;
                    break;
                }
                case INT: {
                    ((IntHolder) result).value = (int) lval;
                    break;
                }
                case LONG: {
                    ((LongHolder) result).value = lval;
                    break;
                }
                case FLOAT: {
                    ((FloatHolder) result).value = (float) dval;
                    break;
                }
                case DOUBLE: {
                    ((DoubleHolder) result).value = dval;
                    break;
                }
                case STRING: {
                    ((StringHolder) result).value = sval;
                    break;
                }
                }
            }
        }
    }

    private String firstHelpOptionName() {
        if (firstHelpOption != null) {
            return firstHelpOption.nameList.name;
        } else {
            return null;
        }
    }

    /**
     * Creates an <code>ArgParser</code> with a synopsis
     * string, and the default help options <code>-help</code> and <code>-&#063;</code>.
     *
     * @param synopsisString string that briefly describes program usage,
     *            for use by {@link #getHelpMessage getHelpMessage}.
     * @see ArgParser#getSynopsisString
     * @see ArgParser#getHelpMessage
     */
    public ArgParser(String synopsisString) {
        this(synopsisString, true);
    }

    /**
     * Creates an <code>ArgParser</code> with a synopsis
     * string. The help options <code>-help</code> and <code>-?</code> are added if <code>defaultHelp</code> is true.
     *
     * @param synopsisString string that briefly describes program usage,
     *            for use by {@link #getHelpMessage getHelpMessage}.
     * @param defaultHelp if true, adds the default help options
     * @see ArgParser#getSynopsisString
     * @see ArgParser#getHelpMessage
     */
    public ArgParser(String synopsisString, boolean defaultHelp) {
        matchList = new Vector<Record>(128);
        this.synopsisString = synopsisString;
        if (defaultHelp) {
            addOption("-help,-? %h #displays help information", null);
            defaultHelpOption = firstHelpOption = matchList.get(0);
        }
    }

    /**
     * Returns the synopsis string used by the parser.
     * The synopsis string is a short description of how to invoke
     * the program, and usually looks something like
     * <p>
     * <prec> "java somepackage.SomeClass [options] files ..." </prec>
     *
     * <p>
     * It is used in help and error messages.
     *
     * @return synopsis string
     * @see ArgParser#setSynopsisString
     * @see ArgParser#getHelpMessage
     */
    public String getSynopsisString() {
        return synopsisString;
    }

    /**
     * Sets the synopsis string used by the parser.
     *
     * @param s new synopsis string
     * @see ArgParser#getSynopsisString
     * @see ArgParser#getHelpMessage
     */
    public void setSynopsisString(String s) {
        synopsisString = s;
    }

    /**
     * Indicates whether or not help options are enabled.
     *
     * @return true if help options are enabled
     * @see ArgParser#setHelpOptionsEnabled
     * @see ArgParser#addOption
     */
    public boolean getHelpOptionsEnabled() {
        return helpOptionsEnabled;
    }

    /**
     * Enables or disables help options. Help options are those
     * associated with a conversion code of <code>%h</code>. If
     * help options are enabled, and a help option is matched,
     * then the string produced by {@link #getHelpMessage getHelpMessage} is printed to the default print stream and the
     * program
     * exits with code 0. Otherwise, arguments which match help
     * options are ignored.
     *
     * @param enable enables help options if <code>true</code>.
     * @see ArgParser#getHelpOptionsEnabled
     * @see ArgParser#addOption
     * @see ArgParser#setDefaultPrintStream
     */
    public void setHelpOptionsEnabled(boolean enable) {
        helpOptionsEnabled = enable;
    }

    /**
     * Returns the default print stream used for outputting help
     * and error information.
     *
     * @return default print stream
     * @see ArgParser#setDefaultPrintStream
     */
    public PrintStream getDefaultPrintStream() {
        return printStream;
    }

    /**
     * Sets the default print stream used for outputting help
     * and error information.
     *
     * @param stream new default print stream
     * @see ArgParser#getDefaultPrintStream
     */
    public void setDefaultPrintStream(PrintStream stream) {
        printStream = stream;
    }

    /**
     * Gets the indentation used by {@link #getHelpMessage
     * getHelpMessage}.
     *
     * @return number of indentation columns
     * @see ArgParser#setHelpIndentation
     * @see ArgParser#getHelpMessage
     */
    public int getHelpIndentation() {
        return helpIndent;
    }

    /**
     * Sets the indentation used by {@link #getHelpMessage
     * getHelpMessage}. This is the number of columns that an option's help
     * information is indented. If the option's name and value information
     * can fit within this number of columns, then all information about
     * the option is placed on one line. Otherwise, the indented help
     * information is placed on a separate line.
     *
     * @param indent number of indentation columns
     * @see ArgParser#getHelpIndentation
     * @see ArgParser#getHelpMessage
     */
    public void setHelpIndentation(int indent) {
        helpIndent = indent;
    }

    //  	public void setTabSpacing (int n)
    //  	 { tabSpacing = n;
    //  	 }

    //  	public int getTabSpacing ()
    //  	 { return tabSpacing;
    //  	 }

    private void scanRangeSpec(Record rec, String s)
            throws IllegalArgumentException {
        StringScanner scanner = new StringScanner(s);
        char c, c0, c1;

        scanner.setStringDelimiters(")],}");
        c = scanner.getc(); // swallow the first '{'
        scanner.skipWhiteSpace();
        while ((c = scanner.peekc()) != '}') {
            RangePnt p0, p1;

            if (c == '[' || c == '(') {
                if (rec.convertCode == 'v' || rec.convertCode == 'b') {
                    throw new IllegalArgumentException("Sub ranges not supported for %b or %v");
                }
                c0 = scanner.getc(); // record & swallow character
                scanner.skipWhiteSpace();
                p0 = new RangePnt(scanner, rec.type);
                scanner.skipWhiteSpace();
                if (scanner.getc() != ',') {
                    throw new IllegalArgumentException("Missing ',' in subrange specification");
                }
                p1 = new RangePnt(scanner, rec.type);
                scanner.skipWhiteSpace();
                if ((c1 = scanner.getc()) != ']' && c1 != ')') {
                    throw new IllegalArgumentException("Unterminated subrange");
                }
                if (c0 == '(') {
                    p0.setClosed(false);
                }
                if (c1 == ')') {
                    p1.setClosed(false);
                }
                rec.addRangeAtom(new RangeAtom(p0, p1, rec.type));
            } else {
                scanner.skipWhiteSpace();
                p0 = new RangePnt(scanner, rec.type);
                rec.addRangeAtom(new RangeAtom(p0));
            }
            scanner.skipWhiteSpace();
            if ((c = scanner.peekc()) == ',') {
                scanner.getc();
                scanner.skipWhiteSpace();
            } else if (c != '}') {
                throw new IllegalArgumentException("Range spec: ',' or '}' expected");
            }
        }
        if (rec.numRangeAtoms() == 1) {
            rec.rangeDesc = s.substring(1, s.length() - 1);
        } else {
            rec.rangeDesc = s;
        }
    }

    private int defaultResultType(char convertCode) {
        switch (convertCode) {
        case 'i':
        case 'o':
        case 'd':
        case 'x': {
            return Record.LONG;
        }
        case 'c': {
            return Record.CHAR;
        }
        case 'v':
        case 'b': {
            return Record.BOOLEAN;
        }
        case 'f': {
            return Record.DOUBLE;
        }
        case 's': {
            return Record.STRING;
        }
        }
        return Record.NOTYPE;
    }

    /**
     * Adds a new option description to the parser. The method takes two
     * arguments: a specification string, and a result holder in which to
     * store the associated value.
     *
     * <p>
     * The specification string has the general form
     *
     * <p>
     * <var>optionNames</var> <code>%</code><var>conversionCode</var> [<code>{</code><var>rangeSpec</var><code>}</code>]
     * [<code>X</code><var>multiplier</var>] [<code>#</code><var>valueDescription</var>] [<code>#</code>
     * <var>optionDescription</var>] </code>
     *
     * <p>
     * where
     * <ul>
     * <p>
     * <li><var>optionNames</var> is a comma-separated list of names for the option (such as <code>-f, --file</code>).
     *
     * <p>
     * <li><var>conversionCode</var> is a single letter, following a <code>%</code> character, specifying information
     * about what value the option requires:
     *
     * <table>
     * <tr>
     * <td><code>%f</code></td>
     * <td>a floating point number</td>
     * <tr>
     * <td><code>%i</code></td>
     * <td>an integer, in either decimal, hex (if preceeded by <code>0x</code>), or octal (if preceeded by
     * <code>0</code>)</td>
     * <tr valign=top>
     * <td><code>%d</code></td>
     * <td>a decimal integer</td>
     * <tr valign=top>
     * <td><code>%o</code></td>
     * <td>an octal integer</td>
     * <tr valign=top>
     * <td><code>%h</code></td>
     * <td>a hex integer (without the preceeding <code>0x</code>)</td>
     * <tr valign=top>
     * <td><code>%c</code></td>
     * <td>a single character, including escape sequences (such as <code>\n</code> or <code>\007</code>), and optionally
     * enclosed in single quotes
     * <tr valign=top>
     * <td><code>%b</code></td>
     * <td>a boolean value (<code>true</code> or <code>false</code>)</td>
     * <tr valign=top>
     * <td><code>%s</code></td>
     * <td>a string. This will be the argument string itself (or its remainder, in the case of a single word option)</td>
     * <tr valign=top>
     * <td><code>%v</code></td>
     * <td>no explicit value is expected, but a boolean value of <code>true</code> (by default) will be stored into the
     * associated result holder if this option is matched. If one wishes to have a value of <code>false</code> stored
     * instead, then the <code>%v</code> should be followed by a "range spec" containing <code>false</code>, as in
     * <code>%v{false}</code>.
     * </table>
     *
     * <p>
     * <li><var>rangeSpec</var> is an optional range specification, placed inside curly braces, consisting of a
     * comma-separated list of range items each specifying permissible values for the option. A range item may be an
     * individual value, or it may itself be a subrange, consisting of two individual values, separated by a comma, and
     * enclosed in square or round brackets. Square and round brackets denote closed and open endpoints of a subrange,
     * indicating that the associated endpoint value is included or excluded from the subrange. The values specified in
     * the range spec need to be consistent with the type of value expected by the option.
     *
     * <p>
     * <b>Examples:</b>
     *
     * <p>
     * A range spec of <code>{2,4,8,16}</code> for an integer value will allow the integers 2, 4, 8, or 16.
     *
     * <p>
     * A range spec of <code>{[-1.0,1.0]}</code> for a floating point value will allow any floating point number in the
     * range -1.0 to 1.0.
     *
     * <p>
     * A range spec of <code>{(-88,100],1000}</code> for an integer value will allow values > -88 and <= 100, as well as
     * 1000.
     *
     * <p>
     * A range spec of <code>{"foo", "bar", ["aaa","zzz")} </code> for a string value will allow strings equal to
     * <code>"foo"</code> or <code>"bar"</code>, plus any string lexically greater than or equal to <code>"aaa"</code>
     * but less then <code>"zzz"</code>.
     *
     * <p>
     * <li><var>multiplier</var> is an optional integer, following a <code>X</code> character, indicating the number of
     * values which the option expects. If the multiplier is not specified, it is assumed to be 1. If the multiplier
     * value is greater than 1, then the result holder should be either an array (of appropriate type) with a length
     * greater than or equal to the multiplier value, or a <code>java.util.Vector</code> <a href=#vectorHolder>as
     * discussed below</a>.
     *
     * <p>
     * <li><var>valueDescription</var> is an optional description of the option's value requirements, and consists of
     * all characters between two <code>#</code> characters. The final <code>#</code> character initiates the <i>option
     * description</i>, which may be empty. The value description is used in <a href=#helpInfo>generating help
     * messages</a>.
     *
     * <p>
     * <li><var>optionDescription</var> is an optional description of the option itself, consisting of all characters
     * between a <code>#</code> character and the end of the specification string. The option description is used in <a
     * href=#helpInfo>generating help messages</a>.
     * </ul>
     *
     * <p>
     * The result holder must be an object capable of holding a value compatible with the conversion code, or it must be
     * a <code>java.util.Vector</code>. When the option is matched, its associated value is placed in the result holder.
     * If the same option is matched repeatedly, the result holder value will be overwritten, unless the result holder
     * is a <code>java.util.Vector</code>, in which case new holder objects for each match will be allocated and added
     * to the vector. Thus if multiple instances of an option are desired by the program, the result holder should be a
     * <code>java.util.Vector</code>.
     *
     * <p>
     * If the result holder is not a <code>Vector</code>, then it must correspond as follows to the conversion code:
     *
     * <table>
     * <tr valign=top>
     * <td><code>%i</code>, <code>%d</code>, <code>%x</code>, <code>%o</code></td>
     * <td>{@link argparser.IntHolder IntHolder}, {@link argparser.LongHolder LongHolder}, <code>int[]</code>, or
     * <code>long[]</code></td>
     * </tr>
     *
     * <tr valign=top>
     * <td><code>%f</code></td>
     * <td>{@link argparser.FloatHolder FloatHolder}, {@link argparser.DoubleHolder DoubleHolder}, <code>float[]</code>,
     * or <code>double[]</code></td>
     * </tr>
     *
     * <tr valign=top>
     * <td><code>%b</code>, <code>%v</code></td>
     * <td>{@link argparser.BooleanHolder BooleanHolder} or <code>boolean[]</code></td>
     * </tr>
     *
     * <tr valign=top>
     * <td><code>%s</code></td>
     * <td>{@link argparser.StringHolder StringHolder} or <code>String[]</code></td>
     * </tr>
     *
     * <tr valign=top>
     * <td><code>%c</code></td>
     * <td>{@link argparser.CharHolder CharHolder} or <code>char[]</code></td>
     * </tr>
     * </table>
     *
     * <p>
     * In addition, if the multiplier is greater than 1, then only the array type indicated above may be used, and the
     * array must be at least as long as the multiplier.
     *
     * <p>
     * <a name=vectorHolder>If the result holder is a <code>Vector</code>, then the system will create an appropriate
     * result holder object and add it to the vector. Multiple occurances of the option will cause multiple results to
     * be added to the vector.
     *
     * <p>
     * The object allocated by the system to store the result will correspond to the conversion code as follows:
     *
     * <table>
     * <tr valign=top>
     * <td><code>%i</code>, <code>%d</code>, <code>%x</code>, <code>%o</code></td>
     * <td>{@link argparser.LongHolder LongHolder}, or <code>long[]</code> if the multiplier value exceeds 1</td>
     * </tr>
     *
     * <tr valign=top>
     * <td><code>%f</code></td>
     * <td>{@link argparser.DoubleHolder DoubleHolder}, or <code>double[]</code> if the multiplier value exceeds 1</td>
     * </tr>
     *
     * <tr valign=top>
     * <td><code>%b</code>, <code>%v</code></td>
     * <td>{@link argparser.BooleanHolder BooleanHolder}, or <code>boolean[]</code> if the multiplier value exceeds 1</td>
     * </tr>
     *
     * <tr valign=top>
     * <td><code>%s</code></td>
     * <td>{@link argparser.StringHolder StringHolder}, or <code>String[]</code> if the multiplier value exceeds 1</td>
     * </tr>
     *
     * <tr valign=top>
     * <td><code>%c</code></td>
     * <td>{@link argparser.CharHolder CharHolder}, or <code>char[]</code> if the multiplier value exceeds 1</td>
     * </tr>
     * </table>
     *
     * @param spec the specification string
     * @param resHolder object in which to store the associated
     *            value
     * @throws IllegalArgumentException if there is an error in
     *             the specification or if the result holder is of an invalid
     *             type.
     */
    public void addOption(String spec, Object resHolder)
            throws IllegalArgumentException {
        // null terminated string is easier to parse
        StringScanner scanner = new StringScanner(spec);
        Record rec = null;
        NameDesc nameTail = null;
        NameDesc ndesc;
        int i0, i1;
        char c;

        do {
            ndesc = new NameDesc();
            boolean nameEndsInWhiteSpace = false;

            scanner.skipWhiteSpace();
            i0 = scanner.getIndex();
            while (!Character.isWhitespace(c = scanner.getc()) &&
                    c != ',' && c != '%' && c != '\000')
                ;
            i1 = scanner.getIndex();
            if (c != '\000') {
                i1--;
            }
            if (i0 == i1) { // then c is one of ',' '%' or '\000'
                throw new IllegalArgumentException("Null option name given");
            }
            if (Character.isWhitespace(c)) {
                nameEndsInWhiteSpace = true;
                scanner.skipWhiteSpace();
                c = scanner.getc();
            }
            if (c == '\000') {
                throw new IllegalArgumentException("No conversion character given");
            }
            if (c != ',' && c != '%') {
                throw new IllegalArgumentException("Names not separated by ','");
            }
            ndesc.name = scanner.substring(i0, i1);
            if (rec == null) {
                rec = new Record();
                rec.nameList = ndesc;
            } else {
                nameTail.next = ndesc;
            }
            nameTail = ndesc;
            ndesc.oneWord = !nameEndsInWhiteSpace;
        } while (c != '%');

        if (!nameTail.oneWord) {
            for (ndesc = rec.nameList; ndesc != null; ndesc = ndesc.next) {
                ndesc.oneWord = false;
            }
        }
        c = scanner.getc();
        if (c == '\000') {
            throw new IllegalArgumentException("No conversion character given");
        }
        if (validConversionCodes.indexOf(c) == -1) {
            throw new IllegalArgumentException("Conversion code '" + c + "' not one of '" +
                    validConversionCodes + "'");
        }
        rec.convertCode = c;

        if (resHolder instanceof Vector) {
            rec.vectorResult = true;
            rec.type = defaultResultType(rec.convertCode);
        } else {
            switch (rec.convertCode) {
            case 'i':
            case 'o':
            case 'd':
            case 'x': {
                if (resHolder instanceof LongHolder ||
                        resHolder instanceof long[]) {
                    rec.type = Record.LONG;
                } else if (resHolder instanceof IntHolder ||
                        resHolder instanceof int[]) {
                    rec.type = Record.INT;
                } else {
                    throw new IllegalArgumentException(
                            "Invalid result holder for %" + c);
                }
                break;
            }
            case 'c': {
                if (!(resHolder instanceof CharHolder) &&
                        !(resHolder instanceof char[])) {
                    throw new IllegalArgumentException(
                            "Invalid result holder for %c");
                }
                rec.type = Record.CHAR;
                break;
            }
            case 'v':
            case 'b': {
                if (!(resHolder instanceof BooleanHolder) &&
                        !(resHolder instanceof boolean[])) {
                    throw new IllegalArgumentException(
                            "Invalid result holder for %" + c);
                }
                rec.type = Record.BOOLEAN;
                break;
            }
            case 'f': {
                if (resHolder instanceof DoubleHolder ||
                        resHolder instanceof double[]) {
                    rec.type = Record.DOUBLE;
                } else if (resHolder instanceof FloatHolder ||
                        resHolder instanceof float[]) {
                    rec.type = Record.FLOAT;
                } else {
                    throw new IllegalArgumentException(
                            "Invalid result holder for %f");
                }
                break;
            }
            case 's': {
                if (!(resHolder instanceof StringHolder) &&
                        !(resHolder instanceof String[])) {
                    throw new IllegalArgumentException(
                            "Invalid result holder for %s");
                }
                rec.type = Record.STRING;
                break;
            }
            case 'h': { // resHolder is ignored for this type
                break;
            }
            }
        }
        if (rec.convertCode == 'h') {
            rec.resHolder = null;
        } else {
            rec.resHolder = resHolder;
        }

        scanner.skipWhiteSpace();
        // get the range specification, if any
        if (scanner.peekc() == '{') {
            if (rec.convertCode == 'h') {
                throw new IllegalArgumentException("Ranges not supported for %h");
            }
            //	      int bcnt = 0;
            i0 = scanner.getIndex(); // beginning of range spec
            do {
                c = scanner.getc();
                if (c == '\000') {
                    throw new IllegalArgumentException("Unterminated range specification");
                }
                //  		 else if (c=='[' || c=='(')
                //  		  { bcnt++;
                //  		  }
                //  		 else if (c==']' || c==')')
                //  		  { bcnt--;
                //  		  }
                //  		 if ((rec.convertCode=='v'||rec.convertCode=='b') && bcnt>1)
                //  		  { throw new IllegalArgumentException
                //  		      ("Sub ranges not supported for %b or %v");
                //  		  }
            } while (c != '}');
            //  	      if (c != ']')
            //  	       { throw new IllegalArgumentException
            //  		    ("Range specification must end with ']'");
            //  	       }
            i1 = scanner.getIndex(); // end of range spec
            scanRangeSpec(rec, scanner.substring(i0, i1));
            if (rec.convertCode == 'v' && rec.rangeList != null) {
                rec.vval = rec.rangeList.low.bval;
            }
        }
        // check for value multiplicity information, if any
        if (scanner.peekc() == 'X') {
            if (rec.convertCode == 'h') {
                throw new IllegalArgumentException("Multipliers not supported for %h");
            }
            scanner.getc();
            try {
                rec.numValues = (int) scanner.scanInt();
            } catch (StringScanException e) {
                throw new IllegalArgumentException("Malformed value multiplier");
            }
            if (rec.numValues <= 0) {
                throw new IllegalArgumentException("Value multiplier number must be > 0");
            }
        } else {
            rec.numValues = 1;
        }
        if (rec.numValues > 1) {
            for (ndesc = rec.nameList; ndesc != null; ndesc = ndesc.next) {
                if (ndesc.oneWord) {
                    throw new IllegalArgumentException(
                            "Multiplier value incompatible with one word option " + ndesc.name);
                }
            }
        }
        if (resHolder != null && resHolder.getClass().isArray()) {
            if (Array.getLength(resHolder) < rec.numValues) {
                throw new IllegalArgumentException(
                        "Result holder array must have a length >= " + rec.numValues);
            }
        } else {
            if (rec.numValues > 1 && !(resHolder instanceof Vector)) {
                throw new IllegalArgumentException(
                        "Multiplier requires result holder to be an array of length >= "
                                + rec.numValues);
            }
        }

        // skip white space following conversion information
        scanner.skipWhiteSpace();

        // get the help message, if any

        if (!scanner.atEnd()) {
            if (scanner.getc() != '#') {
                throw new IllegalArgumentException("Illegal character(s), expecting '#'");
            }
            String helpInfo = scanner.substring(scanner.getIndex());
            // look for second '#'. If there is one, then info
            // between the first and second '#' is the value descriptor.
            int k = helpInfo.indexOf("#");
            if (k != -1) {
                rec.valueDesc = helpInfo.substring(0, k);
                rec.helpMsg = helpInfo.substring(k + 1);
            } else {
                rec.helpMsg = helpInfo;
            }
        } else {
            rec.helpMsg = "";
        }

        // parse helpMsg for required/optional information if present
        // default to required
        if (rec.helpMsg.indexOf("(optional") != -1) {
            rec.required = false;
        }

        // add option information to match list
        if (rec.convertCode == 'h' && firstHelpOption == defaultHelpOption) {
            matchList.remove(defaultHelpOption);
            firstHelpOption = rec;
        }
        matchList.add(rec);
    }

    Record lastMatchRecord() {
        return matchList.lastElement();
    }

    private Record getRecord(String arg, ObjectHolder ndescHolder) {
        NameDesc ndesc;
        for (int i = 0; i < matchList.size(); i++) {
            Record rec = matchList.get(i);
            for (ndesc = rec.nameList; ndesc != null; ndesc = ndesc.next) {
                if (rec.convertCode != 'v' && ndesc.oneWord) {
                    if (arg.startsWith(ndesc.name)) {
                        if (ndescHolder != null) {
                            ndescHolder.value = ndesc;
                        }
                        return rec;
                    }
                } else {
                    if (arg.equals(ndesc.name)) {
                        if (ndescHolder != null) {
                            ndescHolder.value = ndesc;
                        }
                        return rec;
                    }
                }
            }
        }
        return null;
    }

    public void checkRequiredArgs() {
        for (int i = 1; i < matchList.size(); i++) {
            Record rec = matchList.get(i);
            StringHolder myString = (StringHolder) rec.resHolder;
            if (((myString.value == null) || (myString.value.equals(""))) && (rec.required)) {
                printErrorAndExit("Required parameter " + rec.nameList.name + " is not specified.");
            }
        }
    }

    Object getResultHolder(String arg) {
        Record rec = getRecord(arg, null);
        return (rec != null) ? rec.resHolder : null;
    }

    String getOptionName(String arg) {
        ObjectHolder ndescHolder = new ObjectHolder();
        Record rec = getRecord(arg, ndescHolder);
        return (rec != null) ? ((NameDesc) ndescHolder.value).name : null;
    }

    String getOptionRangeDesc(String arg) {
        Record rec = getRecord(arg, null);
        return (rec != null) ? rec.rangeDesc : null;
    }

    String getOptionTypeName(String arg) {
        Record rec = getRecord(arg, null);
        return (rec != null) ? rec.valTypeName() : null;
    }

    private Object createResultHolder(Record rec) throws ArgParseException {
        if (rec.numValues == 1) {
            switch (rec.type) {
            case Record.LONG: {
                return new LongHolder();
            }
            case Record.CHAR: {
                return new CharHolder();
            }
            case Record.BOOLEAN: {
                return new BooleanHolder();
            }
            case Record.DOUBLE: {
                return new DoubleHolder();
            }
            case Record.STRING: {
                return new StringHolder();
            }
            }
        } else {
            switch (rec.type) {
            case Record.LONG: {
                return new long[rec.numValues];
            }
            case Record.CHAR: {
                return new char[rec.numValues];
            }
            case Record.BOOLEAN: {
                return new boolean[rec.numValues];
            }
            case Record.DOUBLE: {
                return new double[rec.numValues];
            }
            case Record.STRING: {
                return new String[rec.numValues];
            }
            }
        }

        throw new ArgParseException("Bad parameters in the Record for Result Holder. Type :" + rec.type
                + " ,Number of Values : " + rec.numValues); // can't happen
    }

    static void stringToArgs(Vector<String> vec, String s,
                  boolean allowQuotedStrings)
            throws StringScanException {
        StringScanner scanner = new StringScanner(s);
        scanner.skipWhiteSpace();
        while (!scanner.atEnd()) {
            if (allowQuotedStrings) {
                vec.add(scanner.scanString());
            } else {
                vec.add(scanner.scanNonWhiteSpaceString());
            }
            scanner.skipWhiteSpace();
        }
    }

    /**
     * Reads in a set of strings from a reader and prepends them to an
     * argument list. Strings are delimited by either whitespace or
     * double quotes <code>"</code>. The character <code>#</code> acts as
     * a comment character, causing input to the end of the current line to
     * be ignored.
     *
     * @param reader Reader from which to read the strings
     * @param args Initial set of argument values. Can be
     *            specified as <code>null</code>.
     * @throws IOException if an error occured while reading.
     */
    public static String[] prependArgs(Reader reader, String[] args)
            throws IOException {
        if (args == null) {
            args = new String[0];
        }
        LineNumberReader lineReader = new LineNumberReader(reader);
        Vector<String> vec = new Vector<String>(100, 100);
        String line;
        int i, k;

        while ((line = lineReader.readLine()) != null) {
            int commentIdx = line.indexOf("#");
            if (commentIdx != -1) {
                line = line.substring(0, commentIdx);
            }
            try {
                stringToArgs(vec, line, /*allowQuotedStings=*/true);
            } catch (StringScanException e) {
                throw new IOException(
                        "malformed string, line " + lineReader.getLineNumber());
            }
        }
        String[] result = new String[vec.size() + args.length];
        for (i = 0; i < vec.size(); i++) {
            result[i] = vec.get(i);
        }
        for (k = 0; k < args.length; k++) {
            result[i++] = args[k];
        }
        return result;
    }

    /**
     * Reads in a set of strings from a file and prepends them to an
     * argument list. Strings are delimited by either whitespace or double
     * quotes <code>"</code>. The character <code>#</code> acts as a
     * comment character, causing input to the end of the current line to
     * be ignored.
     *
     * @param file File to be read
     * @param args Initial set of argument values. Can be
     *            specified as <code>null</code>.
     * @throws IOException if an error occured while reading the file.
     */
    public static String[] prependArgs(File file, String[] args)
            throws IOException {
        if (args == null) {
            args = new String[0];
        }
        if (!file.canRead()) {
            return args;
        }
        try {
            return prependArgs(new FileReader(file), args);
        } catch (IOException e) {
            throw new IOException(
                    "File " + file.getName() + ": " + e.getMessage());
        }
    }

    /**
     * Sets the parser's error message.
     *
     * @param s Error message
     */
    protected void setError(String msg) {
        errMsg = msg;
    }

    /**
     * Prints an error message, along with a pointer to help options,
     * if available, and causes the program to exit with code 1.
     */
    public void printErrorAndExit(String msg) {
        if (helpOptionsEnabled && firstHelpOptionName() != null) {
            msg += "\nUse " + firstHelpOptionName() + " for help information";
        }
        if (printStream != null) {
            printStream.println(msg);
        }
        System.exit(1);
    }

    /**
     * Matches arguments within an argument list.
     *
     * <p>
     * In the event of an erroneous or unmatched argument, the method prints a message and exits the program with code
     * 1.
     *
     * <p>
     * If help options are enabled and one of the arguments matches a help option, then the result of
     * {@link #getHelpMessage
     * getHelpMessage} is printed to the default print stream and the program exits with code 0. If help options are not
     * enabled, they are ignored.
     *
     * @param args argument list
     * @see ArgParser#getDefaultPrintStream
     */
    public void matchAllArgs(String[] args) {
        matchAllArgs(args, 0, EXIT_ON_UNMATCHED | EXIT_ON_ERROR);
    }

    /**
     * Matches arguments within an argument list and returns
     * those which were not matched. The matching starts at a location
     * in <code>args</code> specified by <code>idx</code>, and
     * unmatched arguments are returned in a String array.
     *
     * <p>
     * In the event of an erroneous argument, the method either prints a message and exits the program (if
     * {@link #EXIT_ON_ERROR} is set in <code>exitFlags</code>) or terminates the matching and creates a error message
     * that can be retrieved by {@link #getErrorMessage}.
     *
     * <p>
     * In the event of an umatched argument, the method will print a message and exit if {@link #EXIT_ON_UNMATCHED} is
     * set in <code>errorFlags</code>. Otherwise, the unmatched argument will be appended to the returned array of
     * unmatched values, and the matching will continue at the next location.
     *
     * <p>
     * If help options are enabled and one of the arguments matches a help option, then the result of
     * {@link #getHelpMessage
     * getHelpMessage} is printed to the the default print stream and the program exits with code 0. If help options are
     * not enabled, then they will not be matched.
     *
     * @param args argument list
     * @param idx starting location in list
     * @param exitFlags conditions causing the program to exit. Should be
     *            an or-ed combintion of {@link #EXIT_ON_ERROR} or {@link #EXIT_ON_UNMATCHED}.
     * @return array of arguments that were not matched, or <code>null</code> if all arguments were successfully matched
     * @see ArgParser#getErrorMessage
     * @see ArgParser#getDefaultPrintStream
     */
    public String[] matchAllArgs(String[] args, int idx, int exitFlags) {
        Vector<String> unmatched = new Vector<String>(10);

        while (idx < args.length) {
            try {
                idx = matchArg(args, idx);
                if (unmatchedArg != null) {
                    if ((exitFlags & EXIT_ON_UNMATCHED) != 0) {
                        printErrorAndExit("Unrecognized argument: " + unmatchedArg);
                    } else {
                        unmatched.add(unmatchedArg);
                    }
                }
            } catch (ArgParseException e) {
                if ((exitFlags & EXIT_ON_ERROR) != 0) {
                    printErrorAndExit(e.getMessage());
                }
                break;
            }
        }
        if (unmatched.size() == 0) {
            return null;
        } else {
            return unmatched.toArray(new String[0]);
        }
    }

    /**
     * Matches one option starting at a specified location in an argument
     * list. The method returns the location in the list where the next
     * match should begin.
     *
     * <p>
     * In the event of an erroneous argument, the method throws an {@link argparser.ArgParseException ArgParseException}
     * with an appropriate error message. This error message can also be retrieved using {@link #getErrorMessage
     * getErrorMessage}.
     *
     * <p>
     * In the event of an umatched argument, the method will return idx + 1, and {@link #getUnmatchedArgument
     * getUnmatchedArgument} will return a copy of the unmatched argument. If an argument is matched,
     * {@link #getUnmatchedArgument getUnmatchedArgument} will return <code>null</code>.
     *
     * <p>
     * If help options are enabled and the argument matches a help option, then the result of {@link #getHelpMessage
     * getHelpMessage} is printed to the the default print stream and the program exits with code 0. If help options are
     * not enabled, then they are ignored.
     *
     * @param args argument list
     * @param idx location in list where match should start
     * @return location in list where next match should start
     * @throws ArgParseException if there was an error performing
     *             the match (such as improper or insufficient values).
     * @see ArgParser#setDefaultPrintStream
     * @see ArgParser#getHelpOptionsEnabled
     * @see ArgParser#getErrorMessage
     * @see ArgParser#getUnmatchedArgument
     */
    @SuppressWarnings("unchecked")
    public int matchArg(String[] args, int idx)
            throws ArgParseException {
        unmatchedArg = null;
        setError(null);
        try {
            ObjectHolder ndescHolder = new ObjectHolder();
            Record rec = getRecord(args[idx], ndescHolder);
            if (rec == null || (rec.convertCode == 'h' && !helpOptionsEnabled)) { // didn't match
                unmatchedArg = args[idx];
                return idx + 1;
            }
            NameDesc ndesc = (NameDesc) ndescHolder.value;
            Object result;
            if (rec.resHolder instanceof Vector) {
                result = createResultHolder(rec);
            } else {
                result = rec.resHolder;
            }
            if (rec.convertCode == 'h') {
                if (helpOptionsEnabled) {
                    printStream.println(getHelpMessage());
                    System.exit(0);
                } else {
                    return idx + 1;
                }
            } else if (rec.convertCode != 'v') {
                if (ndesc.oneWord) {
                    rec.scanValue(
                            result, ndesc.name,
                            args[idx].substring(ndesc.name.length()), 0);
                } else {
                    if (idx + rec.numValues >= args.length) {
                        throw new ArgParseException(
                                ndesc.name, "requires " + rec.numValues + " value" +
                                        (rec.numValues > 1 ? "s" : ""));
                    }
                    for (int k = 0; k < rec.numValues; k++) {
                        rec.scanValue(result, ndesc.name, args[++idx], k);
                    }
                }
            } else {
                if (rec.resHolder instanceof BooleanHolder) {
                    ((BooleanHolder) result).value = rec.vval;
                } else {
                    for (int k = 0; k < rec.numValues; k++) {
                        ((boolean[]) result)[k] = rec.vval;
                    }
                }
            }
            if (rec.resHolder instanceof Vector) {
                ((Vector<Object>) rec.resHolder).add(result);
            }
        } catch (ArgParseException e) {
            setError(e.getMessage());
            throw e;
        }
        return idx + 1;
    }

    private String spaceString(int n) {
        StringBuffer sbuf = new StringBuffer(n);
        for (int i = 0; i < n; i++) {
            sbuf.append(' ');
        }
        return sbuf.toString();
    }

    // 	public String getShortHelpMessage ()
    // 	 {
    // 	   String s;
    // 	   Record rec;
    // 	   NameDesc ndesc;
    // 	   int initialIndent = 8;
    // 	   int col = initialIndent;

    // 	   if (maxcols <= 0)
    // 	    { maxcols = 80;
    // 	    }
    // 	   if (matchList.size() > 0)
    // 	    { ps.print (spaceString(initialIndent));
    // 	    }
    // 	   for (int i=0; i<matchList.size(); i++)
    // 	    { rec = (Record)matchList.get(i);
    // 	      s = "[";
    // 	      for (ndesc=rec.nameList; ndesc!=null; ndesc=ndesc.next)
    // 	       { s = s + ndesc.name;
    // 		 if (ndesc.oneWord == false)
    // 		  { s = s + " ";
    // 		  }
    // 		 if (ndesc.next != null)
    // 		  { s = s + ",";
    // 		  }
    // 	       }
    // 	      if (rec.convertCode != 'v' && rec.convertCode != 'h')
    // 	       { if (rec.valueDesc != null)
    // 		  { s += rec.valueDesc;
    // 		  }
    // 		 else
    // 		  { s = s + "<" + rec.valTypeName() + ">";
    // 		    if (rec.numValues > 1)
    // 		     { s += "X" + rec.numValues;
    // 		     }
    // 		  }
    // 	       }
    // 	      s = s + "]";
    // 	      /*
    // 		 (col+=s.length()) > (maxcols-1) => we will spill over edge.
    // 			 we use (maxcols-1) because if we go right to the edge
    // 			 (maxcols), we get wrap new line inserted "for us".
    // 		 i != 0 means we print the first entry, no matter
    // 			 how long it is. Subsequent entries are printed
    // 			 full length anyway. */

    // 	      if ((col+=s.length()) > (maxcols-1) && i != 0)
    // 	       { col = initialIndent+s.length();
    // 		 ps.print ("\n" + spaceString(initialIndent));
    // 	       }
    // 	      ps.print (s);
    // 	    }
    // 	   if (matchList.size() > 0)
    // 	    { ps.print ('\n');
    // 	      ps.flush();
    // 	    }
    // 	 }

    /**
     * Returns a string describing the allowed options
     * in detail.
     *
     * @return help information string.
     */
    public String getHelpMessage() {
        Record rec;
        NameDesc ndesc;
        boolean hasOneWordAlias = false;
        String s;

        s = "Usage: " + synopsisString + "\n";
        s += "Options include:\n\n";
        for (int i = 0; i < matchList.size(); i++) {
            StringBuffer optionInfo = new StringBuffer();
            rec = matchList.get(i);
            if (rec.convertCode == 'h' && !helpOptionsEnabled) {
                continue;
            }
            for (ndesc = rec.nameList; ndesc != null; ndesc = ndesc.next) {
                if (ndesc.oneWord) {
                    hasOneWordAlias = true;
                    break;
                }
            }
            for (ndesc = rec.nameList; ndesc != null; ndesc = ndesc.next) {
                optionInfo.append(ndesc.name);
                if (hasOneWordAlias && !ndesc.oneWord) {
                    optionInfo.append(" ");
                }
                if (ndesc.next != null) {
                    optionInfo.append(",");
                }
            }
            if (!hasOneWordAlias) {
                optionInfo.append(" ");
            }
            if (rec.convertCode != 'v' && rec.convertCode != 'h') {
                if (rec.valueDesc != null) {
                    optionInfo.append(rec.valueDesc);
                } else {
                    if (rec.rangeDesc != null) {
                        optionInfo.append("<" + rec.valTypeName() + " "
                                + rec.rangeDesc + ">");
                    } else {
                        optionInfo.append("<" + rec.valTypeName() + ">");
                    }
                }
            }
            if (rec.numValues > 1) {
                optionInfo.append("X" + rec.numValues);
            }
            s += optionInfo.toString();
            if (rec.helpMsg.length() > 0) {
                int pad = helpIndent - optionInfo.length();
                if (pad < 2) { //s += '\n';
                    pad = helpIndent;
                }
                //		 s += spaceString(pad) + rec.helpMsg;
                s += spaceString(4) + rec.helpMsg;
            }
            s += '\n';
        }
        return s;
    }

    /**
     * Returns the parser's error message. This is automatically
     * set whenever an error is encountered in <code>matchArg</code> or <code>matchAllArgs</code>, and is automatically
     * set to <code>null</code> at the beginning of these methods.
     *
     * @return error message
     */
    public String getErrorMessage() {
        return errMsg;
    }

    /**
     * Returns the value of an unmatched argument discovered {@link #matchArg matchArg} or
     * {@link #matchAllArgs(String[],int,int)
     * matchAllArgs}. If there was no unmatched argument, <code>null</code> is returned.
     *
     * @return unmatched argument
     */
    public String getUnmatchedArgument() {
        return unmatchedArg;
    }
}
