root/usr/src/cmd/dtrace/test/cmd/jdtrace/Getopt.java
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 *
 * ident        "%Z%%M% %I%     %E% SMI"
 */

/* Copyright (c) 1988 AT&T */
/* All Rights Reserved */

import java.io.StringWriter;
import java.io.PrintWriter;

/**
 * A Java port of Solaris {@code lib/libc/port/gen/getopt.c}, which is a
 * port of System V UNIX getopt.  See <b>getopt(3C)</b> and SUS/XPG
 * getopt() for function definition and requirements. Unlike that
 * definition, this implementation moves non-options to the end of the
 * argv array rather than quitting at the first non-option.
 */
public class Getopt {
    static final int EOF = -1;

    private String progname;
    private String[] args;
    private int argc;
    private String optstring;
    private int optind = 0; // args index
    private int optopt = 0;
    private String optarg = null;
    private boolean opterr = true;

    /*
     * _sp is required to keep state between successive calls to
     * getopt() while extracting aggregated short-options (ie: -abcd).
     */
    private int _sp = 1;

    /**
     * Creates a {Code Getopt} instance to parse the given command-line
     * arguments. Modifies the given args array by swapping the
     * positions of non-options and options so that non-options appear
     * at the end of the array.
     */
    public Getopt(String programName, String[] args,
            String optionString)
    {
        progname = programName;
        // No defensive copy; Getopt is expected to modify the given
        // args array
        this.args = args;
        argc = this.args.length;
        optstring = optionString;
        validate();
    }

    private void
    validate()
    {
        if (progname == null) {
            throw new NullPointerException("program name is null");
        }
        int i = 0;
        for (String s : args) {
            if (s == null) {
                throw new NullPointerException("null arg at index " + i);
            }
            ++i;
        }
        if (optstring == null) {
            throw new NullPointerException("option string is null");
        }
    }

    private static class StringRef {
        private String s;

        public String
        get()
        {
            return s;
        }

        public StringRef
        set(String value)
        {
            s = value;
            return this;
        }
    }

    /*
     * Generalized error processing method. If the optstr parameter is
     * null, the character c is converted to a string and displayed
     * instead.
     */
    void
    err(String format, char c, String optstr)
    {
        if (opterr && optstring.charAt(0) != ':') {
            StringWriter w = new StringWriter();
            PrintWriter p = new PrintWriter(w);
            p.printf(format, progname, (optstr == null ?
                    Character.toString(c) : optstr.substring(2)));
            System.err.println(w.toString());
        }
    }

    /*
     * Determine if the specified character (c) is present in the string
     * (optstring) as a regular, single character option. If the option
     * is found, return an index into optstring where the short-option
     * character is found, otherwise return -1. The characters ':' and
     * '(' are not allowed.
     */
    static int
    parseshort(String optstring, char c)
    {
        if (c == ':' || c == '(') {
            return -1;
        }

        int ch;
        int len = optstring.length();
        for (int i = 0; i < len; ++i) {
            ch = optstring.charAt(i);
            if (ch == c) {
                return i;
            }

            while (i < len && ch == '(') {
                for (++i; i < len && (ch = optstring.charAt(i)) != ')'; ++i);
            }
        }

        return -1;
    }

