de.uni_koblenz.ist.utilities.option_handler.OptionHandler.java Source code

Java tutorial

Introduction

Here is the source code for de.uni_koblenz.ist.utilities.option_handler.OptionHandler.java

Source

/*
 * JGraLab - The Java Graph Laboratory
 *
 * Copyright (C) 2006-2014 Institute for Software Technology
 *                         University of Koblenz-Landau, Germany
 *                         ist@uni-koblenz.de
 *
 * For bug reports, documentation and further information, visit
 *
 *                         https://github.com/jgralab/jgralab
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 3 of the License, or (at your
 * option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
 * Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, see <http://www.gnu.org/licenses>.
 *
 * Additional permission under GNU GPL version 3 section 7
 *
 * If you modify this Program, or any covered work, by linking or combining
 * it with Eclipse (or a modified version of that program or an Eclipse
 * plugin), containing parts covered by the terms of the Eclipse Public
 * License (EPL), the licensors of this Program grant you additional
 * permission to convey the resulting work.  Corresponding Source for a
 * non-source form of such a combination shall include the source code for
 * the parts of JGraLab used as well as that of the covered work.
 */

package de.uni_koblenz.ist.utilities.option_handler;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.apache.commons.cli.BasicParser;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.GnuParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.OptionGroup;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.cli.PosixParser;

// TODO handle OptionGroups properly
/**
 * This class is a wrapper for apache commons CLI. It implements a workaround
 * for the missing feature of isolated options for printing the help or the
 * version information. This class is a wrapper for several classes of CLI.
 * 
 * It also provides methods for printing the help and version information. <br>
 * Common mean of the parameters:<br>
 * -a --alternative-schema specifies the alternative schema<br>
 * -c --compile if specified, the .java are compiled<br>
 * -d --domains if set, domain names of attributes will be printed<br>
 * -e --edgeattr if set, edge attributes will be printed<br>
 * -e --exclude-pattern regular expression matching elements which should be
 * excluded<br>
 * -g --graph tg-file of a graph as input<br>
 * -h --help print help<br>
 * -j --jar specifies the name of the jar-file<br>
 * -n --namespace-prefix namespace prefix<br>
 * -n --rolenames if set, role names will be printed<br>
 * -o --output output file<br>
 * -p --path output path for the created files<br>
 * -q --queryfile queryfile which should be executed -r --reversed if set, edges
 * will be reversed<br>
 * -s --schema tg-file of a schema as input<br>
 * -s --shorten-strings if set, strings are shortened<br>
 * -s1 -s2 --schema1 --schema2 schemas to be compared<br>
 * -v --version print version information<br>
 * -x --xsd-location the location of the xsd-schema<br>
 * 
 * @author ist@uni-koblenz.de
 * 
 */
public class OptionHandler {

    public static enum ParserType {
        BASIC, GNU, POSIX;
    }

    /**
     * This List stores all options in addition to the field "options". This
     * redundancy is neccessary because Options does not implement the Interface
     * Iterable meaning it is not possible to iterate over all options.
     */
    private List<Option> optionList;

    /**
     * Same thing for OptionGroups
     */
    private List<OptionGroup> optionGroupList;

    /**
     * This is the Options structure needed for CLI
     */
    private Options options;

    /**
     * This set stores the required options. It is necessary to do this because
     * the workaround requires setting all options to not required.
     */
    private Set<Option> requiredOptions;

    /**
     * Same thing for OptionGroups
     */
    private Set<OptionGroup> requiredOptionGroups;

    /**
     * The String representing the usage. It is lazily created.
     */
    private String usageString;

    /**
     * The String showing the tool name. It is used for the usageString.
     */
    private String toolString;

    /**
     * The String showing the version information.
     */
    private String versionString;

    /**
     * The Helpformatter needed for printing the help information.
     */
    private HelpFormatter helpFormatter;

    private int argumentCount;

    private String argumentName;

    private boolean optionalArgument;

    private ParserType parserType;

    /**
     * The only constructor of this class. It sets the toolString and the
     * versionString. In this constructor the two options -h and -v are created
     * and added. It also ensures that only -h or -v can be set.
     * 
     * @param toolString
     *            the name of the tool
     * @param versionString
     *            the version information of the tool
     */
    public OptionHandler(String toolString, String versionString) {
        options = new Options();
        optionList = new ArrayList<>();
        optionGroupList = new ArrayList<>();
        requiredOptions = new HashSet<>();
        requiredOptionGroups = new HashSet<>();
        helpFormatter = new HelpFormatter();
        parserType = ParserType.GNU;
        this.toolString = toolString;
        this.versionString = versionString;

        Option help = new Option("h", "help", false, "(optional): print this help message.");
        help.setRequired(false);
        addOption(help);

        Option version = new Option("v", "version", false, "(optional): print version information");
        version.setRequired(false);
        addOption(version);

        OptionGroup mainOptions = new OptionGroup();
        mainOptions.setRequired(false);
        mainOptions.addOption(help);
        mainOptions.addOption(version);

        options.addOptionGroup(mainOptions);
        argumentCount = 0;
        argumentName = "parameter";
        optionalArgument = false;
    }

