CliParameterSet.java

/**************************************************************************
 *
 * Copyright (c) 2015-2019 Yawg project contributors.
 *
 **************************************************************************/

package com.varmateo.yawg.cli;

import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.nio.file.Paths;

import io.vavr.collection.Array;
import io.vavr.collection.Seq;
import io.vavr.collection.Set;
import io.vavr.control.Option;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.GnuParser;
import org.apache.commons.cli.MissingArgumentException;
import org.apache.commons.cli.MissingOptionException;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.cli.UnrecognizedOptionException;


/**
 * Represents the set of options supported in the command line.
 */
/* default */ final class CliParameterSet {


    private static final String FLAG_TRUE  = "true";

    private final Set<CliParameter> _allOptions;
    private final CommandLine _cmdLine;


    /**
     * Only used internally.
     */
    private CliParameterSet(
            final Set<CliParameter> allOptions,
            final CommandLine cmdLine) {

        _allOptions = allOptions;
        _cmdLine = cmdLine;
    }


    /**
     *
     */
    public static CliParameterSet parse(
            final Set<CliParameter> options,
            final String[] args)
            throws CliException {

        Options apacheOptions = buildApacheOptions(options);
        CommandLineParser cliParser = new GnuParser();
        final CommandLine cmdLine;

        try {
            cmdLine = cliParser.parse(apacheOptions, args, false);
        } catch ( ParseException e ) {
            throw raiseCliException(apacheOptions, e);
        }

        @SuppressWarnings("unchecked")
        java.util.List<String> argList = cmdLine.getArgList();
        if ( (argList!=null) && !argList.isEmpty() ) {
            // There were arguments that were not options. We require
            // all arguments to be options, thus this is an invalid
            // command line.
            throw CliException.unknownOption(argList.get(0));
        }

        return new CliParameterSet(options, cmdLine);
    }


    /**
     *
     */
    private static Options buildApacheOptions(final Set<CliParameter> options) {

        return options
                .map(CliParameter::apacheOption)
                .foldLeft(
                        new Options(),
                        (xs, x) -> xs.addOption(x));
    }


    /**
     *
     */
    private static CliException raiseCliException(
            final Options options,
            final ParseException e)
            throws CliException {

        final CliException cause;

        if ( e instanceof MissingOptionException ) {
            final MissingOptionException ex = (MissingOptionException)e;
            final String shortOpt = (String)ex.getMissingOptions().get(0);
            final org.apache.commons.cli.Option option = options.getOption(shortOpt);
            final String optionName = getApacheOptionName(option);
            cause = CliException.missingOption(optionName);
        } else if ( e instanceof UnrecognizedOptionException ) {
            final UnrecognizedOptionException ex = (UnrecognizedOptionException)e;
            final String optionName = ex.getOption();
            cause = CliException.unknownOption(optionName);
        } else if ( e instanceof MissingArgumentException ) {
            final MissingArgumentException ex = (MissingArgumentException)e;
            final String optionName = getApacheOptionName(ex.getOption());
            cause = CliException.missingOptionArg(optionName);
        } else {
            cause = CliException.optionParseFailure(e);
        }

        throw cause;
    }


    /**
     *
     */
    private static String getApacheOptionName(
            final org.apache.commons.cli.Option apacheOption) {

        final String result;

        String longName = apacheOption.getLongOpt();
        if ( longName != null ) {
            result = "--" + longName;
        } else {
            String shortName = apacheOption.getOpt();
            result = "-" + shortName;
        }

        return result;
    }


    /**
     *
     */
    public Set<CliParameter> supportedOptions() {

        return _allOptions;
    }


    /**
     *
     */
    public boolean hasOption(final CliParameter option) {

        final String optionName = option.name();

        return _cmdLine.hasOption(optionName);
    }


    /**
     * Retrieves all the values for an option.
     *
     * <p>An option will have multiple values when it is given more
     * than onde in the list of command line arguments.</p>
     *
     * @param option The name of the option whose values are to be
     * returned.
     *
     * @return A list with the values of the given option, in the same
     * order theay appeared in the command line. It will be empty if
     * the option was not given in the command line.
     */
    public Seq<String> getAll(final CliParameter option) {

        final String   optionName   = option.name();
        final String[] optionValues = _cmdLine.getOptionValues(optionName);

        return Option.of(optionValues)
                .map(Array::of)
                .getOrElse(Array::of);
    }


    /**
     * Retrieves the value of a mandatory option.
     *
     * <p>If the given option does not exist, then a
     * <code>CliException</code> will be thrown.
     *
     * @param option The name (short or long) of the option whose
     * value is to be retrieved.
     *
     * @return The value of the given option.
     *
     * @exception CliException Thrown if the given command line does
     * not contain the option.
     */
    public String get(final CliParameter option)
            throws CliException {

        final String optionValue;
        final String optionName = option.name();
        final String[] optionValues = _cmdLine.getOptionValues(optionName);

        if ( optionValues != null ) {
            final int indexOfLast = optionValues.length-1;
            optionValue = optionValues[indexOfLast];
        } else {
            throw CliException.missingOption(option.literal());
        }

        return optionValue;
    }


    /**
     * Retrieves the value of a conditional option.
     *
     * @param option The name (short or long) of the option whose
     * value is to be retrieved.
     *
     * @param defaultValue The value to be returned if the option is
     * not present.
     */
    public String get(
            final CliParameter option,
            final String defaultValue) {

        final String optionValue;
        final String optionName = option.name();
        final String[] optionValues = _cmdLine.getOptionValues(optionName);

        if ( optionValues != null ) {
            final int indexOfLast = optionValues.length-1;
            optionValue = optionValues[indexOfLast];
        } else {
            optionValue = defaultValue;
        }

        return optionValue;
    }


    /**
     * Retrieves the value of a mandatory option as a path.
     */
    public Path getPath(final CliParameter option)
            throws CliException {

        String pathStr = get(option);

        return stringToPath(option, pathStr);
    }


    /**
     *
     */
    public Path getPath(
            final CliParameter option,
            final Path defaultValue)
            throws CliException {

        String pathStr = get(option, null);

        return pathStr == null
                ? defaultValue
                : stringToPath(option, pathStr);
    }


    /**
     *
     */
    private Path stringToPath(
            final CliParameter option,
            final String pathStr)
            throws CliException {

        try {
            return Paths.get(pathStr);
        } catch ( InvalidPathException e ) {
            throw CliException.invalidPath(option.literal(), pathStr);
        }
    }


    /**
     * Checks the given option represents a "true" valued flag.
     */
    public boolean isTrue(final CliParameter option) {

        boolean result = false;

        if ( hasOption(option) ) {
            String value = get(option, FLAG_TRUE);

            result = FLAG_TRUE.equals(value);
        }

        return result;
    }


}