All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.codetaco.cli.impl.CmdLineImpl Maven / Gradle / Ivy

There is a newer version: 6.10.4
Show newest version
package com.codetaco.cli.impl;

import com.codetaco.cli.CliException;
import com.codetaco.cli.annotation.Arg;
import com.codetaco.cli.annotation.ArgCallback;
import com.codetaco.cli.annotation.Args;
import com.codetaco.cli.impl.criteria.ICmdLineArgCriteria;
import com.codetaco.cli.impl.directive.EquDirective;
import com.codetaco.cli.impl.input.Token;
import com.codetaco.cli.impl.parser.CommandLineParser;
import com.codetaco.cli.impl.parser.IParserInput;
import com.codetaco.cli.impl.parser.NamespaceParser;
import com.codetaco.cli.impl.parser.XmlParser;
import com.codetaco.cli.impl.type.BooleanCLA;
import com.codetaco.cli.impl.type.CLAFactory;
import com.codetaco.cli.impl.type.ClaType;
import com.codetaco.cli.impl.type.CmdLineCLA;
import com.codetaco.cli.impl.type.DefaultCLA;
import com.codetaco.cli.impl.type.ICmdLineArg;
import com.codetaco.cli.impl.usage.UsageBuilder;
import com.codetaco.cli.impl.variables.VariableAssigner;
import com.codetaco.cli.impl.variables.VariablePuller;
import com.codetaco.math.Equ;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Pattern;

public final class CmdLineImpl implements ICmdLine, Cloneable {
    static public ClassLoader ClassLoader = CmdLineImpl.class.getClassLoader();

    private final static Logger logger = LoggerFactory.getLogger(CmdLineImpl.class);
    public static final String INCLUDE_FILE_PREFIX = "@";
    private static final String MaxHelpCommandName = "help";
    private static final char MinHelpCommandName = '?';
    private static final char NegateCommandName = '!';

    static private void checkForUnusedInput(Token[] tokens) {
        if (tokenCount(tokens) > 0) {
            StringBuilder extraInput = new StringBuilder();
            for (Token token : tokens) {
                if (token.isUsed()) {
                    continue;
                }
                extraInput.append(token.getValue());
            }
            throw CliException.builder().cause(new ParseException("extraneous input is not valid: " + extraInput.toString(), 0)).build();
        }
    }

    static public String format(String format,
                                Object... args) {
        String result = format;
        for (Object arg : args) {
            result = result.replaceFirst("\\{\\}", arg.toString());
        }
        return result;
    }

    public void load(Object target,
                     String... args) {
        CmdLineImpl.ClassLoader = target.getClass().getClassLoader();
        IParserInput data = CommandLineParser.getInstance(getCommandPrefix(),
                                                                true,
                                                                args);
        parse(data, target);
    }

    public void loadProperties(Object target,
                               File propertyFile) {
        try {
            CmdLineImpl.ClassLoader = target.getClass().getClassLoader();
            IParserInput data = NamespaceParser.getInstance(propertyFile);
            parse(data, target);
        } catch (Exception e) {
            throw CliException.builder().cause(e).build();
        }
    }

    public void loadProperties(Object target,
                               String... args) {
        CmdLineImpl.ClassLoader = target.getClass().getClassLoader();
        IParserInput data = NamespaceParser.getInstance(args);
        parse(data, target);
    }

    public void loadXml(Object target,
                        File propertyFile) {
        try {
            CmdLineImpl.ClassLoader = target.getClass().getClassLoader();
            IParserInput data = XmlParser.getInstance(propertyFile);
            parse(data, target);
        } catch (Exception e) {
            throw CliException.builder().cause(e).build();
        }
    }

    public void loadXml(Object target,
                        String... args) {
        CmdLineImpl.ClassLoader = target.getClass().getClassLoader();
        IParserInput data = XmlParser.getInstance(args);
        parse(data, target);
    }

    static public int matchingArgs(List> bestArgs,
                                   List> possibleArgs,
                                   Token token,
                                   boolean includeAlreadyParsed) {
        int maxTokenLengthUsed = -1;
        Iterator> aIter = possibleArgs.iterator();
        while (aIter.hasNext()) {
            ICmdLineArg arg = aIter.next();
            if (arg.isParsed() && !includeAlreadyParsed) {
                continue;
            }

            int sal = arg.salience(token);
            if (sal == maxTokenLengthUsed) {
                bestArgs.add(arg);
            }
            if (sal > 0 && sal > maxTokenLengthUsed) {
                maxTokenLengthUsed = sal;
                bestArgs.clear();
                bestArgs.add(arg);
            }
        }
        /*
         * It is a special case if the entered arg is exactly the same length as
         * one of the best args. Typically, more than one best arg is considered
         * an error. But this will remove all but the one that has the exact
         * same length. So no error will occur. Args like "archive" and
         * "archiveAll" will conflict when "arc" is entered but "archive" will
         * return "archive" as the only best arg.
         */

        if (bestArgs.size() > 1) {
            for (ICmdLineArg barg : bestArgs) {
                if (barg.getKeyword().equalsIgnoreCase(token.getWordCommand())) {
                    bestArgs.clear();
                    bestArgs.add(barg);
                    break;
                }
            }
        }

        return maxTokenLengthUsed;
    }

    static private boolean mostSalient(List> possibleArgs,
                                       Token[] tokens,
                                       int tokenIdx,
                                       List> args)
      throws ParseException {
        if (!tokens[tokenIdx].isCommand()) {
            return false;
        }

        List> bestArgs = new ArrayList<>();
        matchingArgs(bestArgs, possibleArgs, tokens[tokenIdx], true);

        if (bestArgs.size() == 0) {
            return false;
        }
        if (bestArgs.size() == 1) {
            ICmdLineArg arg = bestArgs.get(0);
            if (arg != null) {
                args.add(arg);
                arg.setParsed(true);
                if (tokens[tokenIdx].isWordCommand()) {
                    tokens[tokenIdx].setUsed(true);
                    return true;
                }
                // max must be 1 at this point since verbose must match
                // entire tokens
                tokens[tokenIdx].removeCharCommand();
                return true;
            }
            return false;
        }

        Iterator> bIter = bestArgs.iterator();
        StringBuilder bldr = new StringBuilder();
        bldr.append("ambiguous token ");
        bldr.append(tokens[tokenIdx].getValue());
        bldr.append(" matches ");
        while (bIter.hasNext()) {
            bldr.append(bIter.next().getKeyword());
            bldr.append(' ');
        }
        throw new ParseException(bldr.toString(), -1);
    }

    static private int parseGroup(CmdLineCLA group,
                                  Token[] tokens,
                                  int _tokenIndex,
                                  Object target)
      throws ParseException, IOException {
        StringBuilder str = null;

        int tlex = 0;
        int tokenIndex = _tokenIndex;

        for (tokenIndex++; tokenIndex < tokens.length; tokenIndex++) {
            if (tokens[tokenIndex].isUsed()) {
                continue;
            }
            if (tokens[tokenIndex].isGroupStart()) {
                tlex++;
                if (tlex == 1) {
                    tokens[tokenIndex].setUsed(true);
                    str = new StringBuilder();
                    continue;
                }
            }
            if (tokens[tokenIndex].isGroupEnd()) {
                tlex--;
                if (tlex == 0) {
                    tokens[tokenIndex].setUsed(true);
                    group.setValue(group.convert(str.toString(), false, target));
                    continue;
                }
            }
            if (tlex == 0) {
                tokenIndex--; // reuse last token later
                break;
            }

            if (tokens[tokenIndex].isLiteral()) {
                /*
                 * Always quote the value in case it was quoted. It doesn't hurt
                 * to unnecessarily quote. But it would hurt not to quote at
                 * all.
                 */
                String value = tokens[tokenIndex].getValue();

                value = replaceEscapes(value);
                boolean singlequote = value.contains("'");
                boolean doublequote = value.contains("\"");
                char delim;
                if (singlequote) {
                    if (doublequote) {
                        delim = '"';
                        value = value.replace("\"", "\\\"");
                    } else {
                        delim = '"';
                    }
                } else {
                    delim = '\'';
                }
                str.append(delim);
                str.append(value);
                str.append(delim);
                str.append(" ");
            } else {
                str.append(tokens[tokenIndex].getValue());
                str.append(" ");
            }
            tokens[tokenIndex].setUsed(true);
        }
        if (tlex != 0) {
            throw new ParseException("Missing " + tlex + " right bracket(s)", 0);
        }
        validateMultipleEntries(group);
        return tokenIndex;
    }

