org.simplx.args.MainArgs.java Source code

Java tutorial

Introduction

Here is the source code for org.simplx.args.MainArgs.java

Source

/*
 * Copyright (c) 2009, 2010, Ken Arnold All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the myself nor the names of its contributors may be used
 * to endorse or promote products derived from this software without specific
 * prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * @SimplxCopyright
 */

/*
 * Parts of this code are:
 * Copyright 2002 Sun Microsystems, Inc. All Rights Reserved.
 *
 * The contents of this file are subject to the Sun Community
 * Source License v 3.0/Jini Technology Specific Attachment v1.0
 * (the "License"). You may not use this file except in compliance
 * with the License. You may obtain a copy of the License
 * at http://www.sun.com/jini/ . Software distributed under the
 * License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF
 * ANY KIND, either express or implied. See the License for the
 * specific language governing rights and limitations under the
 * License.
 *
 * The Reference Code is Jini Technology Core Platform code, v
 * 1.2. The Developer of the Reference Code is Sun Microsystems,
 * Inc.
 *
 * Contributor(s): Sun Microsystems, Inc.
 *
 * The contents of this file comply with the Jini Technology Core
 * Platform Compatibility Kit, v 1.2A.
 *
 * Tester(s): Sun Microsystems, Inc.
 * Java 2 SDK, Standard Edition, Version 1.4.0 and Version 1.3.1_02 for Solaris SPARC/x86
 * Java 2 SDK, Standard Edition, Version 1.4.0 and Version 1.3.1_02 for Linux (Intel x86)
 * Java 2 SDK, Standard Edition, Version 1.4.0 and Version 1.3.1_02 for Microsoft Windows
 *
 */
// Note: The license allows this use  -arnold
package org.simplx.args;

import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.simplx.logging.SimplxLogging;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.RandomAccessFile;
import java.io.Reader;
import java.io.StringWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.Comparator;
import java.util.EnumSet;
import java.util.List;
import java.util.StringTokenizer;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;

/**
 * This class parses a command line that uses multi-character options, such as
 * {@code -verbose} or {@code -help}.
 * <p/>
 * To use {@code MainArgs}, create a {@code MainArgs} object with the array of
 * strings you wish to parse (typically the array passed to the program's {@code
 * main} method), and then consume options from it, providing default values in
 * case the option is not specified by the user. When you have consumed all the
 * options, you invoke the {@code MainArgs} object's {@link #getOperands} method
 * to return the remaining operands on the command line. If ``{@code --}'' is
 * specified it is neither an option nor an operand, just a separator between
 * the two lists. The {@link CommandLineException} is used to signal errors in
 * the construction of the strings, that is, a user error, such as specifying a
 * option that takes an argument but forgetting to provide that argument.
 * <p/>
 * Here is an example:
 * <pre>
 * public static void main(String[] args) throws IOException {
 *      MainArgs line = new MainArgs("flicker", args);
 *      try {
 *          verbose = line.getBoolean("verbose");
 *          max = line.getInt("max", Integer.MAX_VALUE);
 *          Writer out = line.getWriter("out", (Writer) null);
 *          if (out != null)
 *              recordOut = new PrintWriter(out);
 *          String[] files = line.getOperands();
 *          for (String file : files) {
 *              flick(file);
 *          }
 *      } catch (HelpOnlyException e) {
 *          System.exit(0);
 *      } finally {
 *          if (recordOut != null)
 *              recordOut.close();
 *      }
 * }
 * </pre>
 * This program has three possible options: <ul> <li>"verbose", which is a
 * boolean that says whether to generate extra output. The field {@code verbose}
 * will store whether this option was specified or not. <li>"max", which is the
 * maximum number of lines to process in each file. If no maximum is specified,
 * then the maximum will be the largest possible integer, which in effect means
 * "no maximum". The field {@code max} will store this value. <li>"out", which
 * names a file that will be used to record what happens. The field {@code
 * recordOut} will be {@code null} if this is not specified, or a {@code
 * PrintWriter} to that file if it is. </ul> After any options, the arguments
 * will contain files that need to be processed.
 * <p/>
 * So here is a possible invocation of the program:
 * <pre>
 * flicker -verbose -out history f1 f2 f3
 * </pre>
 * In this case, after processing the arguments, the {@code verbose} field will
 * be {@code true}, there will be no maximum, and records will be made to the
 * file "history".  The operation will be run on three files: f1, f2, and f3.
 * <p/>
 * The following invocation will print out the usage:
 * <pre>
 * flicker -help
 * </pre>
 * This will print
 * <pre>
 * flicker [-verbose] [-max int] [-out file] [-help] file ...
 * </pre>
 * <p/>
 * You must call {@link #getOperands} for proper behavior, even if you do not
 * use any operands in your command. {@code getOperands} checks for several user
 * errors, including unknown options. If you do not expect to use operands, you
 * should check the return value of {@code getOperands} and complain if any are
 * specified.
 * <p/>
 * The order that the options are gathered by "get" calls is not relevant.  You
 * can order them in any way that makes sense to you.
 * <p/>
 * The order that the user puts the options in is only relevant when the same
 * option is fetched more than once.  In this case, the first invocation will
 * return the first value for the option, the second the second value, and so
 * on.  If you ask for the value of the option more times than the user provided
 * it, your extra requests will act as if the option is not specified.  (After
 * all, it wasn't specified the third, fourth, and fifth times, for example.)
 * You can use this to gather options in a loop, such as:
 * <p/>
 * In other words, "-verbose -out file" and "-out file -verbose" are the same.
 * But "-user pat -user robin" and "-user robin -user pat" are not.
 * <p/>
 * You can use multiple invocations to set a verbosity level:
 * <pre>
 *    MainArgs line = new MainArgs(args);
 *    int verbosity = 0;
 *    while (line.getBoolean("verbose"))
 *        verbosity++;
 * </pre>
 * or to collect a list of specifications:
 * <pre>
 *    MainArgs line = new MainArgs(args);
 *    List<String> users = new ArrayList<String>();
 *    String user;
 *    while ((user = line.getString("user", null)) != null)
 *        users.add(user);
 * </pre>
 * No options can be consumed after {@code getOperands} is invoked. Failure to
 * follow this rule is a programmer error that will result in an {@link
 * IllegalStateException}.
 * <p/>
 * {@code MainArgs} provides you several methods to get I/O streams from the
 * command line. If these do not suffice for your particular needs, you can get
 * the argument as a {@code String} and do your own processing.
 * <p/>
 * <h3>Combined Multiple and Single Charater Options</h3>
 * <p/>
 * Many programs want to allow the user to specify single character shortcuts
 * for the most common options.  This class handles that by allowing the option
 * specification to have both, separated by a {@code |} character.  For example,
 * <tt>getBoolean("v|verbose")</tt> means that the verbose option can be
 * specified as either {@code -v} or {@code --verbose}.  (In such commands,
 * {@code "--"} is used for the multi-char version of the option.)
 * <p/>
 * If an option does not have a single-character version, you can simply leave
 * out that part of the option specification: {@code "|version"} means that the
 * option {@code --version} has no single character equivalent.  Similary,
 * {@code "v|"} means that there is no multi-character version of the {@code -v}
 * option.  (Single-character-only options are painful to the user and
 * unnecessary, because there is an unlimited number of choices for
 * multi-character equivalents.)
 * <p/>
 * Single character options can be combined in a shorter form.  If the command
 * has single character options {@code x}, {@code y}, and {@code z}, you can
 * specify all three together, as in {@code -xyz}, which is equivalent to {@code
 * -x -y -z}.  If an option takes a parameter, you can merge it in as well.  If
 * the command also had a {@code o} option for an output file, you could say
 * {@code -xyzofile}, {@code -xyzo file}, {@code -xyz -ofile}, or {@code -xyz -o
 * file}.
 * <p/>
 * <h3>Usage Descriptions</h3>
 * <p/>
 * When you ask for the value of options, the object builds up knowledge of the
 * expected usage.  For example, when you call <tt>getBoolean("verbose")</tt>,
 * the class knows there is a boolean option named "verbose".  From this kind of
 * information, you can get a usage message for the user.
 *
 * @author Ken Arnold
 * @see StringTokenizer
 */