    /**
     * Determine if the specified string (opt) is present in the string
     * (optstring) as a long-option contained within parenthesis. If the
     * long-option specifies option-argument, return a reference to it
     * in longoptarg.  Otherwise set the longoptarg reference to null.
     * If the option is found, return an index into optstring at the
     * position of the short-option character associated with the
     * long-option; otherwise return -1.
     *
     * @param optstring the entire optstring passed to the {@code
     * Getopt} constructor
     * @param opt the long option read from the command line
     * @param longoptarg the value of the option is returned in this
     * parameter, if an option exists. Possible return values in
     * longoptarg are:
     * <ul>
     * <li><b>NULL:</b> No argument was found</li>
     * <li><b>empty string (""):</b> Argument was explicitly left empty
     * by the user (e.g., --option= )</li>
     * <li><b>valid string:</b> Argument found on the command line</li>
     * </ul>
     * @return index to equivalent short-option in optstring, or -1 if
     * option not found in optstring.
     */
    static int
    parselong(String optstring, String opt, StringRef longoptarg)
    {
        int cp; // index into optstring, beginning of one option spec
        int ip; // index into optstring, traverses every char
        char ic; // optstring char
        int il; // optstring length
        int op; // index into opt
        char oc; // opt char
        int ol; // opt length
        boolean match; // true if opt is matching part of optstring

        longoptarg.set(null);
        cp = ip = 0;
        il = optstring.length();
        ol = opt.length();
        do {
            ic = optstring.charAt(ip);
            if (ic != '(' && ++ip == il)
                break;
            ic = optstring.charAt(ip);
            if (ic == ':' && ++ip == il)
                break;
            ic = optstring.charAt(ip);
            while (ic == '(') {
                if (++ip == il)
                    break;
                op = 0;
                match = true;
                while (ip < il && (ic = optstring.charAt(ip)) != ')' &&
                        op < ol) {
                    oc = opt.charAt(op++);
                    match = (ic == oc && match);
                    ++ip;
                }

                if (match && ip < il && ic == ')' && (op >= ol ||
                        opt.charAt(op) == '=')) {
                    if (op < ol && opt.charAt(op) == '=') {
                        /* may be an empty string - OK */
                        longoptarg.set(opt.substring(op + 1));
                    } else {
                        longoptarg.set(null);
                    }
                    return cp;
                }
                if (ip < il && ic == ')' && ++ip == il)
                    break;
                ic = optstring.charAt(ip);
            }
            cp = ip;
            /*
             * Handle double-colon in optstring ("a::(longa)") The old
             * getopt() accepts it and treats it as a required argument.
             */
            while ((cp > 0) && (cp < il) && (optstring.charAt(cp) == ':')) {
                --cp;
            }
        } while (cp < il);
        return -1;
    }

    /**
     * Get the current option value.
     */
    public String
    getOptarg()
    {
        return optarg;
    }

    /**
     * Get the index of the next option to be parsed.
     */
    public int
    getOptind()
    {
        return optind;
    }

    /**
     * Gets the command-line arguments.
     */
    public String[]
    getArgv()
    {
        // No defensive copy: Getopt is expected to modify the given
        // args array.
        return args;
    }

    /**
     * Gets the aggregated short option that just failed. Since long
     * options can't be aggregated, a failed long option can be obtained
     * by {@code getArgv()[getOptind() - 1]}.
     */
    public int
    getOptopt()
    {
        return optopt;
    }

    /**
     * Set to {@code false} to suppress diagnostic messages to stderr.
     */
    public void
    setOpterr(boolean err)
    {
        opterr = err;
    }