    static private void parseOrphaned(Token[] tokens) throws ParseException {
        StringBuilder bldr = new StringBuilder();
        for (int t = 0; t < tokens.length; t++) {
            if (!tokens[t].isUsed()) {
                bldr.append(tokens[t].getValue());
                bldr.append(' ');
            }
        }
        if (bldr.length() != 0) {
            throw new ParseException("unexpected input: " + bldr.toString(), -1);
        }
    }

    static private int parseValues(ICmdLineArg arg,
                                   Token[] tokens,
                                   int t)
      throws ParseException {
        int tokenIndex = t;
        /*
         * take remainder of the token if any as parm 1
         */
        boolean aValueWasFound = false;
        if (!tokens[tokenIndex].isUsed()) {
            // skip the dash
            arg.setValue(
              arg.convert(tokens[tokenIndex].remainderValue(), arg.isCaseSensitive(), null));
            tokens[tokenIndex].setUsed(true);
            aValueWasFound = true;
        }

        /*
         * take any following non-dash parms
         */

        if (!aValueWasFound || arg.isMultiple()) {
            for (tokenIndex++; tokenIndex < tokens.length; tokenIndex++) {
                if (arg.isMultiple() && arg.size() == arg.getMultipleMax()) {
                    tokenIndex--; // make sure to allow reuse of - token
                    break;
                }
                if (tokens[tokenIndex].isUsed()) {
                    continue;
                }
                if (!tokens[tokenIndex].isCommand()) {
                    arg.setValue(arg.convert(tokens[tokenIndex].getValue(), (arg.isCaseSensitive()),
                                             null));
                    tokens[tokenIndex].setUsed(true);
                    if (!arg.isMultiple()) {
                        break;
                    }
                } else {
                    tokenIndex--; // make sure to allow reuse of - token
                    break;
                }
            }
        }

        validateMultipleEntries(arg);

        if (arg.hasValue() && arg.getCriteria() != null) {
            for (int v = 0; v < arg.size(); v++) {
                /*
                 * The user may have entered in a partial value. If the value
                 * can be normalized to something in the criteria then we will
                 * use the normalized value. This pretty much only applies to
                 * lists even though it is implemented on all criteria.
                 */
                arg.setValue(v,
                             arg.getCriteria().normalizeValue(arg.getValue(v), arg.isCaseSensitive()));
                if (!arg.getCriteria().isSelected((Comparable) arg.getValue(v),
                                                  arg.isCaseSensitive())) {
                    throw new ParseException(arg.getValue(v) + " is not valid for " + arg,
                                             -1);
                }
            }
        }
        return tokenIndex;
    }

    static private String replaceEscapes(String value) {
        boolean backslash = value.contains("\\");
        if (backslash) {
            return value.replace("\\", "\\\\");
        }
        return value;
    }

    static private int tokenCount(Token[] tokens) {
        int cnt = 0;
        for (int t = 0; t < tokens.length; t++) {
            if (!tokens[t].isUsed()) {
                cnt++;
            }
        }
        return cnt;
    }

    /**
     * Verify the multiple requirement if any. Use group->values().size().
     */
    static private void validateMultipleEntries(ICmdLineArg arg) throws ParseException {
        if (arg.isRequiredValue() && !arg.hasValue()) {
            throw new ParseException("missing a required value for " + arg, -1);
        }
        if (arg.hasValue() && arg.size() > 1 && !arg.isMultiple()) {
            throw new ParseException("multiple values not allowed for " + arg, -1);
        }

        if (arg.hasValue() && arg.isMultiple()) {
            if (arg.size() < arg.getMultipleMin()) {
                throw new ParseException("insufficient required values for " + arg, -1);
            }
            if (arg.size() > arg.getMultipleMax()) {
                throw new ParseException("excessive required values for " + arg, -1);
            }
        }
    }

    /**
     * Set by the factory when this is created.
     */
    private int uniqueId;

    private String name;
    private String help;
    private char commandPrefix;
    private char notPrefix;
    private final List defaultIncludeDirectories = new ArrayList<>();

    private IParserInput originalInput;
    private List> allPossibleArgs = new ArrayList<>();
    private List> _namedBooleans = null;
    private List> _namedValueArgs = null;
    private List> _namedGroups = null;
    private List> _positional = null;
    private List parseExceptions = new ArrayList<>();

    private int depth;

    public CmdLineImpl(String _name,
                       String _help,
                       char _commandPrefix,
                       char _notPrefix) {
        super();

        depth = 0;

        commandPrefix = _commandPrefix;
        notPrefix = _notPrefix;
        setName(_name);

        if (_help != null) {
            setHelp(_help);
        }

        /*-
        final String defaultHelp = INCLUDE_FILE_PREFIX
            +
            " will import a specification from filename. You can return an cli to its default value with "
            + commandPrefix
            + notPrefix
            + " ; eg: "
            + commandPrefix
            + "debug and "
            + commandPrefix
            + "help can be turned off with "
            + commandPrefix
            + notPrefix
            + "debug,help";
        if (help == null)
        setHelp(defaultHelp + "\n");
        else
        setHelp(help + "\n\n" + defaultHelp + "\n");
         */
    }

    @Override
    public void add(ICmdLineArg arg) {
        allPossibleArgs.add(arg);
    }

    @Override
    public void add(int index,
                    ICmdLineArg arg) {
        allPossibleArgs.add(index, arg);
    }

    @Override
    public void addDefaultIncludeDirectory(File defaultIncludeDirectory) {
        defaultIncludeDirectories.add(defaultIncludeDirectory);
    }

    @Override
    public List> allArgs() {
        return allPossibleArgs;
    }

    private List allAvailableInstanceFields(Class targetClass) {
        List fields = new ArrayList<>();
        allAvailableInstanceFields(targetClass, fields);
        return fields;
    }

    private void allAvailableInstanceFields(Class targetClass,
                                            List fields) {
        for (Field field : targetClass.getDeclaredFields()) {
            if (field.isAnnotationPresent(Arg.class) || field.isAnnotationPresent(Args.class)) {
                fields.add(field);
            }
        }
        Class superclass = targetClass.getSuperclass();
        if (superclass != null && superclass != Object.class)
            /*
             * Recursive from here, up the hierarchy of classes all the way to
             * the top.
             */ {
            allAvailableInstanceFields(targetClass.getSuperclass(), fields);
        }
    }

    @Override
    public void applyDefaults() {
        Iterator> aIter = allPossibleArgs.iterator();
        while (aIter.hasNext()) {
            ICmdLineArg arg = aIter.next();
            if (arg instanceof DefaultCLA) {
                ((DefaultCLA) arg).applyDefaults(commandPrefix, allPossibleArgs);
            } else {
                arg.applyDefaults();
            }
        }
    }