    /**
     * This method is used for adding a new Option. It stores the information if
     * the Option is required, sets this value to false and adds the Option to
     * the Options object.
     * 
     * @param o
     */
    public void addOption(Option o) {
        // backup required Status and set it to false
        if (o.isRequired()) {
            requiredOptions.add(o);
            o.setRequired(false);
        }
        optionList.add(o);
        options.addOption(o);
    }

    public void addOptionGroup(OptionGroup og) {
        if (og.isRequired()) {
            requiredOptionGroups.add(og);
            og.setRequired(false);
        }
        optionGroupList.add(og);
        options.addOptionGroup(og);
    }

    /**
     * Returns the Options structure.
     * 
     * @return the Options structure.
     */
    public Options getOptions() {
        return options;
    }

    /**
     * Prints the version information and exits with the given exitCode.
     * 
     * @param exitCode
     *            the exitCode
     */
    public void printVersionAndExit(int exitCode) {
        System.out.println(versionString);
        System.exit(exitCode);
    }

    /**
     * Prints the help information and exits with the given exitCode.
     * 
     * @param exitCode
     */
    public void printHelpAndExit(int exitCode) {
        helpFormatter.printHelp(getUsageString(), options);
        System.exit(exitCode);
    }

    /**
     * Lazily generates and returns the usageString.
     * 
     * @return the usageString.
     */
    private String getUsageString() {
        if (usageString == null) {
            StringBuilder out = new StringBuilder(toolString);

            for (Option current : optionList) {
                out.append(" ");
                if (isOptionRequired(current)) {
                    appendOption(out, current);
                } else {
                    out.append("[");
                    appendOption(out, current);
                    out.append("]");
                }
            }
            appendAdditionalArguments(out);
            usageString = out.toString();
        }
        return usageString;
    }

    private void appendAdditionalArguments(StringBuilder out) {
        if (argumentCount != 0) {
            out.append(" (");
            if (optionalArgument) {
                out.append("[");
            }
            if (argumentCount == Option.UNLIMITED_VALUES) {
                appendArgument(out);
                out.append(" ");
                out.append("{");
                appendArgument(out);
                out.append("}");
            } else {
                for (int i = 0; i < argumentCount; i++) {
                    appendArgument(out);
                }
            }
            if (optionalArgument) {
                out.append("]");
            }
            out.append(")");
        }
    }

    private void appendArgument(StringBuilder out) {
        out.append("<");
        out.append(argumentName);
        out.append(">");
    }

    /**
     * Checks if a given Option is required according to the information stored
     * in the Map.
     * 
     * @param o
     *            the Option to check.
     * @return true if the option is required, false otherwise. If the Option is
     *         not in the Map, false is returned.
     */
    public boolean isOptionRequired(Option o) {
        return requiredOptions.contains(o);
    }

    /**
     * Checks if all required options are present in the given CommandLine
     * according to the internal Map.
     * 
     * @param comLine
     *            the commandLine to check.
     * @return true if the given CommandLine contains all required options.
     */
    public boolean containsAllRequiredOptions(CommandLine comLine) {
        boolean ok = true;
        Option[] setOptions = comLine.getOptions();

        Set<Option> setOptionsSet = new HashSet<>();
        for (Option current : setOptions) {
            setOptionsSet.add(current);
        }

        for (Option current : optionList) {
            if (isOptionRequired(current)) {
                ok &= setOptionsSet.contains(current);
                if (!ok) {
                    break;
                }
            }
        }
        // if all required options are set and there are required OptionGroups,
        // check them
        return ok && !requiredOptionGroups.isEmpty() ? ok && containsAllRequiredOptionGroups(setOptions) : ok;
    }

    private boolean containsAllRequiredOptionGroups(Option[] setOptions) {
        boolean allContained = true;
        for (OptionGroup currentRequiredOptionGroup : requiredOptionGroups) {
            boolean currentContained = false;
            for (Option currentOption : setOptions) {
                currentContained |= currentRequiredOptionGroup.getOptions().contains(currentOption);
                if (currentContained) {
                    break;
                }
            }
            allContained &= currentContained;
            if (!allContained) {
                break;
            }
        }
        return allContained;
    }