public class MainArgs {
    private static final Logger logger = SimplxLogging.loggerFor(MainArgs.class);

    /** The args provided. */
    private final String[] args;

    /** The arguments ones have been used. */
    private final BitSet used;

    /** The list of known options for the usage message. */
    private final List<Opt> options;

    /** The program name (if specified). */
    private final String programName;

    /** This command line specifies single character equivalents. */
    private boolean hasSingles;

    /** The current group name for options. */
    private int curGroup;

    /** The list of option group names. */
    private final List<String> groups;

    /** Description of the overall program. */
    private String[] programDesc = new String[0];

    /** Has some description text. */
    private boolean hasDescs;

    /** The operands have been fetched via getOperands(). */
    private boolean operandsFetched;

    /** The operands description. */
    private String[] operandsDesc;

    /** Whether we should accept enum abbreviations. */
    private boolean abbreviatedEnums = true;

    private int nextOptOrderNum = 0;

    private static final Pattern STRIP_NAME_PATTERN = Pattern.compile("_");

    // I wouldn't do this stateful stuff if I could return more than one
    // value from a method -- it didn't seem worth creating a new object
    // to hold the necessary values on each call to findOpt(). So I've
    // ensured that only one parsing method can be executing at a time
    // and "returned" values via this side effect. YUCK!
    private int foundStr; // found in which string
    private String foundOpt; // which String was found

    /**
     * Creates a new {@link MainArgs} object that will return specified options,
     * arguments, and operands. The program name will be the simple name of the
     * class, that is, the class name with the package name stripped off.
     *
     * @param mainClass The class that has the {@code main} method.
     * @param args      The command line arguments.
     *
     * @see #MainArgs(String,String...)
     */
    public MainArgs(Class mainClass, String... args) {
        this(mainClass.getSimpleName(), args);
    }

    /**
     * Creates a new {@link MainArgs} object that will return specified options,
     * arguments, and operands. The {@code prog} parameter is the program name.
     *
     * @param programName The name to use for the program.
     * @param args        The command line arguments.
     */
    public MainArgs(String programName, String... args) {
        if (logger.isLoggable(Level.FINE)) {
            SimplxLogging.logFormat(logger, Level.FINE, "MainArgs(%s)", programName);
            for (int i = 0; i < args.length; i++) {
                String arg = args[i];
                SimplxLogging.logFormat(logger, Level.FINE, "    %d: %s", i, arg);
            }
        }

        this.programName = programName;
        this.args = args.clone();
        used = new BitSet(args.length);
        options = new ArrayList<Opt>();
        curGroup = 0;
        groups = new ArrayList<String>();
        groups.add("");
    }

    /**
     * Add descriptive text for the program itself.  This will be included in
     * the usage message.
     *
     * @param desc The desriptive text.  Each string will be shown on a line of
     *             its own.
     */
    public void programDescription(String... desc) {
        programDesc = desc.clone();
        hasDescs |= desc.length > 0;
    }

    /** Used to store known option types so we can generate a usage message. */
    private class Opt {
        /** This particular option has no single-char equivalent. */
        static final char HAS_NO_SINGLE = '\uffff';

        /** The option. */
        final String multi;

        /** The single char of it. */
        final char single;

        /** The argument type. */
        final String argType;

        /** Option can be specified more than once. */
        boolean repeatable;

        final String paramName;
        final String[] desc;

        final int group;
        final int order;

        Opt(String option, String argType, String... doc) {
            this.argType = argType;
            int or = option.indexOf('|');
            if (or < 0) {
                single = HAS_NO_SINGLE;
                multi = option;
            } else if (or == 0) {
                single = HAS_NO_SINGLE;
                hasSingles = true; // this has no singles, but singles are specified
                multi = option.substring(1);
            } else if (or == 1) {
                hasSingles = true;
                single = option.charAt(or - 1);
                multi = option.substring(2);
            } else {
                throw new IllegalArgumentException("'|' at illegal position in \"" + option + '"');
            }

            group = curGroup;
            order = nextOptOrderNum++;
            if (argType == null) {
                paramName = "";
                desc = doc.clone();
            } else {
                paramName = nullToEmpty(doc.length > 0 ? doc[0] : argType);
                desc = (String[]) ArrayUtils.subarray(doc, 1, doc.length);
            }
            hasDescs |= desc.length > 0;
        }