    @Override
    public ICmdLineArg arg(String commandToken) {
        if (commandToken == null) {
            return null;
        }

        List> bestArgs = new ArrayList<>();
        matchingArgs(bestArgs, allPossibleArgs, new Token(commandPrefix, commandToken), true);

        if (bestArgs.size() == 0)
        // throw new ParseException(commandToken + " is unknown", -1);
        {
            return null;
        }
        if (bestArgs.size() > 1)
        // throw new ParseException(commandToken + " is ambiguous", -1);
        {
            return null;
        }
        return bestArgs.get(0);
    }

    @Override
    public ICmdLineArg argForVariableName(String variableName) {
        if (variableName == null) {
            return null;
        }
        for (ICmdLineArg arg : allPossibleArgs) {
            if (arg.getVariable() != null) {
                if (arg.getVariable().equals(variableName)) {
                    return arg;
                }
            }
        }
        throw CliException.builder().cause(new ParseException(variableName + " not found or not annotated with @Arg", 0)).build();
    }

    @Override
    public void asDefinedType(StringBuilder sb) {
        // should not be called.
    }

    @Override
    public Object asEnum(String _name,
                         Object[] _possibleConstants) {
        // should not be called.
        throw CliException.builder().cause(new ParseException("invalid to store " + toString() + " in an Enum", 0)).build();
    }

    @Override
    public Object[] asEnumArray(String _name,
                                Object[] _possibleConstants) {
        // should not be called.
        throw CliException.builder().cause(new ParseException("invalid to store " + toString() + " in an Enum[]", 0)).build();
    }

    @Override
    public void assignVariables(Object target) {
        try {
            Iterator> aIter = allPossibleArgs.iterator();
            while (aIter.hasNext()) {
                ICmdLineArg arg = aIter.next();
                if (arg instanceof CmdLineCLA) {
                    CmdLineCLA cmdArg = (CmdLineCLA) arg;
                    for (ICmdLine cl : cmdArg.getValues()) {
                        Object newtarget = VariableAssigner.getInstance()
                                             .newGroupVariable(cmdArg, target,
                                                               cl.argForVariableName(cmdArg.getFactoryArgName()));
                        if (newtarget == null) {
                            newtarget = target;
                        }
                        cl.assignVariables(newtarget);
                    }
                } else if (arg.getVariable() != null && arg.hasValue()) {
                    if (target instanceof Object[]) {
                        Object[] targetArray = (Object[]) target;
                        VariableAssigner.getInstance().assign(arg, targetArray[targetArray.length - 1]);
                    } else if (target instanceof List) {
                        VariableAssigner.getInstance().assign(arg,
                                                              ((List) target).get(((List) target).size() - 1));
                    } else {
                        VariableAssigner.getInstance().assign(arg, target);
                    }
                }
            }
            checkForAnnotationPostParseCallback(target);
        } catch (Exception e) {
            throw CliException.builder().cause(e).build();
        }
    }

    void checkForAnnotationPostParseCallback(Object target) throws ParseException {
        for (Method method : target.getClass().getDeclaredMethods()) {
            if (method.isAnnotationPresent(ArgCallback.class)) {
                try {
                    ArgCallback cb = method.getAnnotation(ArgCallback.class);
                    if (cb.postParse()) {
                        method.setAccessible(true);
                        method.invoke(target);
                    }
                } catch (IllegalAccessException | IllegalArgumentException e) {
                    throw new ParseException(e.getMessage(), 0);
                } catch (InvocationTargetException e) {
                    throw (ParseException) e.getTargetException();
                }
            }
        }
    }

    void attemptAnnotationCompile(Class targetClass,
                                  boolean topLevel,
                                  List> alreadySeen,
                                  String[] excludeArgsByVariableName) throws ParseException, IOException {
        /*
         * Recursive needs to be allowed. Not sure how to stop incorrect
         * recursion other than to let the resulting stack overflow occur.
         */

        /*-
        if (alreadySeen.contains(targetClass))
        throw new ParseException("recursive cli definition at " +
                targetClass.toString(), 0);
         */

        alreadySeen.add(targetClass);

        try {
            for (Field oneField : allAvailableInstanceFields(targetClass)) {
                if (isFieldExcluded(oneField, excludeArgsByVariableName)) {
                    continue;
                }
                Args args = oneField.getAnnotation(Args.class);
                if (args == null) {
                    Arg argAnnotation = oneField.getAnnotation(Arg.class);
                    compileArgAnnotation(oneField, argAnnotation, alreadySeen,
                                         excludeArgsByVariableName);

                } else {
                    for (Arg argAnnotation : args.value()) {
                        compileArgAnnotation(oneField, argAnnotation, alreadySeen,
                                             excludeArgsByVariableName);
                    }
                }
            }

        } finally {
            alreadySeen.remove(targetClass);
        }
        if (topLevel) {
            createSystemGeneratedArguments(CLAFactory.getInstance(), this);
            parseExceptions = postCompileAnalysis();
            if (!parseExceptions.isEmpty()) {
                for (ParseException pe : parseExceptions) {
                    logger.warn(pe.getMessage());
                }
                if (parseExceptions.size() == 1) {
                    throw parseExceptions.get(0);
                }
                throw new ParseException("multiple parse exceptions", 0);
            }
        }
    }

    private void checkRequired() throws ParseException {
        StringBuilder bldr = new StringBuilder();
        Iterator> aIter = allPossibleArgs.iterator();
        while (aIter.hasNext()) {
            ICmdLineArg arg = aIter.next();
            if (arg.isRequired() && !arg.isParsed()) {
                bldr.append(arg.toString());
                bldr.append(" ");
            }
        }
        if (bldr.length() != 0) {
            throw new ParseException("missing required parameters: " + bldr.toString(), -1);
        }
    }

    @Override
    public ICmdLine clone() throws CloneNotSupportedException {
        CmdLineImpl clone = (CmdLineImpl) super.clone();
        clone.allPossibleArgs = new ArrayList<>();
        Iterator> aIter = allPossibleArgs.iterator();
        while (aIter.hasNext()) {
            clone.allPossibleArgs.add(aIter.next().clone());
        }
        clone.setDepth(getDepth() + 1);
        return clone;
    }

    @Override
    public int compareTo(ICmdLine o) {
        return 0;
    }

    private void compileArgAnnotation(Field oneField,
                                      Arg argAnnotation,
                                      List> alreadySeen,
                                      String[] excludeArgsByVariableName) throws ParseException, IOException {
        ICmdLineArg arg = CLAFactory.getInstance().instanceFor(
          commandPrefix,
          oneField,
          argAnnotation);
        if (!argAnnotation.variable().equals("")) {
            /*
             * This actually annotating an embedded class, probably because it
             * can not be modified. The previous annotation will be the
             * subparser that this cli should be added to.
             */
            CmdLineCLA subparser = (CmdLineCLA) argForVariableName(oneField.getName());
            if (subparser == null) {
                throw new ParseException("invalid variable reference: " + argAnnotation.variable(),
                                         0);
            }
            subparser.templateCmdLine.add(arg);
            return;
        }
        add(arg);
        if (arg instanceof CmdLineCLA) {
            CmdLineImpl embedded = new CmdLineImpl(arg.getKeyword() == null
                                                           ? ("" + arg.getKeychar())
                                                           : ("" + arg.getKeychar() + "," + arg.getKeyword()),
                                                         "",
                                                         commandPrefix,
                                                         notPrefix);
            ((CmdLineCLA) arg).templateCmdLine = embedded;
            Class embeddedTarget;
            try {
                if (arg.getInstanceClass() != null) {
                    embeddedTarget = CmdLineImpl.ClassLoader
                                       .loadClass(arg.getInstanceClass());
                } else {
                    embeddedTarget = CLAFactory.instanceType(oneField);
                }
            } catch (ClassNotFoundException e) {
                throw new ParseException(e.getMessage(), 0);
            }
            embedded.attemptAnnotationCompile(embeddedTarget, false, alreadySeen,
                                              argAnnotation.excludeArgs());
        }
    }

