name.livitski.databag.cli.Syntax.java Source code

Java tutorial

Introduction

Here is the source code for name.livitski.databag.cli.Syntax.java

Source

/**
 *  Copyright 2013 Konstantin Livitski
 * 
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the Data-bag Project License.
 *
 *  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
 *  Data-bag Project License for more details.
 *
 *  You should find a copy of the Data-bag Project License in the
 *  `data-bag.md` file in the `LICENSE` directory
 *  of this package or repository.  If not, see
 *  <http://www.livitski.name/projects/data-bag/license>. If you have any
 *  questions or concerns, contact the project's maintainers at
 *  <http://www.livitski.name/contact>. 
 */
package name.livitski.databag.cli;

import java.io.PrintStream;
import java.io.PrintWriter;
import java.util.Collection;
import java.util.Comparator;
import java.util.MissingResourceException;
import java.util.SortedSet;
import java.util.TreeSet;

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.OptionBuilder;
import org.apache.commons.cli.OptionGroup;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;

/**
 * Parses the application's command line and displays the
 * summary of command line syntax.
 */
public class Syntax {
    public CommandLine parseCommandLine(String[] args) throws ParseException {
        CommandLineParser parser = new GnuParser();
        return parser.parse(OPTIONS, args);
    }

    @SuppressWarnings("unchecked")
    public void usage(PrintStream out) {
        PrintWriter pw = new PrintWriter(out, true);
        pw.println(getMessage("USAGE"));
        pw.println();
        pw.println("    " + getMessage("SYNTAX"));
        pw.println();
        pw.println(getMessage("COMMANDS"));
        pw.println();
        SortedSet<Option> commands = new TreeSet<Option>(OPTION_COMPARATOR);
        commands.addAll(COMMAND_OPTION_GROUP.getOptions());
        listOptions(commands, pw);
        pw.println(getMessage("OPTIONS"));
        pw.println();
        SortedSet<Option> options = new TreeSet<Option>(OPTION_COMPARATOR);
        options.addAll(OPTIONS.getOptions());
        options.removeAll(commands);
        listOptions(options, pw);
        getHelpFormatter().printWrapped(pw, OUTPUT_WIDTH, getMessage("MORE"));
        pw.println();
        pw.close();
    }

    public Syntax withResources(Resources resources) {
        this.resources = resources;
        return this;
    }

    /**
     * Diagnostic entry point for the build process to check that all
     * usage strings that describe the application's syntax are present
     * in <code>usage.resources</code>.
     * Absent resources cause a <code>System.err</code> message
     * and a non-zero exit code.
     * @param args this method ignores its argument
     */
    @SuppressWarnings("unchecked")
    public static void main(String[] args) {
        PrintStream out = System.err;
        Resources resources = new Resources();
        final Class<Syntax> clazz = Syntax.class;
        String id = null;
        String legend = null;
        for (Option option : (Collection<Option>) OPTIONS.getOptions()) {
            try {
                id = getOptionId(option);
                legend = resources.getString(USAGE_BUNDLE, clazz, id).trim();
                if (legend.indexOf('.') < legend.length() - 1) {
                    out.printf(
                            "Description of option %s must be a single sentence ending with a period. Got:%n\"%s\"%n",
                            id, legend);
                    System.exit(5);
                }
                if (option.hasArg())
                    resources.getString(USAGE_BUNDLE, clazz, "arg" + id);
                legend = null;
            } catch (MissingResourceException missing) {
                if (null == legend) {
                    out.printf("Option \"%s\" does not have a description string in the resource bundle \"%s\"%n",
                            id, USAGE_BUNDLE);
                    missing.printStackTrace(out);
                    System.exit(1);
                }
                out.printf("Required argument spec \"%s\" is missing from the resource bundle \"%s\"%n",
                        getArgumentId(option), USAGE_BUNDLE);
                missing.printStackTrace(out);
                System.exit(2);
            } catch (Exception problem) {
                out.printf("Error while verifying option \"%s\" in the resource bundle \"%s\"%n", id, USAGE_BUNDLE);
                problem.printStackTrace(out);
                System.exit(-1);
            }
        }
    }

    protected void listOptions(Collection<Option> options, PrintWriter out) {
        HelpFormatter formatter = getHelpFormatter();
        for (Option option : options) {
            out.printf("    %s%s%n", formatOptionHeader(option), formatArguments(option));
            String summary = "## NO DESCRIPTION PROVIDED ##";
            try {
                String id = getOptionId(option);
                summary = getResources().getString(USAGE_BUNDLE, getClass(), id);
            } catch (MissingResourceException missing) {
            }
            out.print("        ");
            formatter.printWrapped(out, OUTPUT_WIDTH - 8, 8, summary);
        }
        if (!options.isEmpty())
            out.println();
    }

    protected static String getOptionId(Option option) {
        return option.hasLongOpt() ? LONG_OPT_PREFIX + option.getLongOpt() : OPT_PREFIX + option.getOpt();
    }

    protected static String getArgumentId(Option option) {
        return "arg" + getOptionId(option);
    }

    protected String formatArguments(Option option) {
        if (!option.hasArg())
            return "";
        else {
            String arg = "#unknown#";
            try {
                String id = getArgumentId(option);
                arg = getResources().getString(USAGE_BUNDLE, getClass(), id);
            } catch (MissingResourceException missing) {
            }
            return ' ' + arg;
        }
    }

    protected static String formatOptionHeader(Option option) {
        String title;
        if (null == option.getOpt())
            title = LONG_OPT_PREFIX + option.getLongOpt();
        else
            title = OPT_PREFIX + option.getOpt()
                    + (option.hasLongOpt() ? ", " + LONG_OPT_PREFIX + option.getLongOpt() : "");
        return title;
    }

    protected Resources getResources() {
        return resources;
    }

    protected HelpFormatter getHelpFormatter() {
        if (null == formatter)
            formatter = new HelpFormatter();
        return formatter;
    }

    protected static class OptionComparator implements Comparator<Option> {
        final private String getKey(Option option) {
            String opt = option.getOpt();
            return opt == null ? option.getLongOpt() : opt;
        }

        public int compare(Option opt1, Option opt2) {
            return getKey(opt1).compareTo(getKey(opt2));
        }
    }

    protected static final String USAGE_BUNDLE = "usage";
    protected static final OptionComparator OPTION_COMPARATOR = new OptionComparator();
    protected static final String LONG_OPT_PREFIX = "--";
    protected static final String OPT_PREFIX = "-";
    protected static final int OUTPUT_WIDTH = 80;

    private String getMessage(String id) {
        return getResources().getMessage(Syntax.class, id);
    }

    private Resources resources;
    private HelpFormatter formatter;

    protected static final String HELP_COMMAND = "help"; // -?

    protected static final String DROP_COMMAND = "drop";

    protected static final String LIST_COMMAND = "list"; // -l

    protected static final String HISTORY_COMMAND = "history"; // -h

    protected static final String LOG_COMMAND = "log";

    protected static final String PURGE_COMMAND = "purge";

    protected static final String RESTORE_COMMAND = "restore"; // -r

    protected static final String UNDO_COMMAND = "undo"; // -u

    protected static final String SYNC_COMMAND = "sync"; // -s

    protected static final String NOSYNC_OPTION = "nosync"; // -N

    protected static final String CREATE_OPTION = "create";

    protected static final String ENCRYPT_OPTION = "encrypt"; // -E

    protected static final String NOBANNER_OPTION = "nobanner";

    protected static final String LOCAL_OPTION = "local"; // -C

    // an argument to ENCRYPT_OPTION
    protected static final String CIPHER_OPTION = "--cipher";

    // an argument to LOCAL_OPTION
    protected static final String DEFAULT_OPTION = "--default";

    // an argument to FILTER_OPTION
    protected static final String INVERT_OPTION = "--invert";

    // an argument to DROP FILTER command
    protected static final String FORCE_OPTION = "--force";

    protected static final String COMPRESSION_OPTION = "compress";

    protected static final String LOB_SIZE_OPTION = "lob-size";

    protected static final String MEDIUM_OPTION = "medium"; // -d

    protected static final String FILE_ID_OPTION = "fn";

    protected static final String VERSION_ID_OPTION = "vn";

    protected static final String AS_OF_OPTION = "as-of"; // -a

    protected static final String FILTER_OPTION = "filter"; // -F

    protected static final String SAVE_OPTION = "save"; // -o

    protected static final String LOAD_OPTION = "load";

    protected static final String SET_OPTION = "set";

    protected static final String DEFAULT_ACTION_OPTION = "default-action"; // -A

    protected static final String VERBOSE_OPTION = "verbose"; // -v

    protected static final String ALLOWED_TIMESTAMP_DISCREPANCY_OPTION = "allow-time-diff";

    protected static final String CUMULATIVE_DELTA_SIZE_OPTION = "cds";

    protected static final String DELTA_CHAIN_SIZE_OPTION = "dcs";

    protected static final String SCHEMA_EVOLUTION_OPTION = "upgrade-db";

    /**
     * NOTE: DO NOT add commands' descriptions here. Place them in the
     * <code>usage.properties</code> resource file instead. All argument names MUST BE EMPTY.
     */
    @SuppressWarnings("static-access")
    private static final OptionGroup COMMAND_OPTION_GROUP = new OptionGroup()

            .addOption(OptionBuilder.withLongOpt(PURGE_COMMAND).hasOptionalArgs(2).withArgName("").create())

            .addOption(OptionBuilder.withLongOpt(DROP_COMMAND).hasOptionalArgs().withArgName("").create())

            .addOption(OptionBuilder.withLongOpt(LIST_COMMAND).hasOptionalArg().withArgName("").create('l'))

            .addOption(OptionBuilder.withLongOpt(HISTORY_COMMAND).hasOptionalArg().withArgName("").create('h'))

            .addOption(OptionBuilder.withLongOpt(RESTORE_COMMAND).hasOptionalArg().withArgName("").create('r'))

            .addOption(OptionBuilder.withLongOpt(UNDO_COMMAND).hasOptionalArg().withArgName("").create('u'))

            .addOption(OptionBuilder.withLongOpt(SYNC_COMMAND).hasOptionalArg().withArgName("").create('s'))

            .addOption(OptionBuilder.withLongOpt(LOG_COMMAND).hasOptionalArgs(2).withArgName("").create())

            .addOption(OptionBuilder.withLongOpt(HELP_COMMAND).create('?'));

    /**
     * NOTE: DO NOT add option descriptions here. Place them in the
     * <code>usage.properties</code> resource file instead. All argument names MUST BE EMPTY.
     */
    @SuppressWarnings("static-access")
    private static final Options OPTIONS = new Options()

            .addOption(OptionBuilder.withLongOpt(CREATE_OPTION).create())

            .addOption(OptionBuilder.withLongOpt(SAVE_OPTION).hasArg().withArgName("").create('o'))

            .addOption(OptionBuilder.withLongOpt(NOSYNC_OPTION).create('N'))

            .addOption(
                    // TODO: consider implementing:
                    //    + " The 'file' argument value followed by a file"
                    //    + " name tells data-bag to read the encryption key from that file. Since the encryption key"
                    //    + " is read as plain text, you have to make sure that access to that file is restricted."
                    //    + " You may want to store the key file in an encrypted format if your system supports that."
                    //    + " Data-bag will use the entire file as a password string, including all end-of-line sequences"
                    //    + " in it."
                    OptionBuilder.withLongOpt(ENCRYPT_OPTION).hasOptionalArgs().withArgName("").create('E'))

            .addOption(OptionBuilder.withLongOpt(FILTER_OPTION).hasOptionalArgs(2).withArgName("").create('F'))

            .addOption(OptionBuilder.withLongOpt(VERBOSE_OPTION).hasOptionalArg().withArgName("").create('v'))

            .addOption(OptionBuilder.withLongOpt(FILE_ID_OPTION).hasArg().withArgName("").withType(Number.class)
                    .create())

            .addOption(OptionBuilder.withLongOpt(VERSION_ID_OPTION).hasArg().withArgName("").withType(Number.class)
                    .create())

            .addOption(OptionBuilder.withLongOpt(AS_OF_OPTION).hasOptionalArgs(2).withArgName("").create('a'))

            .addOption(OptionBuilder.withLongOpt(LOAD_OPTION).hasArg().withArgName("").create())

            .addOption(OptionBuilder.withLongOpt(SET_OPTION).hasArgs(2).withArgName("").create())

            .addOption(OptionBuilder.withLongOpt(NOBANNER_OPTION).create())

            .addOption(OptionBuilder.withLongOpt(MEDIUM_OPTION).withArgName("").hasOptionalArgs(2).create('d'))

            .addOption(OptionBuilder.withLongOpt(DEFAULT_ACTION_OPTION).hasArg().withArgName("").create('A'))

            .addOption(OptionBuilder.withLongOpt(LOCAL_OPTION).hasOptionalArgs(2).withArgName("").create('C'))

            .addOption(OptionBuilder.withLongOpt(ALLOWED_TIMESTAMP_DISCREPANCY_OPTION).hasArg().withArgName("")
                    .create())

            .addOption(OptionBuilder.withLongOpt(CUMULATIVE_DELTA_SIZE_OPTION).hasArg().withArgName("").create())

            .addOption(OptionBuilder.withLongOpt(DELTA_CHAIN_SIZE_OPTION).hasArg().withArgName("").create())

            .addOption(OptionBuilder.withLongOpt(COMPRESSION_OPTION).hasArg().withArgName("").create())

            .addOption(OptionBuilder.withLongOpt(LOB_SIZE_OPTION).hasArg().withArgName("").withType(Number.class)
                    .create())

            .addOption(OptionBuilder.withLongOpt(SCHEMA_EVOLUTION_OPTION).create())

            .addOptionGroup(COMMAND_OPTION_GROUP);
}