    /**
     * Gets the next option character, or -1 if there are no more
     * options. If getopt() encounters a short-option character or a
     * long-option string not described in the {@code optionString}
     * argument to the constructor, it returns the question-mark (?)
     * character. If it detects a missing option-argument, it also
     * returns the question-mark (?) character, unless the first
     * character of the {@code optionString} argument was a colon (:),
     * in which case getopt() returns the colon (:) character.
     * <p>
     * This implementation swaps the positions of options and
     * non-options in the given argv array.
     */
    public int
    getopt()
    {
        char c;
        int cp;
        boolean longopt;
        StringRef longoptarg = new StringRef();

        /*
         * Has the end of the options been encountered?  The following
         * implements the SUS requirements:
         *
         * If, when getopt() is called:
         *      - the first character of argv[optind] is not '-'
         *      - argv[optind] is the string "-"
         * getopt() returns -1 without changing optind if
         *      - argv[optind] is the string "--"
         * getopt() returns -1 after incrementing optind
         */
        if (_sp == 1) {
            boolean nonOption;
            do {
                nonOption = false;
                if (optind >= argc || args[optind].equals("-")) {
                    return EOF;
                } else if (args[optind].equals("--")) {
                    ++optind;
                    return EOF;
                } else if (args[optind].charAt(0) != '-') {
                    // non-option: here we deviate from the SUS requirements
                    // by not quitting, and instead move non-options to the
                    // end of the args array
                    nonOption = true;
                    String tmp = args[optind];
                    if (optind + 1 < args.length) {
                        System.arraycopy(args, optind + 1, args, optind,
                                args.length - (optind + 1));
                        args[args.length - 1] = tmp;
                    }
                    --argc;
                }
            } while (nonOption);
        }

        /*
         * Getting this far indicates that an option has been encountered.
         * Note that the syntax of optstring applies special meanings to
         * the characters ':' and '(', so they are not permissible as
         * option letters. A special meaning is also applied to the ')'
         * character, but its meaning can be determined from context.
         * Note that the specification only requires that the alnum
         * characters be accepted.
         *
         * If the second character of the argument is a '-' this must be
         * a long-option, otherwise it must be a short option.  Scan for
         * the option in optstring by the appropriate algorithm. Either
         * scan will return an index to the short-option character in
         * optstring if the option is found and -1 otherwise.
         *
         * For an unrecognized long-option, optopt will equal 0, but
         * since long-options can't aggregate the failing option can be
         * identified by argv[optind-1].
         */
        optopt = c = args[optind].charAt(_sp);
        optarg = null;
        longopt = (_sp == 1 && c == '-');
        if (!(longopt
                ? ((cp = parselong(optstring, args[optind].substring(2),
                longoptarg)) != -1)
                : ((cp = parseshort(optstring, c)) != -1))) {
            err("%s: illegal option -- %s", c,
                    (longopt ? args[optind] : null));
            /*
             * Note: When the long option is unrecognized, optopt will
             * be '-' here, which matches the specification.
             */
            if (args[optind].length() == ++_sp || longopt) {
                ++optind;
                _sp = 1;
            }
            return '?';
        }
        optopt = c = optstring.charAt(cp);

        /*
         * A valid option has been identified.  If it should have an
         * option-argument, process that now.  SUS defines the setting
         * of optarg as follows:
         *
         *   1. If the option was the last character in an element of
         *   argv, then optarg contains the next element of argv, and
         *   optind is incremented by 2. If the resulting value of
         *   optind is not less than argc, this indicates a missing
         *   option-argument, and getopt() returns an error indication.
         *
         *   2. Otherwise, optarg points to the string following the
         *   option character in that element of argv, and optind is
         *   incremented by 1.
         *
         * The second clause allows -abcd (where b requires an
         * option-argument) to be interpreted as "-a -b cd".
         *
         * Note that the option-argument can legally be an empty string,
         * such as:
         *      command --option= operand
         * which explicitly sets the value of --option to nil
         */
        if (cp + 1 < optstring.length() && optstring.charAt(cp + 1) == ':') {
            // The option takes an argument
            if (!longopt && ((_sp + 1) < args[optind].length())) {
                optarg = args[optind++].substring(_sp + 1);
            } else if (longopt && (longoptarg.get() != null)) {
                /*
                 * The option argument was explicitly set to the empty
                 * string on the command line (--option=)
                 */
                optind++;
                optarg = longoptarg.get();
            } else if (++optind >= argc) {
                err("%s: option requires an argument -- %s", c,
                        (longopt ? args[optind - 1] : null));
                _sp = 1;
                optarg = null;
                return (optstring.charAt(0) == ':' ? ':' : '?');
            } else
                optarg = args[optind++];
                _sp = 1;
            } else {
                // The option does NOT take an argument
                if (longopt && (longoptarg.get() != null)) {
                // User supplied an arg to an option that takes none
                err("%s: option doesn't take an argument -- %s", (char)0,
                        (longopt ? args[optind] : null));
                optarg = longoptarg.set(null).get();
                c = '?';
            }

            if (longopt || args[optind].length() == ++_sp) {
                _sp = 1;
                ++optind;
            }
            optarg = null;
        }
        return (c);
    }
}