    @Override
    public ICmdLine convert(String valueStr) {
        /*
         * This is only here as a place-holder so that this class can be a sub
         * command line as well as a top level.
         */
        return null;
    }

    @Override
    public ICmdLine convert(String valueStr,
                            boolean caseSensitive,
                            Object target) {
        /*
         * This is only here as a place-holder so that this class can be a sub
         * command line as well as a top level.
         */
        return null;
    }

    void createSystemGeneratedArguments(CLAFactory factory,
                                        ICmdLine cmdline) throws ParseException {
        ICmdLineArg sysgen;

        sysgen = ClaType.DEFAULT.argumentInstance(commandPrefix, notPrefix, null);
        sysgen.setMultiple(1);
        sysgen.setHelp("Return one or more arguments to their initial states.");
        sysgen.setSystemGenerated(true);
        cmdline.add(sysgen);

        sysgen = ClaType.BOOLEAN.argumentInstance(commandPrefix, MinHelpCommandName, null);
        sysgen.setHelp("Show a help message.");
        sysgen.setSystemGenerated(true);
        cmdline.add(sysgen);
        sysgen = ClaType.BOOLEAN.argumentInstance(commandPrefix, commandPrefix, MaxHelpCommandName);
        sysgen.setHelp("Show a very abbreviated help message.");
        sysgen.setSystemGenerated(true);
        cmdline.add(sysgen);
        sysgen = ClaType.BOOLEAN.argumentInstance(commandPrefix, commandPrefix,
                                                  MaxHelpCommandName + ":1");
        sysgen.setHelp("Show a help message.");
        sysgen.setSystemGenerated(true);
        cmdline.add(sysgen);
        sysgen = ClaType.BOOLEAN.argumentInstance(commandPrefix, commandPrefix,
                                                  MaxHelpCommandName + ":2");
        sysgen.setHelp("Show a brief description with the help message.");
        sysgen.setSystemGenerated(true);
        cmdline.add(sysgen);
        sysgen = ClaType.BOOLEAN.argumentInstance(commandPrefix, commandPrefix,
                                                  MaxHelpCommandName + ":3");
        sysgen.setHelp("Show the most detailed help message.");
        sysgen.setSystemGenerated(true);
        cmdline.add(sysgen);
    }

    private void crossCheck() {
        /*
         * not yet, but probably needs a new criteria class
         */
    }

    @Override
    public String defaultInstanceClass() {
        return null;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        CmdLineImpl other = (CmdLineImpl) obj;
        if (name == null) {
            return other.name == null;
        } else {
            return name.equals(other.name);
        }
    }

    @Override
    public void exportCommandLine(StringBuilder str) {
        /*
         * Making sure that anything the user may have changed still gets the
         * default if necessary.
         */
        applyDefaults();
        CommandLineParser.unparseTokens(allPossibleArgs, str);
    }

    @Override
    public void exportNamespace(String prefix,
                                StringBuilder out) {
        /*
         * Making sure that anything the user may have changed still gets the
         * default if necessary.
         */
        applyDefaults();
        NamespaceParser.unparseTokens(prefix, allPossibleArgs, out);
    }

    @Override
    public void exportXml(String tag,
                          StringBuilder out) {
        if (tag != null && tag.length() > 0) {
            out.append("<").append(tag).append(">");
        }
        applyDefaults();
        XmlParser.unparseTokens(allPossibleArgs, out);
        if (tag != null && tag.length() > 0) {
            out.append("");
        }
    }

    private void extractArgumentsFromTokens(Token[] tokens,
                                            Object target,
                                            List> args) throws ParseException, IOException {
        if (tokenCount(tokens) > 0) {
            parseDirectives(args, tokens, target);
        }

        if (tokenCount(tokens) > 0) {
            parseIncludeFiles(args, tokens, target);
        }

        if (tokenCount(tokens) > 0) {
            parseNamedBoolean(args, tokens);
        }

        if (isUsageRun()) {
            return;
        }

        if (tokenCount(tokens) > 0) {
            parseNamedGroups(args, tokens, target);
        }
        if (tokenCount(tokens) > 0) {
            parseNamedValueArgs(args, tokens);
        }
        if (tokenCount(tokens) > 0) {
            parsePositional(args, tokens);
        }
        if (tokenCount(tokens) > 0) {
            parseOrphaned(tokens);
        }
    }

    @Override
    public String genericClassName() {
        return null;
    }

    @Override
    public Pattern getCamelCaps() {
        /*
         * This is only here as a place-holder so that this class can be a sub
         * command line as well as a top level.
         */
        return null;
    }

    @Override
    public char getCommandPrefix() {
        return commandPrefix;
    }

    @Override
    public ICmdLineArgCriteria getCriteria() {
        /*
         * This is only here as a place-holder so that this class can be a sub
         * command line as well as a top level.
         */
        return null;
    }

    public List getDefaultIncludeDirectories() {
        return defaultIncludeDirectories;
    }

    @Override
    public List getDefaultValues() {
        return null;
    }

    @Override
    public Object getDelegateOrValue() {
        /*
         * This is only here as a place-holder so that this class can be a sub
         * command line as well as a top level.
         */
        return null;
    }

    @Override
    public Object getDelegateOrValue(int occurrence) {
        /*
         * This is only here as a place-holder so that this class can be a sub
         * command line as well as a top level.
         */
        return null;
    }

    int getDepth() {
        return depth;
    }

    @Override
    public String getEnumClassName() {
        // should not be called.
        return null;
    }

    @Override
    public String getFactoryArgName() {
        return null;
    }

    @Override
    public String getFactoryMethodName() {
        return null;
    }

    @Override
    public String getFormat() {
        return null;
    }

    @Override
    public String getHelp() {
        return help;
    }

    @Override
    public String getInstanceClass() {
        /*
         * This is only here as a place-holder so that this class can be a sub
         * command line as well as a top level.
         */
        return null;
    }

    @Override
    public Character getKeychar() {
        /*
         * This is only here as a place-holder so that this class can be a sub
         * command line as well as a top level.
         */
        return ' ';
    }

    @Override
    public String getKeyword() {
        /*
         * This is only here as a place-holder so that this class can be a sub
         * command line as well as a top level.
         */
        return null;
    }

    @Override
    public String getMetaphone() {
        /*
         * This is only here as a place-holder so that this class can be a sub
         * command line as well as a top level.
         */
        return null;
    }

    @Override
    public int getMultipleMax() {
        /*
         * This is only here as a place-holder so that this class can be a sub
         * command line as well as a top level.
         */
        return 0;
    }