    /**
     * Used by the method getUsageString for adding an Option to the String.
     * 
     * @param out
     *            The StringBuilder to write into.
     * @param current
     *            The option to add.
     */
    private void appendOption(StringBuilder out, Option current) {
        out.append("-");
        out.append(current.getOpt());
        int numberOfArgs = current.getArgs();
        out.append(" ");
        if (current.hasOptionalArg()) {
            out.append("[");
        }
        if (numberOfArgs == Option.UNLIMITED_VALUES) {
            appendArgName(out, current);
            out.append("{");
            out.append(current.getValueSeparator());
            appendArgName(out, current);
            out.append("}");
        }
        if (numberOfArgs >= 1) {
            appendArgName(out, current);
            for (int i = 1; i < numberOfArgs; i++) {
                out.append(current.getValueSeparator());
                appendArgName(out, current);
            }
        }
        if (current.hasOptionalArg()) {
            out.append("]");
        }
    }

    /**
     * Appends the argument name of the given Option to the given StringBuilder.
     * 
     * @param out
     *            the StringBuilder to write into.
     * @param current
     *            the Option of which to write the argument name
     */
    private void appendArgName(StringBuilder out, Option current) {
        out.append("<").append(current.getArgName()).append(">");
    }

    /**
     * Parses command line parameters <code>args</code> and checks wheter -h or
     * -v were specified. In case of any error, prints diagnostic message, usage
     * information, and exits.
     * 
     * @param args
     *            command line parameters
     * @return a CommandLine object containing parsed options
     */
    public CommandLine parse(String[] args) {
        try {
            CommandLineParser parser = getParser();
            CommandLine comLine = parser.parse(getOptions(), args);
            if (comLine.hasOption("h")) {
                printHelpAndExit(0);
            } else if (comLine.hasOption("v")) {
                printVersionAndExit(0);
            } else if (!containsAllRequiredOptions(comLine)) {
                System.err.println("Required options are missing.");
                printHelpAndExit(1);
            }
            return comLine;
        } catch (ParseException e) {
            System.err.println(parseErrorMessage(e.getMessage()));

            printHelpAndExit(1);
        }
        // never reached
        return null;
    }

    private static String parseErrorMessage(String error) {
        StringBuilder sb = new StringBuilder(error.length());

        int begin = 0;
        int end = error.indexOf('[');
        if (end == -1) {
            return error;
        }
        sb.append(error.substring(begin, end));
        sb.append("\n\t");
        begin = end + 1;

        while ((end = error.indexOf(',', begin)) != -1) {
            sb.append(error.substring(begin, end + 1).trim());
            sb.append("\n\t");
            begin = end + 1;
        }

        sb.append(error.substring(begin, error.length() - 1).trim());

        return sb.toString();
    }

    /**
     * Sets the amount of additional arguments. The default is 0. It can be set
     * to Option.UNLIMITED_VALUES for an arbitrary amount. This number will not
     * be enforced, it is only used for usage string generation.
     * 
     * @param argumentCount
     */
    public void setArgumentCount(int argumentCount) {
        this.argumentCount = argumentCount;
    }

    /**
     * Sets the name of additional arguments. This value is needed for usage
     * string creation.
     * 
     * @param argumentName
     */
    public void setArgumentName(String argumentName) {
        this.argumentName = argumentName;
    }

    /**
     * Sets if additional arguments are optional. This is not enforced, it is
     * only used for usage string generation.
     * 
     * @param optionalArgument
     */
    public void setOptionalArgument(boolean optionalArgument) {
        this.optionalArgument = optionalArgument;
    }

    /**
     * Makes this OptionHandler use the POSIX parser for parsing the command
     * line.
     * 
     * @return this
     */
    public OptionHandler withPosixParser() {
        parserType = ParserType.POSIX;
        return this;
    }

    /**
     * Makes this OptionHandler use the BASIC parser for parsing the command
     * line.
     * 
     * @return this
     */
    public OptionHandler withBasicParser() {
        parserType = ParserType.BASIC;
        return this;
    }

    /**
     * Makes this OptionHandler use the GNU parser for parsing the command line
     * (this is the default).
     * 
     * @return this
     */
    public OptionHandler withGnuParser() {
        parserType = ParserType.GNU;
        return this;
    }

    /**
     * Returns the {@link CommandLineParser} that can be used to parse the
     * arguments
     * 
     * @return the {@link CommandLineParser}
     */
    private CommandLineParser getParser() {
        switch (parserType) {
        case GNU:
            return new GnuParser();
        case POSIX:
            return new PosixParser();
        case BASIC:
            return new BasicParser();
        }
        return null;
    }

}