        boolean matches(String arg) {
            if (arg.charAt(0) != '-') {
                return false;
            }

            int dashLen = 1;
            if (hasSingles) {
                if (arg.length() == 2 && arg.charAt(1) == single) {
                    return true;
                }
                if (arg.charAt(1) != '-') {
                    return false;
                }
                dashLen = 2;
            }
            return arg.length() - dashLen == multi.length() && arg.regionMatches(dashLen, multi, 0, multi.length());
        }

        String helpString(String prefix) {
            StringBuilder sb = new StringBuilder();

            sb.append(prefix);

            toString(sb);

            if (desc.length > 0) {
                String spaces = prefix + StringUtils.repeat(" ", 16);
                if (sb.length() < spaces.length()) {
                    sb.append(spaces.substring(sb.length()));
                }
                sb.append(desc[0]);
                for (int i = 1; i < desc.length; i++) {
                    sb.append('\n');
                    sb.append(prefix).append(desc[i]);
                }
            }
            sb.append('\n');

            return sb.toString();
        }

        private void toString(StringBuilder sb) {
            if (!hasSingles) {
                sb.append('-').append(multi);
            } else {
                if (single != HAS_NO_SINGLE) {
                    sb.append('-').append(single).append(", ");
                }
                sb.append("--").append(multi);
            }

            if (argType != null && argType.length() != 0) {
                sb.append(' ').append(paramName);
            }
        }

        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder();
            toString(sb);
            return sb.toString();
        }
    }

    private static String nullToEmpty(String s) {
        return s == null ? "" : s;
    }

    /**
     * Sets the current option group.  The option group name is used for usage
     * messages. When you ask for the value of an option, that option is placed
     * in the current option group. In a usage message, options will be shown by
     * group, and within a group, in the order they are requested.  You can set
     * the option group back to one used previously.
     *
     * @param groupName The name of the current option group.
     */
    public void optionGroup(String groupName) {
        curGroup = groups.indexOf(groupName);
        if (curGroup < 0) {
            groups.add(groupName);
            curGroup = groups.size() - 1;
        }
        hasDescs |= groups.size() > 0;
    }

    /**
     * Returns {@code true} if the given option is specified on the command
     * line.
     *
     * @param option The name of the option.
     * @param doc    Usage documentation for the option.  Each string will be
     *               printed on its own line, exept the first which will be put
     *               on the same line as the option itself if it fits.
     *
     * @return {@code true} if the option is specified; otherwise {@code
     *         false}.
     */
    public boolean getBoolean(String option, String... doc) {
        Opt o = addOpt(option, null, doc);
        boolean retval = false;
        if (findOpt(o, option)) {
            retval = true;
        }
        return retval;
    }

    /**
     * Returns the argument for the given option. This is a workhorse routine
     * shared by all the methods that get options with arguments.
     *
     * @param option The name of the option.
     * @param type   The type of the option for the usage message.
     * @param doc    Usage documentation for the option.
     *
     * @return If the option has been specified and is still unused, returns the
     *         argument for the option; otherwise return {@code null}.
     *
     * @throws CommandLineException No argument was present.
     */
    private String getArgument(String option, String type, String... doc) throws CommandLineException {

        Opt o = addOpt(option, type, doc);
        if (findOpt(o, option)) {
            return optArg();
        }
        return null;
    }

    /**
     * Returns the argument of the given string option from the command line. If
     * the option is not specified, return {@code defaultValue}.
     *
     * @param option       The name of the option.
     * @param defaultValue The value to return if the option is not specified.
     * @param doc          Usage documentation for the option.  The first string
     *                     will be the name of the parameter in the usage.  If
     *                     there are any other strings, each string will be
     *                     printed on its own line, exept the first which will
     *                     be put on the same line as the option itself if it
     *                     fits.
     *
     * @return The value for the option, or {@code defaultValue}.
     *
     * @throws CommandLineException No argument was present.
     */
    public String getString(String option, String defaultValue, String... doc) throws CommandLineException {

        String str = getArgument(option, "str", doc);
        return str != null ? str : defaultValue;
    }

    /**
     * Returns the argument of the given {@code int} option from the command
     * line. If the option is not specified, return {@code defaultValue}. The
     * number is parsed according to {@link Integer#parseInt(String)}, except
     * that a leading plus sign is accepted.
     *
     * @param option       The name of the option.
     * @param defaultValue The value to return if the option is not specified.
     * @param doc          Usage documentation for the option (@see {@link
     *                     #getString(String,String,String...) getString}).
     *
     * @return The value for the option, or {@code defaultValue}.
     *
     * @throws NumberFormatException The argument is not a valid number.
     * @throws CommandLineException  No argument was present.
     */
    public int getInt(String option, int defaultValue, String... doc)
            throws CommandLineException, NumberFormatException {

        String str = getArgument(option, "int", doc);
        try {
            if (str == null) {
                return defaultValue;
            }
            // ignore leading plus
            if (str.length() > 0 && str.charAt(0) == '+') {
                str = str.substring(1);
            }
            return Integer.decode(str);
        } catch (NumberFormatException e) {
            throw numException(e, option);
        }
    }

    /**
     * Returns the argument of the given {@code long} option from the command
     * line. If the option is not specified, return {@code defaultValue}. The
     * number is parsed according to {@link Long#decode(String)}, except that a
     * leading plus sign is accepted.
     *
     * @param option       The option specification.
     * @param defaultValue The value to return if the option is not specified.
     * @param doc          Usage documentation for the option (@see {@link
     *                     #getString(String,String,String...)} getString}).
     *
     * @return The value for the option, or {@code defaultValue}.
     *
     * @throws NumberFormatException The argument is not a valid number.
     * @throws CommandLineException  No argument was present.
     */
    public long getLong(String option, long defaultValue, String... doc)
            throws CommandLineException, NumberFormatException {

        String str = getArgument(option, "long", doc);
        try {
            if (str == null) {
                return defaultValue;
            }
            // ignore leading plus
            if (str.length() > 0 && str.charAt(0) == '+') {
                str = str.substring(1);
            }
            return Long.decode(str);
        } catch (NumberFormatException e) {
            throw numException(e, option);
        }
    }

    /**
     * Returns the value of the given {@code double} from the command line. If
     * the option is not specified, return {@code defaultValue}. The number is
     * parsed according to {@link Double#valueOf(String)}.
     *
     * @param option       The option specification.
     * @param defaultValue The value to return if the option is not specified.
     * @param doc          Usage documentation for the option (@see {@link
     *                     #getString(String,String,String...)} getString}).
     *
     * @return The value for the option, or {@code defaultValue}.
     *
     * @throws NumberFormatException The argument is not a valid number.
     * @throws CommandLineException  No argument was present.
     */
    public double getDouble(String option, double defaultValue, String... doc)
            throws CommandLineException, NumberFormatException {

        String str = getArgument(option, "val", doc);
        try {
            if (str == null) {
                return defaultValue;
            }
            return Double.parseDouble(str);
        } catch (NumberFormatException e) {
            throw numException(e, option);
        }
    }

    /**
     * Returns <tt>true</tt> if this object accepts abbreviations for enum
     * values.
     *
     * @return <tt>true</tt> if this object accepts abbreviations for enum
     *         values.
     */
    public boolean isAbbreviatedEnums() {
        return abbreviatedEnums;
    }

    /**
     * Sets whether this object accepts abbreviations for enum values.  By
     * default, this is <tt>true</tt>.
     * <p/>
     * Abbreviations are any string that matches exactly one of the possible
     * values.
     *
     * @param abbreviatedEnums <tt>true</tt> if this object should accept
     *                         abbreviations for enum values.
     */
    public void setAbbreviatedEnums(boolean abbreviatedEnums) {
        this.abbreviatedEnums = abbreviatedEnums;
    }

    /**
     * Returns a value from an enum class.  The enum is the one that contains
     * the default value.  The default value cannot be <tt>null</tt>.  If you
     * need to have a <tt>null</tt> default, use {@link #getEnumValue(String,Enum,Class,String...)}.
     * <p/>
     * See {@link #getEnumValue(String,Enum,Class,String...)} for a description
     * of how the argument is processed.
     *
     * @param option       The option.
     * @param defaultValue The enum value to use if the option is not
     *                     specified.
     * @param doc          Usage documentation for the option (@see {@link
     *                     #getString(String,String,String...)} getString}).
     * @param <T>          The type of the enum; derived from {@code
     *                     defaultValue}.
     *
     * @return The value for the option, or {@code defaultValue}.
     *
     * @see #getEnumValue(String, Enum, Class, String...)
     */
    public <T extends Enum<T>> T getEnumValue(String option, T defaultValue, String... doc) {

        Class<T> enumClass = defaultValue.getDeclaringClass();
        return getEnumValue(option, defaultValue, enumClass, doc);
    }

    /**
     * Returns a value selected from an enum class.  The argument string names
     * an element of the enum.  The string must match one element name in the
     * enum. An abbreviation of any unique starting string is accepted by
     * default. For example, if the enum had elements <tt>MIN</tt>,
     * <tt>AVG</tt>, and <tt>MAX</tt>, the user could use any of <tt>a</tt>,
     * <tt>av</tt>, or <tt>avg</tt> to match <tt>AVG</tt>.  But <tt>m</tt> would
     * cause an error since it could match either <tt>MIN</tt> or <tt>MAX</tt>,
     * whose shortest abbreviations are <tt>mi</tt> and <tt>ma</tt>,
     * respectively.
     * <p/>
     * The string is compared against the enum element names both with and
     * without any underscore they may have.  For example, if {@code GO_HOME} is
     * an enum member, the command-line argument to match it can be {@code
     * GO_HOME}, {@code go_home}, {@code GoHome}, or even {@code gOhOmE}.  (If
     * the enum has two elements whose names differ only by the presence of
     * underscores, the behavior is undefined.)
     * <p/>
     * You can turn off abbreviations, requiring only full enum member names,
     * using {@link #setAbbreviatedEnums(boolean)}.
     *
     * @param option       The option.
     * @param defaultValue The enum value to use if the option is not
     *                     specified.
     * @param enumClass    The class of the enum to be parsed.
     * @param doc          Usage documentation for the option (@see {@link
     *                     #getString(String,String,String...)} getString}).
     * @param <T>          The type of the enum; derived from {@code
     *                     defaultValue}.
     *
     * @return The value for the option, or {@code defaultValue}.
     *
     * @see #setAbbreviatedEnums(boolean)
     */
    public <T extends Enum<T>> T getEnumValue(String option, T defaultValue, Class<T> enumClass, String... doc) {

        String typeName = enumClass.getSimpleName();
        String str = getArgument(option, typeName, doc);
        if (str == null)
            return defaultValue;
        T[] possibles = enumClass.getEnumConstants();
        EnumSet<T> matches = EnumSet.noneOf(enumClass);
        for (T e : possibles) {
            String name = e.toString();
            if (str.equalsIgnoreCase(name))
                return e;
            String stripped = STRIP_NAME_PATTERN.matcher(name).replaceAll("");
            if (str.equalsIgnoreCase(stripped))
                return e;

            if (abbreviatedEnums && (abbreviation(str, name) || abbreviation(str, stripped))) {
                matches.add(e);
            }
        }

        // No exact match, see if it is an abbreviation
        if (abbreviatedEnums) {
            if (matches.size() == 0) {
                throw new CommandLineException(str + " unknown in " + enumClass.getSimpleName());
            }
            if (matches.size() == 1)
                return matches.iterator().next();
            else if (matches.size() > 1) {
                String msg = "Ambiguous option \"" + str + "\": Could be " + matches;
                throw new CommandLineException(msg);
            }
        }

        return defaultValue;
    }

    private static boolean abbreviation(String str, String name) {
        return str.regionMatches(true, 0, name, 0, str.length());
    }

    /**
     * Returns a {@link Writer} that is the result of creating a new {@link
     * FileWriter} object for the file named by the given option. If the option
     * is {@code "-"}, the returned writer writes to {@code System.out}, and is
     * <em>not</em> a {@link FileWriter}. (You can therefore test if it is
     * writing to a file by checking if the returned writer is an instance of
     * {@link FileWriter}.) If the option is not specified, return {@code
     * defaultValue}.
     *
     * @param option       The option specification.
     * @param defaultValue The value to return if the option is not specified.
     * @param doc          Usage documentation for the option (@see {@link
     *                     #getString(String,String,String...)} getString}).
     *
     * @return The value for the option, or {@code defaultValue}.
     *
     * @throws IOException          There was a problem opening the file.
     * @throws CommandLineException No argument was present.
     */
    @SuppressWarnings("UseOfSystemOutOrSystemErr")
    public Writer getWriter(String option, Writer defaultValue, String... doc)
            throws IOException, CommandLineException {

        String path = getArgument(option, "file", doc);
        try {
            if (path == null) {
                return defaultValue;
            }
            if (path.equals("-")) {
                return new OutputStreamWriter(System.out);
            }
            return new FileWriter(path);
        } catch (IOException e) {
            throw ioException(option, path, e);
        }
    }

    /**
     * Returns a {@link Writer} that is the result of creating a new {@link
     * FileWriter} object for the file named by the given option. If the option
     * is {@code "-"}, the returned writer writes to {@code System.out}, and is
     * <em>not</em> a {@link FileWriter}. (You can therefore test if it is
     * writing to a file by checking if the returned writer is an instance of
     * {@link FileWriter}.) If the option is not specified, the string {@code
     * path} is used as the file name. If {@code path} is {@code null} then
     * {@code null} is returned.
     *
     * @param option      The option specification.
     * @param defaultPath The path to use if the option is not specified or
     *                    {@code null} if no reader is to be returned in that
     *                    case.
     * @param doc         Usage documentation for the option (@see {@link
     *                    #getString(String,String,String...)} getString}).
     *
     * @return The value for the option, or the file specified by {@code
     *         defaultPath}.
     *
     * @throws IOException          There was a problem opening the file.
     * @throws CommandLineException No argument was present.
     */
    @SuppressWarnings("UseOfSystemOutOrSystemErr")
    public Writer getWriter(String option, String defaultPath, String... doc)
            throws IOException, CommandLineException {

        String path = getArgument(option, "file", doc);
        try {
            if (path == null) {
                if (defaultPath == null) {
                    return null;
                }
                path = defaultPath;
            }
            if (path.equals("-")) {
                return new OutputStreamWriter(System.out);
            }
            return new FileWriter(path);
        } catch (IOException e) {
            throw ioException(option, path, e);
        }
    }

    /**
     * Returns a {@link Reader} that is the result of creating a new {@link
     * FileReader} object for the file named by the given option. If the option
     * is {@code "-"}, the returned reader reads from {@code System.in}, and is
     * <em>not</em> a {@link FileReader}. (You can therefore test if it is
     * reading from a file by checking if the returned reader is an instance of
     * {@link FileReader}.) If the option is not specified, returns {@code
     * defaultValue}.
     *
     * @param option       The option specification.
     * @param defaultValue The value to return if the option is not specified.
     * @param doc          Usage documentation for the option (@see {@link
     *                     #getString(String,String,String...)} getString}).
     *
     * @return The value for the option, or {@code defaultValue}.
     *
     * @throws IOException          There was a problem opening the file.
     * @throws CommandLineException No argument was present.
     */
    public Reader getReader(String option, Reader defaultValue, String... doc)
            throws IOException, CommandLineException {

        String path = getArgument(option, "file", doc);
        try {
            if (path == null) {
                return defaultValue;
            }
            if (path.equals("-")) {
                return new InputStreamReader(System.in);
            }
            return new FileReader(path);
        } catch (FileNotFoundException e) {
            throw ioException(option, path, e);
        }
    }

    /**
     * Returns a {@link Reader} that is the result of creating a new {@link
     * FileReader} object for the file named by the given option. If the option
     * is {@code "-"}, the returned reader reads from {@code System.in}, and is
     * <em>not</em> a {@link FileReader}. (You can therefore test if it is
     * reading from a file by checking if the returned reader is an instance of
     * {@link FileReader}.) If the option is not specified, the string {@code
     * path} is used as the file name. If {@code path} is {@code null} then
     * {@code null} is returned.
     *
     * @param option      The option specification.
     * @param defaultPath The path to use if the option is not specified or
     *                    {@code null} if no reader is to be returned in that
     *                    case.
     * @param doc         Usage documentation for the option (@see {@link
     *                    #getString(String,String,String...)} getString}).
     *
     * @return The value for the option, or the file specified by {@code
     *         defaultPath}.
     *
     * @throws IOException          There was a problem opening the file.
     * @throws CommandLineException No argument was present.
     */
    public Reader getReader(String option, String defaultPath, String... doc)
            throws IOException, CommandLineException {

        String path = getArgument(option, "file", doc);
        try {
            if (path == null) {
                if (defaultPath == null) {
                    return null;
                }
                path = defaultPath;
            }
            if (path.equals("-")) {
                return new InputStreamReader(System.in);
            }
            return new FileReader(path);
        } catch (FileNotFoundException e) {
            throw ioException(option, path, e);
        }
    }

    /**
     * Returns an {@link OutputStream} that is the result of creating a new
     * {@link FileOutputStream} object for the file named by the given option.
     * If the option is {@code "-"}, the returned stream writes to {@code
     * System.out}, and is <em>not</em> a {@link FileOutputStream}. (You can
     * therefore test if it is writing to a file by checking if the returned
     * stream is an instance of {@link FileOutputStream}.) If the option is not
     * specified, returns {@code defaultValue}.
     *
     * @param option       The option specification.
     * @param defaultValue The value to return if the option is not specified.
     * @param doc          Usage documentation for the option (@see {@link
     *                     #getString(String,String,String...)} getString}).
     *
     * @return The value for the option, or {@code defaultValue}.
     *
     * @throws IOException          There was a problem opening the file.
     * @throws CommandLineException No argument was present.
     */
    @SuppressWarnings("UseOfSystemOutOrSystemErr")
    public OutputStream getOutputStream(String option, OutputStream defaultValue, String... doc)
            throws IOException, CommandLineException {

        String path = getArgument(option, "file", doc);
        try {
            if (path == null) {
                return defaultValue;
            }
            if (path.equals("-")) {
                return System.out;
            }
            return new FileOutputStream(path);
        } catch (FileNotFoundException e) {
            throw ioException(option, path, e);
        }
    }

    /**
     * Returns an {@link OutputStream} that is the result of creating a new
     * {@link FileOutputStream} object for the file named by the given option.
     * If the option is {@code "-"}, the returned stream writes to {@code
     * System.out}, and is <em>not</em> a {@link FileOutputStream}. (You can
     * therefore test if it is writing to a file by checking if the returned
     * stream is an instance of {@link FileOutputStream}.) If the option is not
     * specified, the string {@code path} is used as the file name. If {@code
     * path} is {@code null} then {@code null} is returned.
     *
     * @param option      The option specification.
     * @param defaultPath The path to use if the option is not specified or
     *                    {@code null} if no reader is to be returned in that
     *                    case.
     * @param doc         Usage documentation for the option (@see {@link
     *                    #getString(String,String,String...)} getString}).
     *
     * @return The value for the option, or the file specified by {@code
     *         defaultPath}.
     *
     * @throws IOException          There was a problem opening the file.
     * @throws CommandLineException No argument was present.
     */
    @SuppressWarnings({ "UseOfSystemOutOrSystemErr" })
    public OutputStream getOutputStream(String option, String defaultPath, String... doc)
            throws IOException, CommandLineException {

        String path = getArgument(option, "file", doc);
        try {
            if (path == null) {
                if (defaultPath == null) {
                    return null;
                }
                path = defaultPath;
            }
            if (path.equals("-")) {
                return System.out;
            }
            return new FileOutputStream(path);
        } catch (FileNotFoundException e) {
            throw ioException(option, path, e);
        }
    }

    /**
     * Returns an {@link InputStream} that is the result of creating a new
     * {@link FileInputStream} object for the file named by the given option. If
     * the option is {@code "-"}, the returned stream reads from {@code
     * System.in}, and is <em>not</em> a {@link FileInputStream}. (You can
     * therefore test if it is reading from a file by checking if the returned
     * stream is an instance of {@link FileInputStream}.) If the option is not
     * specified, returns {@code defaultValue}.
     *
     * @param option       The option.
     * @param defaultValue The value to return if the option is not specified.
     * @param doc          Usage documentation for the option (@see {@link
     *                     #getString(String,String,String...)} getString}).
     *
     * @return The value for the option, or {@code defaultValue}.
     *
     * @throws IOException          There was a problem opening the file.
     * @throws CommandLineException No argument was present.
     */
    public InputStream getInputStream(String option, InputStream defaultValue, String... doc)
            throws IOException, CommandLineException {

        String path = getArgument(option, "file", doc);
        try {
            if (path == null) {
                return defaultValue;
            }
            if (path.equals("-")) {
                return System.in;
            }
            return new FileInputStream(path);
        } catch (FileNotFoundException e) {
            throw ioException(option, path, e);
        }
    }

    /**
     * Returns an {@link InputStream} that is the result of creating a new
     * {@link FileInputStream} object for the file named by the given option. If
     * the option is {@code "-"}, the returned stream reads from {@code
     * System.in}, and is <em>not</em> a {@link FileInputStream}. (You can
     * therefore test if it is reading from a file by checking if the returned
     * stream is an instance of {@link FileInputStream}.) If the option is not
     * specified, the string {@code path} is used as the file name. If {@code
     * path} is {@code null} then {@code null} is returned.
     *
     * @param option      The option.
     * @param defaultPath The path to use if the option is not specified.
     * @param doc         Usage documentation for the option (@see {@link
     *                    #getString(String,String,String...)} getString}).
     *
     * @return The value for the option, or the file specified by {@code
     *         defaultPath}.
     *
     * @throws IOException          There was a problem opening the file.
     * @throws CommandLineException No argument was present.
     */
    public InputStream getInputStream(String option, String defaultPath, String... doc)
            throws IOException, CommandLineException {

        String path = getArgument(option, "file", doc);
        try {
            if (path == null) {
                if (defaultPath == null) {
                    return null;
                }
                path = defaultPath;
            }
            if (path.equals("-")) {
                return System.in;
            }
            return new FileInputStream(path);
        } catch (FileNotFoundException e) {
            throw ioException(option, path, e);
        }
    }

    /**
     * Returns a {@link RandomAccessFile} that is the result of creating a new
     * {@link RandomAccessFile} object for the file named by the given option,
     * using the given {@code mode}. If the option is not specified, return
     * {@code defaultValue}.
     *
     * @param option       The option.
     * @param defaultValue The value to return if the option is not specified.
     * @param mode         The mode parameter for {@link RandomAccessFile#RandomAccessFile(String,String)}
     * @param doc          Usage documentation for the option (@see {@link
     *                     #getString(String,String,String...)} getString}).
     *
     * @return The value for the option, or {@code defaultValue}.
     *
     * @throws IOException          There was a problem opening the file.
     * @throws CommandLineException No argument was present.
     */
    public RandomAccessFile getRandomAccessFile(String option, RandomAccessFile defaultValue, String mode,
            String... doc) throws IOException, CommandLineException {

        String path = getArgument(option, "file", doc);
        try {
            if (path == null) {
                return defaultValue;
            }
            return new RandomAccessFile(path, mode);
        } catch (FileNotFoundException e) {
            throw ioException(option, path, e);
        }
    }

    /**
     * Returns a {@link RandomAccessFile} that is the result of creating a new
     * {@link RandomAccessFile} object for the file named by the given option,
     * using the given {@code mode}. If the option is not specified, the string
     * {@code path} is used as the file name. If {@code path} is {@code null}
     * then {@code null} is returned.
     *
     * @param option      The option.
     * @param defaultPath The path to use if the option is not specified.
     * @param mode        The mode parameter for {@link RandomAccessFile#RandomAccessFile(String,String)}
     * @param doc         Usage documentation for the option (@see {@link
     *                    #getString(String,String,String...)} getString}).
     *
     * @return The value for the option, or {@code defaultValue}.
     *
     * @throws IOException          There was a problem opening the file.
     * @throws CommandLineException No argument was present.
     */
    public RandomAccessFile getRandomAccessFile(String option, String defaultPath, String mode, String... doc)
            throws IOException, CommandLineException {

        String path = getArgument(option, "file", doc);
        try {
            if (path == null) {
                if (defaultPath == null) {
                    return null;
                }
                path = defaultPath;
            }
            return new RandomAccessFile(path, mode);
        } catch (FileNotFoundException e) {
            throw ioException(option, path, e);
        }
    }

    /**
     * Returns a directory specified by the user. If the option is not
     * specified, {@code path} is used. If the path is of an existing entity in
     * the file system, it must be a directory. If {@code path} is {@code null}
     * then {@code null} is returned and no directory is created.
     *
     * @param option      The option.
     * @param defaultPath The path to use if the option is not specified.
     * @param doc         Usage documentation for the option (@see {@link
     *                    #getString(String,String,String...)} getString}).
     *
     * @return The value for the option, or {@code defaultValue}.
     *
     * @throws CommandLineException No argument was present.
     */
    public String getDirectory(String option, String defaultPath, String... doc) throws CommandLineException {

        String dir = getArgument(option, "dir", doc);
        return parseDirectory(option, dir, defaultPath, MissingDirAction.NONE);
    }

    /**
     * Returns a directory specified by the user. If the option is not
     * specified, {@code path} is used. If the path is of an existing entity in
     * the file system, it must be a directory.
     * <p/>
     * If the path is for a non-existing directory, the {@code ifMissing}
     * parameter says what to do: <li> {@link MissingDirAction#NONE}: Simply
     * return the path. <li> {@link MissingDirAction#EXCEPTION}: Throw a {@link
     * CommandLineException}. <li> {@link MissingDirAction#CREATE}: Create the
     * directory. If this is not posible, throw a {@link CommandLineException}.
     *
     * @param option      The option.
     * @param defaultPath The value to return if the option is not specified.
     * @param ifMissing   The action to take if the directory is missing.
     * @param doc         Usage documentation for the option (@see {@link
     *                    #getString(String,String,String...)} getString}).
     *
     * @return The value for the option, or {@code defaultValue}.
     *
     * @throws CommandLineException No argument was present.
     */
    public String getDirectory(String option, String defaultPath, MissingDirAction ifMissing, String... doc)
            throws CommandLineException {

        String path = getArgument(option, "dir", doc);
        return parseDirectory(option, path, defaultPath, ifMissing);
    }

    /**
     * Finds the given option somewhere in the command line. If the option is
     * not found, return {@code false}. Otherwise set {@code str}, {@code pos},
     * and {@code opt} fields, mark the option character as used, and then
     * return {@code true}.
     *
     * @param o       The option object.
     * @param lookFor The option.
     *
     * @return {@code true} if the option is specified.
     */
    private boolean findOpt(Opt o, String lookFor) {
        if (o == null) {
            return false;
        }

        for (int i = 0; i < args.length; i++) {
            if (used.get(i)) {
                // already consumed
                continue;
            }

            String arg = args[i];
            if (arg.charAt(0) != '-') {
                // not an option
                continue;
            }
            if (arg.equals("--")) {
                // "--" ends the list
                break;
            }
            if (o.matches(arg)) {
                foundStr = i;
                foundOpt = lookFor;
                used.set(i);
                return true;
            }
        }
        return false;
    }

    /**
     * Return the current option's argument, marking its characters as used.
     *
     * @return The current option's argument.
     *
     * @throws CommandLineException No argument is given.
     */
    private String optArg() throws CommandLineException {
        if (foundStr + 1 >= args.length) {
            String msg = "Argument missing for -" + foundOpt;
            throw new CommandLineException(msg);
        }
        used.set(foundStr + 1);
        return args[foundStr + 1];
    }

    /**
     * Returns the command line operands that come after the options. This
     * checks to make sure that all specified options have been consumed -- any
     * options remaining at this point are assumed to be unknown options. If no
     * operands remain, an empty array is returned.
     * <p/>
     * This is also where {@code -help} is handled. If the user specifies {@code
     * -help}, and that option is not manually process by the code using {@link
     * #getBoolean(String, String...)} getBoolean}, then the method {@link
     * #usage} is invoked and {@link HelpOnlyException} is thrown. The program
     * is expected to catch this exception and simply exit successfully.
     *
     * @param operandsDesc Operand descriptions. In usage messages, this will be
     *                     printed out after the options usage is described.
     *                     Typically this ends with {@code "..."} if an
     *                     arbitrary number of operands can be specified.
     *
     * @return The operands that follow the options.
     *
     * @throws CommandLineException An unknown option was specified.
     * @throws HelpOnlyException    The user asked for usage/help information.
     * @see #synopsis()
     * @see #usage()
     */
    @SuppressWarnings({ "ParameterHidesMemberVariable" })
    public String[] getOperands(String... operandsDesc) throws CommandLineException, HelpOnlyException {

        operandsFetched = true;
        this.operandsDesc = operandsDesc.clone();

        checkForHelp();

        StringBuilder unused = new StringBuilder();
        int count = 0;
        int a;
        for (a = 0; a < args.length; a++) {
            if (used.get(a)) { // skip used parameters
                continue;
            }
            if (!args[a].startsWith("-")) { // first non-option argument
                break;
            }
            if (args[a].equals("--")) { // "--" ends things
                a++; // skip the "--"
                break;
            }
            if (unused.length() > 1) {
                unused.append(' ');
            }
            unused.append(args[a]);
            count++;
        }
        if (unused.length() != 0) {
            String ustr = unused.toString();
            String plural = count > 0 ? "s" : "";
            String msg = "unknown/unused option" + plural + ": " + ustr;
            throw new CommandLineException(msg);
        }

        String[] remains = new String[args.length - a];
        System.arraycopy(args, a, remains, 0, remains.length);
        return remains;
    }

    private void checkForHelp() {
        // see if the user has already checked for and handled "help"
        for (Opt o : options) {
            if (o.multi.equals("help")) {
                return;
            }
        }

        // They haven't, so we will check
        if (groups.size() > 1) {
            optionGroup("Help");
        }

        boolean wantsHelp = hasDescs ? getBoolean("help", "Print help message") : getBoolean("help");
        if (wantsHelp) {
            usage();
            throw new HelpOnlyException();
        }
    }

    /**
     * Adds the given option of the given type to the list of known options;
     * {@code -help} is handled separately in {@link #getOperands}.
     *
     * @param opt     The option to add as known.
     * @param optType The type of option.
     * @param doc     Usage documentation for the option.
     *
     * @return The option object.
     *
     * @see #getOperands(String...)
     * @see #usage()
     */
    private Opt addOpt(String opt, String optType, String... doc) {
        // ensure this is a new, not a redundant, option.
        for (Opt o : options) {
            if (o.multi.equals(opt)) {
                o.repeatable = true;
                return o; // already known
            }
        }

        Opt o = new Opt(opt, optType, doc);
        options.add(o);
        return o;
    }

    /**
     * Prints out the command's usage, inferred from the requested options. You
     * can override this to provide a more specific summary, or you can handle
     * "help" as a boolean option yourself. This implementation is only valid
     * after all known options have been requested and {@link #getOperands} has
     * been called.
     *
     * @param out Stream for the usage message.
     *
     * @see #getOperands
     * @see #synopsis
     */
    public void usage(PrintWriter out) {
        Opt[] opts = doSynopsis(out);

        // If there are no descriptive texts, just return
        if (!hasDescs) {
            return;
        }

        for (String desc : programDesc) {
            out.println(desc);
        }

        int lastGrp = -1;
        for (Opt opt : opts) {
            if (opt.group != lastGrp) {
                if (lastGrp != -1 || programDesc.length > 0) {
                    out.println();
                }
                String groupName = groups.get(opt.group);
                if (groupName.length() > 0) {
                    out.print(groupName);
                    out.println(" Options:");
                }
                lastGrp = opt.group;
            }
            out.print(opt.helpString("    "));
        }
    }

    /**
     * Prints out a synopsis the command's usage, inferred from the requested
     * options. You can override this to provide a more specific summary, or you
     * can handle "help" as a boolean option yourself. This implementation is
     * only valid after all known options have been requested and {@link
     * #getOperands} has been called.
     *
     * @param out Stream for the usage message.
     *
     * @see #getOperands
     */
    public void synopsis(PrintWriter out) {
        doSynopsis(out);
    }

    private Opt[] doSynopsis(PrintWriter out) {
        if (!operandsFetched) {
            throw new IllegalStateException("must call getOperands() before asking for usage");
        }

        // Order the options
        Opt[] opts = options.toArray(new Opt[options.size()]);
        Arrays.sort(opts, new Comparator<Opt>() {
            @Override
            public int compare(Opt o1, Opt o2) {
                if (o1.group != o2.group) {
                    return o1.group - o2.group;
                }
                return o1.order - o2.order;
            }
        });

        if (programName != null) {
            out.print(programName);
        }

        for (Opt opt : opts) {
            out.print(" [");
            out.print(opt);
            out.print("]");
        }
        for (String desc : operandsDesc) {
            out.print(' ');
            out.print(desc);
        }
        out.println();
        return opts;
    }

    /**
     * Prints out the command's usage on {@code System.out}. Equivalent to
     * <pre>
     * usage(System.out, "...");
     * </pre>
     *
     * @see #usage(PrintWriter)
     */
    @SuppressWarnings({ "UseOfSystemOutOrSystemErr" })
    public void usage() {
        System.out.flush();
        PrintWriter pout = new PrintWriter(System.out);
        usage(pout);
        pout.flush();
    }

    /**
     * Prints out a synposis of the command's usage on {@code System.out}.
     *
     * @see #usage(PrintWriter)
     */
    @SuppressWarnings({ "UseOfSystemOutOrSystemErr" })
    public void synopsis() {
        System.out.flush();
        PrintWriter pout = new PrintWriter(System.out);
        synopsis(pout);
        pout.flush();
    }

    /**
     * Returns the string that would be printed by {@link #usage(PrintWriter)}.
     *
     * @return The string that would be printed by {@link #usage(PrintWriter)}.
     *
     * @see #usage(PrintWriter)
     */
    public String usageString() {
        StringWriter out = new StringWriter();
        PrintWriter pout = new PrintWriter(out);
        usage(pout);
        pout.close();
        return out.toString();
    }

    /**
     * Returns the string that would be printed by {@link
     * #synopsis(PrintWriter)}.
     *
     * @return The string that would be printed by {@link #synopsis(PrintWriter)}.
     *
     * @see #synopsis(PrintWriter)
     */
    public String synopsisString() {
        StringWriter out = new StringWriter();
        PrintWriter pout = new PrintWriter(out);
        synopsis(pout);
        pout.close();
        return out.toString();
    }

    /**
     * Returns the result of parsing the given directory from the command line.
     * If {@code path} is {@code null} return {@code defaultPath}. If the path
     * is of an existing entity in the file system, it must be a directory. If
     * {@code defaultPath} is also {@code null}, this returns {@code null}.
     * <p/>
     * If the path is for a non-existing directory, the {@code ifMissing}
     * parameter says what to do: <li> {@link MissingDirAction#NONE}: Simply
     * return the path. <li> {@link MissingDirAction#EXCEPTION}: Throw a {@link
     * CommandLineException}. <li> {@link MissingDirAction#CREATE}: Create the
     * directory. If this is not posible, throw a {@link CommandLineException}.
     *
     * @param opt         The option this is being done for.
     * @param path        The path to parse.
     * @param defaultPath The path to use if {@code str} is {@code null}.
     * @param ifMissing   What to do if the directory does not exist.
     *
     * @return The final path.
     *
     * @throws CommandLineException The path exists already, but is not a
     *                              directory.
     */
    private static String parseDirectory(String opt, String path, String defaultPath, MissingDirAction ifMissing)
            throws CommandLineException {

        if (path == null) {
            if (defaultPath == null) {
                return null;
            }
            path = defaultPath;
        }

        File dir = new File(path);
        if (dir.exists()) {
            if (dir.isDirectory()) {
                return path;
            }
            throw new CommandLineException("Exists, but is not a directory: " + path);
        }

        switch (ifMissing) {
        case NONE:
            break;
        case EXCEPTION:
            throw new CommandLineException("No such directory: " + path);
        case CREATE:
            try {
                mkdirs(path);
            } catch (IOException e) {
                //noinspection ThrowInsideCatchBlockWhichIgnoresCaughtException
                throw new CommandLineException("-" + opt + " " + e.getMessage());
            }
            break;
        }
        return path;
    }

    /**
     * Creates the given directory if needed, including any intermediate missing
     * directories.
     *
     * @param path The path for the directory.
     *
     * @throws IOException The path already exists as a file or {@link
     *                     File#mkdirs} returns {@code false}.
     */
    private static void mkdirs(String path) throws IOException {
        File dir = new File(path);
        if (dir.isDirectory()) {
            return;
        }

        if (dir.exists() && !dir.isDirectory()) {
            throw new IOException("mkdirs: " + dir + " exists but is not a directory");
        }

        if (!dir.mkdirs()) {
            throw new IOException("mkdirs: " + dir + " Cannot create directory");
        }
    }

    @SuppressWarnings({ "TypeMayBeWeakened" })
    private static NumberFormatException numException(NumberFormatException e, String option) {

        NumberFormatException ne = new NumberFormatException("-" + option + " " + e.getMessage());
        ne.initCause(e);
        return ne;
    }

    @SuppressWarnings({ "TypeMayBeWeakened" })
    private static IOException ioException(String opt, String path, IOException e) {

        IOException ne = new IOException("-" + opt + " " + path);
        ne.initCause(e);
        return ne;
    }
}