    @Override
    public int getMultipleMin() {
        /*
         * This is only here as a place-holder so that this class can be a sub
         * command line as well as a top level.
         */
        return 0;
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public List getParseExceptions() {
        return parseExceptions;
    }

    @Override
    public int getUniqueId() {
        return uniqueId;
    }

    @Override
    public ICmdLine getValue() {
        /*
         * This is only here as a place-holder so that this class can be a sub
         * command line as well as a top level.
         */
        return null;
    }

    @Override
    public List getValues() {
        /*
         * This is only here as a place-holder so that this class can be a sub
         * command line as well as a top level.
         */
        return null;
    }

    @Override
    public ICmdLine getValue(int index) {
        /*
         * This is only here as a place-holder so that this class can be a sub
         * command line as well as a top level.
         */
        return null;
    }

    @Override
    public byte[] getValueAsbyteArray() {
        // should not be called.
        throw CliException.builder().cause(new ParseException("invalid to store " + toString() + " in a byte[]", 0)).build();
    }

    @Override
    public Byte[] getValueAsByteArray() {
        // should not be called.
        throw CliException.builder().cause(new ParseException("invalid to store " + toString() + " in a Byte[]", 0)).build();
    }

    @Override
    public Calendar[] getValueAsCalendarArray() {
        // should not be called.
        throw CliException.builder().cause(new ParseException("invalid to store " + toString() + " in a Calendar[]", 0)).build();
    }

    @Override
    public Character[] getValueAsCharacterArray() {
        // should not be called.
        throw CliException.builder().cause(new ParseException("invalid to store " + toString() + " in a Character[]", 0)).build();
    }

    @Override
    public char[] getValueAscharArray() {
        // should not be called.
        throw CliException.builder().cause(new ParseException("invalid to store " + toString() + " in a char[]", 0)).build();
    }

    @Override
    public Date[] getValueAsDateArray() {
        // should not be called.
        throw CliException.builder().cause(new ParseException("invalid to store " + toString() + " in a Date[]", 0)).build();
    }

    @Override
    public ZonedDateTime[] getValueAsZonedDateTimeArray() {
        // should not be called.
        throw CliException.builder().cause(new ParseException("invalid to store " + toString() + " in a ZonedDateTime[]", 0)).build();
    }

    @Override
    public DateTimeFormatter getValueAsDateTimeFormatter() {
        // should not be called.
        throw CliException.builder().cause(new ParseException("invalid to store " + toString() + " in a DateTimeFormatter", 0)).build();
    }

    @Override
    public DateTimeFormatter[] getValueAsDateTimeFormatterArray() {
        // should not be called.
        throw CliException.builder().cause(new ParseException("invalid to store " + toString() + " in a DateTimeFormatter[]", 0)).build();
    }

    @Override
    public double[] getValueAsdoubleArray() {
        // should not be called.
        throw CliException.builder().cause(new ParseException("invalid to store " + toString() + " in a double[]", 0)).build();
    }

    @Override
    public Double[] getValueAsDoubleArray() {
        // should not be called.
        throw CliException.builder().cause(new ParseException("invalid to store " + toString() + " in a Double[]", 0)).build();
    }

    @Override
    public Equ getValueAsEquation() {
        // should not be called.
        throw CliException.builder().cause(new ParseException("invalid to store " + toString() + " in an Equ", 0)).build();
    }

    @Override
    public Equ[] getValueAsEquationArray() {
        // should not be called.
        throw CliException.builder().cause(new ParseException("invalid to store " + toString() + " in a Equ[]", 0)).build();
    }

    @Override
    public File[] getValueAsFileArray() {
        // should not be called.
        throw CliException.builder().cause(new ParseException("invalid to store " + toString() + " in a File[]", 0)).build();
    }

    @Override
    public URL[] getValueAsURLArray() {
        // should not be called.
        throw CliException.builder().cause(new ParseException("invalid to store " + toString() + " in a URL[]", 0)).build();
    }

    @Override
    public float[] getValueAsfloatArray() {
        // should not be called.
        throw CliException.builder().cause(new ParseException("invalid to store " + toString() + " in a float[]", 0)).build();
    }

    @Override
    public Float[] getValueAsFloatArray() {
        // should not be called.
        throw CliException.builder().cause(new ParseException("invalid to store " + toString() + " in a Float[]", 0)).build();
    }

    @Override
    public int[] getValueAsintArray() {
        // should not be called.
        throw CliException.builder().cause(new ParseException("invalid to store " + toString() + " in a int[]", 0)).build();
    }

    @Override
    public Integer[] getValueAsIntegerArray() {
        // should not be called.
        throw CliException.builder().cause(new ParseException("invalid to store " + toString() + " in a Integer[]", 0)).build();
    }

    @Override
    public LocalDate[] getValueAsLocalDateArray() {
        throw CliException.builder().cause(new ParseException("invalid to store " + toString() + " in a LocalDate[]", 0)).build();
    }

    @Override
    public LocalDateTime[] getValueAsLocalDateTimeArray() {
        throw CliException.builder().cause(new ParseException("invalid to store " + toString() + " in a LocalDateTime[]", 0)).build();
    }

    @Override
    public LocalTime[] getValueAsLocalTimeArray() {
        throw CliException.builder().cause(new ParseException("invalid to store " + toString() + " in a LocalTime[]", 0)).build();
    }

    @Override
    public long[] getValueAslongArray() {
        // should not be called.
        throw CliException.builder().cause(new ParseException("invalid to store " + toString() + " in a long[]", 0)).build();
    }

    @Override
    public Long[] getValueAsLongArray() {
        // should not be called.
        throw CliException.builder().cause(new ParseException("invalid to store " + toString() + " in a Long[]", 0)).build();
    }

    @Override
    public Pattern getValueAsPattern() {
        // should not be called.
        throw CliException.builder().cause(new ParseException("invalid to store " + toString() + " in a Pattern", 0)).build();
    }

    @Override
    public Pattern[] getValueAsPatternArray() {
        // should not be called.
        throw CliException.builder().cause(new ParseException("invalid to store " + toString() + " in a Pattern[]", 0)).build();
    }

    @Override
    public SimpleDateFormat getValueAsSimpleDateFormat() {
        throw CliException.builder().cause(new ParseException("invalid to store " + toString() + " in a SimpleDateFormat", 0)).build();
    }

    @Override
    public SimpleDateFormat[] getValueAsSimpleDateFormatArray() {
        throw CliException.builder().cause(new ParseException("invalid to store " + toString() + " in a SimpleDateFormat[]", 0)).build();
    }

    @Override
    public String[] getValueAsStringArray() {
        // should not be called.
        throw CliException.builder().cause(new ParseException("invalid to store " + toString() + " in a String[]", 0)).build();
    }

    @Override
    public String getVariable() {
        /*
         * This is only here as a place-holder so that this class can be a sub
         * command line as well as a top level.
         */
        return null;
    }

    private Token handleDirective(Token[] tokens,
                                  int directiveIdx,
                                  int parmStart,
                                  int parmEnd) throws ParseException, IOException {
        int originalInputStart = tokens[parmStart].getInputStartX();
        int originalInputEnd = tokens[parmEnd].getInputEndX();

        String data = originalInput.substring(originalInputStart, originalInputEnd + 1);

        String directiveName = tokens[directiveIdx].getValue().toLowerCase();
        if ("_".equals(directiveName) || "=".equals(directiveName)) {
            return new EquDirective(data).replaceToken(tokens, parmStart, parmEnd);
        }
        throw new ParseException("Unknown directive: " + tokens[directiveIdx], 0);
    }

    @Override
    public int hashCode() {
        int prime = 31;
        int result = 1;
        result = prime * result + ((name == null)
                                     ? 0
                                     : name.hashCode());
        return result;
    }

    @Override
    public boolean hasValue() {
        /*
         * This is only here as a place-holder so that this class can be a sub
         * command line as well as a top level.
         */
        return false;
    }

    @Override
    public int indexOf(ICmdLineArg arg) {
        return allPossibleArgs.indexOf(arg);
    }

    @Override
    public boolean isCaseSensitive() {
        /*
         * This is only here as a place-holder so that this class can be a sub
         * command line as well as a top level.
         */
        return false;
    }

    public boolean isCompiled() {
        return !allPossibleArgs.isEmpty();
    }

    private boolean isFieldExcluded(Field oneField,
                                    String[] excludeArgsByVariableName) {
        for (int f = 0; f < excludeArgsByVariableName.length; f++) {
            if (oneField.getName().equalsIgnoreCase(excludeArgsByVariableName[f])) {
                return true;
            }
        }
        return false;
    }

    @Override
    public boolean isMetaphoneAllowed() {
        /*
         * This is only here as a place-holder so that this class can be a sub
         * command line as well as a top level.
         */
        return false;
    }

    @Override
    public boolean isMultiple() {
        /*
         * This is only here as a place-holder so that this class can be a sub
         * command line as well as a top level.
         */
        return false;
    }

    @Override
    public boolean isParsed() {
        /*
         * This is only here as a place-holder so that this class can be a sub
         * command line as well as a top level.
         */
        return false;
    }

    @Override
    public boolean isPositional() {
        /*
         * This is only here as a place-holder so that this class can be a sub
         * command line as well as a top level.
         */
        return false;
    }

    @Override
    public boolean isRequired() {
        /*
         * This is only here as a place-holder so that this class can be a sub
         * command line as well as a top level.
         */
        return false;
    }

    @Override
    public boolean isRequiredValue() {
        /*
         * This is only here as a place-holder so that this class can be a sub
         * command line as well as a top level.
         */
        return false;
    }

    @Override
    public boolean isSystemGenerated() {
        /*
         * This is only here as a place-holder so that this class can be a sub
         * command line as well as a top level.
         */
        return false;
    }

    public boolean isUsageRun() {
        boolean isUr = false;

        ICmdLineArg arg = arg("" + commandPrefix + MinHelpCommandName);
        isUr = arg != null && arg.isParsed();
        if (isUr) {
            return true;
        }

        arg = arg("" + commandPrefix + commandPrefix + MaxHelpCommandName);
        isUr = arg != null && arg.isParsed();
        if (isUr) {
            return true;
        }

        arg = arg("" + commandPrefix + commandPrefix + MaxHelpCommandName + ":1");
        isUr = arg != null && arg.isParsed();
        if (isUr) {
            return true;
        }

        arg = arg("" + commandPrefix + commandPrefix + MaxHelpCommandName + ":2");
        isUr = arg != null && arg.isParsed();
        return isUr;

    }

    /**
     * Attempt to find the specification file in one of several places. We start with the exact name that is given. Then
     * we
     * remove the path and replace the path part with known places where specification files may be found. We only
     * report
     * an error if it could not be found at all.
     */
    private Token[] loadCommandLineParserIncludeFile(String filename) throws ParseException {
        File specFile = new File(filename);
        /*
         * Find the file.
         */
        String nameOnly = specFile.getName();
        if (!specFile.exists()) {
            for (File dir : defaultIncludeDirectories) {
                specFile = new File(dir, nameOnly);
                if (specFile.exists()) {
                    break;
                }
            }
        }

        IParserInput clp;
        try {
            clp = CommandLineParser.getInstance(commandPrefix, specFile);
            return clp.parseTokens();
        } catch (IOException e) {
            throw new ParseException(INCLUDE_FILE_PREFIX + nameOnly + " could not be found", 0);
        }
    }

    private List> namedBooleans() {
        if (_namedBooleans != null) {
            return _namedBooleans;
        }

        _namedBooleans = new ArrayList<>();
        Iterator> aIter = allPossibleArgs.iterator();
        while (aIter.hasNext()) {
            ICmdLineArg arg = aIter.next();
            if (!arg.isPositional() && !arg.isRequiredValue()) {
                _namedBooleans.add(arg);
            }
        }
        return _namedBooleans;
    }

    private List> namedGroups() {
        if (_namedGroups != null) {
            return _namedGroups;
        }

        _namedGroups = new ArrayList<>();
        Iterator> aIter = allPossibleArgs.iterator();
        while (aIter.hasNext()) {
            ICmdLineArg arg = aIter.next();
            if (!arg.isPositional() && arg instanceof CmdLineCLA) {
                _namedGroups.add(arg);
            }
        }
        return _namedGroups;
    }

    private List> namedValueArgs() {
        if (_namedValueArgs != null) {
            return _namedValueArgs;
        }

        _namedValueArgs = new ArrayList<>();
        Iterator> aIter = allPossibleArgs.iterator();
        while (aIter.hasNext()) {
            ICmdLineArg arg = aIter.next();
            if (!arg.isPositional() && arg.isRequiredValue()) {
                _namedValueArgs.add(arg);
            }
        }
        return _namedValueArgs;
    }

    @Override
    public Object parse(IParserInput data) {
        try {
            parseTokens(data, null);
            return null;
        } catch (Exception e) {
            throw CliException.builder().cause(e).build();
        }
    }

    @Override
    public Object parse(IParserInput data,
                        Object target) {
        try {
            if (!isCompiled()) {
                if (target == null) {
                    attemptAnnotationCompile(data.getClass(), true, new ArrayList<>(),
                                             new String[]{});
                } else {
                    CmdLineImpl.ClassLoader = target.getClass().getClassLoader();
                    attemptAnnotationCompile(target.getClass(), true, new ArrayList<>(),
                                             new String[]{});
                }
            }
            parseTokens(data, target);
            return target;
        } catch (Exception e) {
            throw CliException.builder().cause(e).build();
        }
    }

    @Override
    public Object parse(Object target,
                        String... args) {
        try {
            if (target != null) {
                CmdLineImpl.ClassLoader = target.getClass().getClassLoader();
            }

            IParserInput data = CommandLineParser.getInstance(getCommandPrefix(), args);
            if (!isCompiled()) {
                if (target == null) {
                    attemptAnnotationCompile(data.getClass(), true, new ArrayList<>(),
                                             new String[]{});
                } else {
                    attemptAnnotationCompile(target.getClass(), true, new ArrayList<>(),
                                             new String[]{});
                }
            }
            parseTokens(data, target);
            return target;
        } catch (Exception e) {
            throw CliException.builder().cause(e).build();
        }
    }

    @Override
    public Object parse(String... args) {
        try {
            IParserInput data = CommandLineParser.getInstance(getCommandPrefix(), args);
            parseTokens(data, null);
            return null;
        } catch (Exception e) {
            throw CliException.builder().cause(e).build();
        }
    }

    private void parseDirectives(List> args,
                                 Token[] tokens,
                                 Object target) throws ParseException, IOException {
        for (int t = 0; t < tokens.length; t++) {
            if (!tokens[t].isUsed()) {
                if (tokens[t].isParserDirective()) {
                    /*
                     * A Parser directive always starts with an underscore. It
                     * must be followed by (). It can also be a single '='
                     * followed immediately by a '('. This indicates an
                     * equation.
                     */
                    tokens[t].setUsed(true);
                    /*
                     * token + 1 = (
                     *
                     * find corresponding ) token #
                     */
                    int parmStart = t + 1;
                    int parmEnd = -1;
                    int lex = 0;
                    for (int e = parmStart; e < tokens.length; e++) {
                        tokens[e].setUsed(true);
                        if (tokens[e].isGroupStart()) {
                            lex++;
                            continue;
                        }
                        if (tokens[e].isGroupEnd()) {
                            lex--;
                            if (lex == 0) {
                                parmEnd = e;
                                break;
                            }
                        }
                    }
                    if (parmEnd == -1) {
                        throw new ParseException("unended directive: " + tokens[t], 0);
                    }

                    /*
                     * Start and end tokens by requirement are () and not really
                     * part of the directive, so skip over them.
                     */
                    tokens[t] = handleDirective(tokens, t, parmStart + 1, parmEnd - 1);

                    continue;
                }
            }
        }

    }

    private void parseIncludeFiles(List> args,
                                   Token[] tokens,
                                   Object target) throws ParseException, IOException {
        for (int t = 0; t < tokens.length; t++) {
            if (!tokens[t].isUsed()) {
                if (tokens[t].isIncludeFile()) {
                    /*
                     * An include file always starts with @ and is immediately
                     * followed by the filename. However, the file name might be
                     * the next token if it was quoted or there was some
                     * whitespace after the @. And it would otherwise be the
                     * remainder of the @ token.
                     */

                    tokens[t].setUsed(true);
                    Token[] newTokens = null;

                    if (tokens[t].getValue().length() > 1) {
                        newTokens = loadCommandLineParserIncludeFile(
                          tokens[t].getValue().substring(1));
                    } else {
                        int filenameT = t + 1;

                        if (filenameT >= tokens.length) {
                            throw new ParseException(
                              "end of input found instead of include directive file name", 0);
                        }

                        if (!tokens[filenameT].isLiteral()) {
                            throw new ParseException("missing include directive file name, found \""
                                                       + tokens[filenameT].toString()
                                                       + "\"", 0);
                        }

                        tokens[filenameT].setUsed(true);
                        newTokens = loadCommandLineParserIncludeFile(tokens[filenameT].getValue());
                    }
                    extractArgumentsFromTokens(newTokens, target, args);

                    if (isUsageRun()) {
                        args.clear();
                        return;
                    }

                    continue;
                }
            }
        }

    }

    private void parseNamedBoolean(List> args,
                                   Token[] tokens) throws ParseException {
        List> possibleArgs = namedBooleans();
        int tlex = 0;
        for (int t = 0; t < tokens.length; t++) {
            if (!tokens[t].isUsed()) {
                if (tokens[t].isGroupStart()) {
                    tlex++;
                    continue;
                }
                if (tokens[t].isGroupEnd()) {
                    tlex--;
                    continue;
                }
                if (tlex == 0) {
                    int holdArgCnt = args.size();
                    while (mostSalient(possibleArgs, tokens, t, args)) {
                        if (holdArgCnt < args.size()) { // arg was found
                            BooleanCLA arg = (BooleanCLA) args.get(args.size() - 1);
                            if (arg.getKeychar() != null && arg.getKeychar() == MinHelpCommandName) {
                                System.out.println(UsageBuilder.getWriter(this, 1).toString());
                                return;
                            }
                            if (arg.getKeyword() != null
                                  && arg.getKeyword().equalsIgnoreCase(MaxHelpCommandName)) {
                                System.out.println(UsageBuilder.getWriter(this, 3).toString());
                                return;
                            }
                            if (arg.getKeyword() != null
                                  && arg.getKeyword().equalsIgnoreCase(MaxHelpCommandName + ":1")) {
                                System.out.println(UsageBuilder.getWriter(this, 1).toString());
                                return;
                            }
                            if (arg.getKeyword() != null
                                  && arg.getKeyword().equalsIgnoreCase(MaxHelpCommandName + ":2")) {
                                System.out.println(UsageBuilder.getWriter(this, 2).toString());
                                return;
                            }
                        }
                    }
                }
            }
        }
        if (tlex != 0) {
            throw new ParseException("Unmatched bracket", 0);
        }
    }

    private void parseNamedGroups(List> args,
                                  Token[] tokens,
                                  Object target) throws ParseException, IOException {
        List> possibleArgs = namedGroups();
        for (int t = 0; t < tokens.length; t++) {
            if (!tokens[t].isUsed()) {
                int holdArgCnt = args.size();
                mostSalient(possibleArgs, tokens, t, args);
                if (holdArgCnt < args.size()) {
                    t = parseGroup((CmdLineCLA) args.get(args.size() - 1), tokens, t, target);
                }
            }
        }
    }

    private void parseNamedValueArgs(List> args,
                                     Token[] tokens) throws ParseException, IOException {
        List> possibleArgs = namedValueArgs();
        int tlex = 0;
        for (int t = 0; t < tokens.length; t++) {
            if (!tokens[t].isUsed()) {
                if (tokens[t].isGroupStart()) {
                    tlex++;
                    continue;
                }
                if (tokens[t].isGroupEnd()) {
                    tlex--;
                    continue;
                }
                if (tlex == 0) {
                    int holdArgCnt = args.size();
                    mostSalient(possibleArgs, tokens, t, args);
                    if (holdArgCnt < args.size()) {
                        t = parseValues(args.get(args.size() - 1), tokens, t);
                    }
                }
            }
        }
        if (tlex != 0) {
            throw new ParseException("Unmatched bracket", 0);
        }
    }

    private void parsePositional(List> args,
                                 Token[] tokens) throws ParseException, IOException {
        List> possibleArgs = positional();
        Iterator> pIter = possibleArgs.iterator();
        int t = 0;
        while (pIter.hasNext()) {
            ICmdLineArg arg = pIter.next();

            for (; t < tokens.length; t++) {
                if (!tokens[t].isUsed()) {
                    /*
                     * - and -- commands are mutually exclusive from
                     * positionals.
                     */
                    if (tokens[t].isCommand()) {
                        continue;
                    }

                    if (arg instanceof CmdLineCLA) {
                        t = parseGroup((CmdLineCLA) arg, tokens, --t, null);
                    } else {
                        t = parseValues(arg, tokens, t);
                    }
                    args.add(arg);
                    break;
                }
            }
        }
    }

    private List> parseTokens(IParserInput data,
                                             Object target) throws ParseException, IOException {
        if (!isCompiled()) {
            throw new ParseException("parser must be compiled", 0);
        }

        originalInput = data;

        Token[] tokens = data.parseTokens();

        resetArgs();

        List> args = new ArrayList<>();
        extractArgumentsFromTokens(tokens, target, args);

        if (isUsageRun()) {
            args.clear();
            return args;
        }

        checkForUnusedInput(tokens);
        applyDefaults();
        checkRequired();
        crossCheck();

        /*
         * Only start this process when the top level cmdline is exiting. That
         * means that everything is parsed into the value holders and now we can
         * start making instances.
         */
        if (getDepth() == 0) {
            assignVariables(target);
        }

        return args;
    }

    private List> positional() {
        if (_positional != null) {
            return _positional;
        }

        _positional = new ArrayList<>();
        Iterator> aIter = allPossibleArgs.iterator();
        while (aIter.hasNext()) {
            ICmdLineArg arg = aIter.next();
            if (arg.isPositional()) {
                _positional.add(arg);
            }
        }
        return _positional;
    }

    List postCompileAnalysis() {
        List localExceptions = new ArrayList<>();
        /*
         * scanforward keeps redundant errors from appearing
         */
        boolean scanforward = false;
        for (ICmdLineArg arg1 : allPossibleArgs) {
            scanforward = false;
            for (ICmdLineArg arg2 : allPossibleArgs) {
                if (arg1 == arg2) {
                    scanforward = true;
                    continue;
                }
                if (!scanforward) {
                    continue;
                }
                if (arg1.getKeychar() > ' ' && arg1.getKeychar() == arg2.getKeychar()) {
                    localExceptions.add(new ParseException(
                      format("duplicate short name, found \"{}\"' and \"{}\"", arg1, arg2),
                      0));
                }
                if (arg1.getKeyword() != null
                      && arg1.getKeyword().equalsIgnoreCase(arg2.getKeyword())) {
                    localExceptions.add(new ParseException(
                      format("duplicate long name, found \"{}\"' and \"{}\"", arg1, arg2),
                      0));
                }
                if (arg1.isMetaphoneAllowed() && arg2.isMetaphoneAllowed()
                      && arg1.getMetaphone() != null
                      && arg1.getMetaphone().equals(arg2.getMetaphone())) {
                    localExceptions.add(new ParseException(
                      format("duplicate values for metaphone, found \"{}\"' and \"{}\"", arg1,
                             arg2),
                      0));
                }
                if (arg1.isMetaphoneAllowed() && arg1.getMetaphone() != null
                      && arg1.getMetaphone().toString().equals(arg2.getKeyword())) {
                    localExceptions.add(new ParseException(
                      format("metaphone equals long name, found \"{}\"' and \"{}\"", arg1,
                             arg2),
                      0));
                }
                if (arg1.isPositional() && arg1.isMultiple() && arg2.isPositional()) {
                    localExceptions.add(new ParseException(
                      format("a multi-value positional cli must be the only positional cli, found \"{}\"' and \"{}\"",
                             arg1, arg2),
                      0));
                }
            }
        }
        return localExceptions;
    }

    /**
     * Pulls the values of the variables back into the arguments. This is usually in preparation for an export. When
     * used
     * in conjunction with a properties file, for instance, this allows the program to periodically update the
     * properties
     * and then pull them back into the properties file on disk.
     */
    @Override
    public void pull(Object variableSource) {
        try {
            reset();

            Iterator> aIter = allPossibleArgs.iterator();
            while (aIter.hasNext()) {
                ICmdLineArg arg = aIter.next();
                if (arg.getVariable() != null) {
                    try {
                        VariablePuller.getInstance().pull(arg, variableSource);
                    } catch (IllegalArgumentException | IllegalAccessException e) {
                        throw new ParseException(e.getMessage(), 0);
                    }
                }
            }
        } catch (Exception e) {
            throw CliException.builder().cause(e).build();
        }
    }

    @Override
    public void remove(ICmdLineArg arg) {
        allPossibleArgs.remove(arg);
    }

    @Override
    public void remove(int argIndex) {
        allPossibleArgs.remove(argIndex);
    }

    @Override
    public void reset() {
        // n/a
    }

    private void resetArgs() {
        Iterator> aIter = allPossibleArgs.iterator();
        while (aIter.hasNext()) {
            ICmdLineArg arg = aIter.next();
            arg.reset();
        }
    }

    @Override
    public ICmdLineArg resetCriteria() {
        // intentionally left blank
        return this;
    }

    @Override
    public int salience(Token word) {
        /*
         * This is only here as a place-holder so that this class can be a sub
         * command line as well as a top level.
         */
        return 0;
    }

    @Override
    public ICmdLineArg setCaseSensitive(boolean bool) {
        return this;
    }

    @Override
    public ICmdLineArg setDefaultValue(String defaultValue) {
        return this;
    }

    void setDepth(
      int _depth) {
        depth = _depth;
    }

    @Override
    public ICmdLineArg setEnumCriteria(String enumClassName) {
        return null;
    }

    @Override
    public ICmdLineArg setEnumCriteriaAllowError(String enumClassName) {
        return null;
    }

    @Override
    public ICmdLineArg setFactoryArgName(String argName) {
        return this;
    }

    @Override
    public ICmdLineArg setFactoryMethodName(String methodName) {
        return this;
    }

    @Override
    public ICmdLineArg setFormat(String format) {
        return this;
    }

    @Override
    public ICmdLineArg setHelp(String _help) {
        help = _help;
        return this;
    }

    @Override
    public ICmdLineArg setInstanceClass(String classString) {
        return this;
    }

    @Override
    public ICmdLineArg setKeychar(Character _keychar) {
        return this;
    }

    @Override
    public ICmdLineArg setKeyword(String _keyword) {
        return this;
    }

    @Override
    public ICmdLineArg setListCriteria(String[] values) {
        return this;
    }

    @Override
    public ICmdLineArg setMetaphoneAllowed(boolean bool) {
        /*
         * This is only here as a place-holder so that this class can be a sub
         * command line as well as a top level.
         */
        return null;
    }

    @Override
    public ICmdLineArg setMultiple(boolean bool) {
        return this;
    }

    @Override
    public ICmdLineArg setMultiple(int min) {
        return this;
    }

    @Override
    public ICmdLineArg setMultiple(int min,
                                             int max) {
        return this;
    }

    public void setName(String _name) {
        name = _name;
    }

    @Override
    public void setObject(Object value) {
        // nothing to do
    }

    @Override
    public ICmdLineArg setParsed(boolean bool) {
        return this;
    }

    @Override
    public ICmdLineArg setPositional(boolean bool) {
        return this;
    }

    @Override
    public ICmdLineArg setRangeCriteria(String min,
                                                  String max) {
        return this;
    }

    @Override
    public ICmdLineArg setRegxCriteria(String pattern) {
        return this;
    }

    @Override
    public ICmdLineArg setRequired(boolean bool) {
        return this;
    }

    @Override
    public ICmdLineArg setRequiredValue(boolean bool) {
        return this;
    }

    @Override
    public ICmdLineArg setSystemGenerated(boolean bool) {
        return this;
    }

    @Override
    public void setType(ClaType claType) {
        // n/a
    }

    @Override
    public void setUniqueId(int uId) {
        uniqueId = uId;
    }

    @Override
    public void setValue(ICmdLine value) {
        // intentionally left blank
    }

    @Override
    public void setValue(int index,
                         ICmdLine value) {
        // intentionally left blank
    }

    @Override
    public ICmdLineArg setVariable(String string) {
        return this;
    }

    @Override
    public int size() {
        int cnt = 0;
        Iterator> aIter = allPossibleArgs.iterator();
        while (aIter.hasNext()) {
            ICmdLineArg arg = aIter.next();
            if (arg instanceof DefaultCLA) {
                continue;
            }
            if (arg.isParsed()) {
                cnt++;
            }
        }
        return cnt;
    }

    @Override
    public boolean supportsCaseSensitive() {
        return false;
    }

    @Override
    public boolean supportsDefaultValues() {
        return false;
    }

    @Override
    public boolean supportsExcludeArgs() {
        return false;
    }

    @Override
    public boolean supportsFactoryArgName() {
        return false;
    }

    @Override
    public boolean supportsFactoryMethod() {
        return false;
    }

    @Override
    public boolean supportsFormat() {
        return false;
    }

    @Override
    public boolean supportsHelp() {
        return false;
    }

    @Override
    public boolean supportsInList() {
        return false;
    }

    @Override
    public boolean supportsInstanceClass() {
        return false;
    }

    @Override
    public boolean supportsLongName() {
        return false;
    }

    @Override
    public boolean supportsMatches() {
        return false;
    }

    @Override
    public boolean supportsMetaphone() {
        return false;
    }

    @Override
    public boolean supportsMultimax() {
        return false;
    }

    @Override
    public boolean supportsMultimin() {
        return false;
    }

    @Override
    public boolean supportsPositional() {
        return false;
    }

    @Override
    public boolean supportsRange() {
        return false;
    }

    @Override
    public boolean supportsRequired() {
        return false;
    }

    @Override
    public boolean supportsShortName() {
        return false;
    }

    @Override
    public void useDefaults() {
        /*
         * This is only here as a place-holder so that this class can be a sub
         * command line as well as a top level.
         */
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy