de.bmw.yamaica.common.console.CommandExecuter.java Source code

Java tutorial

Introduction

Here is the source code for de.bmw.yamaica.common.console.CommandExecuter.java

Source

/* Copyright (C) 2013-2015 BMW Group
 * Author: Manfred Bathelt (manfred.bathelt@bmw.de)
 * Author: Juergen Gehring (juergen.gehring@bmw.de)
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package de.bmw.yamaica.common.console;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.cli.AlreadySelectedException;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.MissingArgumentException;
import org.apache.commons.cli.MissingOptionException;
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.Parser;
import org.apache.commons.cli.PosixParser;
import org.apache.commons.cli.UnrecognizedOptionException;
import org.apache.commons.lang.WordUtils;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExtensionPoint;
import org.eclipse.core.runtime.IExtensionRegistry;
import org.eclipse.core.runtime.ISafeRunnable;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.SafeRunner;
import org.eclipse.core.runtime.Status;

import de.bmw.yamaica.common.console.internal.Application;

public enum CommandExecuter {
    INSTANCE;

    private final String COMMANDS_EXTENSION_POINT_ID = Application.PLUGIN_ID + ".commands";
    private final String OPTIONS_EXTENSION_POINT_ID = Application.PLUGIN_ID + ".options";
    private final String OPTION_GROUPS_EXTENSION_POINT_ID = Application.PLUGIN_ID + ".optionGroups";

    private final String COMMAND_ID_ATTRIBUTE_NAME = "id";
    private final String COMMAND_NAME_ATTRIBUTE_NAME = "name";
    private final String COMMAND_HANDLER_ATTRIBUTE_NAME = "class";
    private final String COMMAND_SYNTAX_ATTRIBUTE_NAME = "syntax";

    private final String OPTION_ID_ATTRIBUTE_NAME = "id";
    private final String OPTION_SHORT_NAME_ATTRIBUTE_NAME = "shortName";
    private final String OPTION_LONG_NAME_ATTRIBUTE_NAME = "longName";
    private final String OPTION_DESCRIPTION_ATTRIBUTE_NAME = "description";
    private final String OPTION_REQUIRED_ATTRIBUTE_NAME = "required";
    private final String OPTION_ARG_COUNT_ATTRIBUTE_NAME = "argCount";
    private final String OPTION_ARG_NAME_ATTRIBUTE_NAME = "argName";
    private final String OPTION_HAS_OPTIONAL_ARG_ATTRIBUTE_NAME = "hasOptionalArg";
    private final String OPTION_VALUE_SEPARATOR_ATTRIBUTE_NAME = "valueSeparator";

    private final String OPTION_ID_OPTION_ID_ATTRIBUTE_NAME = "optionId";

    private final String OPTION_GROUP_ID_ATTRIBUTE_NAME = "id";
    private final String OPTION_GROUP_REQUIRED_ATTRIBUTE_NAME = "required";

    private final String OPTION_GROUP_OPTION_GROUP_ID_ATTRIBUTE_NAME = "optionGroupId";

    private final String OPTIONS_CONFIGURATION_NAME = "options";
    private final String HEADER_CONFIGURATION_NAME = "header";
    private final String FOOTER_CONFIGURATION_NAME = "footer";
    private final String OPTION_CONFIGURATION_NAME = "option";
    private final String OPTION_ID_CONFIGURATION_NAME = "optionId";
    private final String OPTION_GROUP_CONFIGURATION_NAME = "optionGroup";
    private final String OPTION_GROUP_ID_CONFIGURATION_NAME = "optionGroupId";

    private final String ECLIPSE_LAUNCHER_PROPERTY_NAME = "eclipse.launcher";

    private final String ID_OPTION_DESCRIPTION = "%s of the desired console command";
    private final String EXTENSION_POINT_PARSING_ERROR_MESSAGE = "An error occured while parsing console command \"%s\"!";
    private final String OPTION_ID_NOT_FOUND_MESSAGE = "The option ID \"%s\" could not be found!";
    private final String OPTION_GROUP_ID_NOT_FOUND_MESSAGE = "The option group ID \"%s\" could not be found!";
    private final String NO_REQUIRED_OPTION_MESSAGE = "This console command needs to specify at least one required option!";
    private final String HELP_NO_COMMANDS_TEXT_MESSAGE = "No registered console commands available!";
    private final String HELP_NAME_TEXT_MESSAGE = "Command: %s";
    private final String HELP_ID_TEXT_MESSAGE = "%s: %s";
    private final String HELP_WRONG_ID_MESSAGE = HELP_ID_TEXT_MESSAGE + "%nID does not match: %s";
    private final String HELP_SEVERAL_COMMANDS_MESSAGE = "Several console commands are compatible to this set of parameters. Add ID option to select the desired one.%n";
    private final String EXECUTE_COMMAND_MESSAGE = "Excuting command \"%s\" with %s \"%s\".%n";
    private final String LAUNCHER_NAME;
    private final Option ID_OPTION;

    public static final String SHORT_ID_OPTION = "ID";
    public static final String LONG_ID_OPTION = "COMMAND_ID";

    public static final int DEFAULT_RETURN_VALUE = -1000;
    public static final int CONSOLE_WIDTH = 80;

    private int returnValue = DEFAULT_RETURN_VALUE;

    private CommandExecuter() {
        String launcherName = System.getProperty(ECLIPSE_LAUNCHER_PROPERTY_NAME);
        LAUNCHER_NAME = (null != launcherName) ? new Path(launcherName).lastSegment()
                : Platform.getProduct().getName();

        ID_OPTION = new Option(SHORT_ID_OPTION, LONG_ID_OPTION, true,
                String.format(ID_OPTION_DESCRIPTION, SHORT_ID_OPTION));
        ID_OPTION.setRequired(true);
        ID_OPTION.setArgs(1);
        ID_OPTION.setOptionalArg(true);
    }

    public int executeCommand(String command) {
        Assert.isNotNull(command);

        String[] arguments = (command.length() > 0) ? (command.trim().split("\\s")) : (new String[0]);

        return executeCommand(arguments);
    }

    public int executeCommand(String[] arguments) {
        Assert.isNotNull(arguments);

        final List<ConsoleConfiguration> configurations = getParsedExtensionPointConfigurations();

        // Print message if there are no registered console commands.
        if (configurations.size() == 0) {
            println(HELP_NO_COMMANDS_TEXT_MESSAGE);

            return DEFAULT_RETURN_VALUE;
        }

        // Print help text if no option is available.
        if (arguments.length == 0) {
            printHelp(configurations);

            return DEFAULT_RETURN_VALUE;
        }

        // Find a suitable console configuration.
        final List<ConsoleConfiguration> perfectMatchingConfigurations = new ArrayList<ConsoleConfiguration>();
        final List<ConsoleConfiguration> partialMatchingConfigurations = new ArrayList<ConsoleConfiguration>();
        final List<ConsoleConfiguration> notMatchingConfigurations = new ArrayList<ConsoleConfiguration>();

        Parser parser = new PosixParser();

        for (ConsoleConfiguration configuration : configurations) {
            try {
                CommandLine commandLine = parser.parse(configuration.options, arguments);

                // Add ID option and ID information to configuration. Thus if there are several
                // perfect matches the printHelp method can print this information.
                configuration.options.addOption(ID_OPTION);
                configuration.message = String.format(HELP_ID_TEXT_MESSAGE, SHORT_ID_OPTION, configuration.id);
                configuration.commandLine = commandLine;
                perfectMatchingConfigurations.add(configuration);
            } catch (MissingOptionException | MissingArgumentException | AlreadySelectedException exception) {
                configuration.message = exception.getMessage();
                partialMatchingConfigurations.add(configuration);
            } catch (UnrecognizedOptionException exception) {
                // Check if the unrecognized option is the ID option.
                String unrecognizedOption = exception.getOption();

                if (unrecognizedOption.equals("-" + SHORT_ID_OPTION)
                        || unrecognizedOption.equals("--" + LONG_ID_OPTION)) {
                    try {
                        // Add ID option to options object an parse arguments again.
                        CommandLine commandLine = parser.parse(configuration.options.addOption(ID_OPTION),
                                arguments);
                        String idValue = commandLine.getOptionValue(SHORT_ID_OPTION);

                        // The correct console configuration is found if parsing did not throw an
                        // exception and the ID option equals extension point ID.
                        if (configuration.id.equals(idValue)) {
                            configuration.commandLine = commandLine;
                            perfectMatchingConfigurations.add(configuration);
                        } else {
                            configuration.message = String.format(HELP_WRONG_ID_MESSAGE, SHORT_ID_OPTION,
                                    configuration.id, idValue);
                            partialMatchingConfigurations.add(configuration);
                        }
                    } catch (ParseException e) {
                        configuration.message = e.getMessage();
                        notMatchingConfigurations.add(configuration);
                    }
                } else {
                    configuration.message = exception.getMessage();
                    notMatchingConfigurations.add(configuration);
                }
            } catch (ParseException exception) {
                configuration.message = exception.getMessage();
                notMatchingConfigurations.add(configuration);
            }
        }

        // Execute compatible command or print help text
        if (perfectMatchingConfigurations.size() == 1) {
            SafeRunner.run(new ISafeRunnable() {
                @Override
                public void run() throws Exception {
                    ConsoleConfiguration configuration = perfectMatchingConfigurations.get(0);

                    if (configurations.size() > 1) {
                        println(EXECUTE_COMMAND_MESSAGE, configuration.name, SHORT_ID_OPTION, configuration.id);
                    }

                    Object executable = configuration.commandConfiguration
                            .createExecutableExtension(COMMAND_HANDLER_ATTRIBUTE_NAME);
                    ICommandLineHandler handler = (ICommandLineHandler) executable;

                    returnValue = handler.excute(configuration.commandLine);
                }

                @Override
                public void handleException(Throwable throwable) {
                    log(throwable);
                }
            });
        } else if (perfectMatchingConfigurations.size() > 1) {
            println(HELP_SEVERAL_COMMANDS_MESSAGE);
            printHelp(perfectMatchingConfigurations);
        } else if (partialMatchingConfigurations.size() > 0) {
            printHelp(partialMatchingConfigurations);
        } else {
            printHelp(notMatchingConfigurations);
        }

        return returnValue;
    }

    private void printHelp(List<ConsoleConfiguration> configurations) {
        for (ConsoleConfiguration configuration : configurations) {
            printHelp(configuration);
        }
    }

    private void printHelp(ConsoleConfiguration configuration) {
        println(HELP_NAME_TEXT_MESSAGE, configuration.name);

        if (null != configuration.message) {
            println(configuration.message);
        }

        boolean generateSyntax = true;
        String syntax = LAUNCHER_NAME;

        if (null != configuration.syntax && configuration.syntax.length() > 0) {
            syntax = syntax + " " + configuration.syntax;
            generateSyntax = false;
        }

        new HelpFormatter().printHelp(CONSOLE_WIDTH, syntax, configuration.header, configuration.options,
                configuration.footer, generateSyntax);

        System.out.println();
    }

    private void log(Throwable throwable) {
        IStatus status = new Status(Status.ERROR, Application.PLUGIN_ID, throwable.getMessage(), throwable);

        Platform.getLog(Platform.getBundle(Application.PLUGIN_ID)).log(status);
    }

    private List<ConsoleConfiguration> getParsedExtensionPointConfigurations() {
        List<ConsoleConfiguration> configurations = new ArrayList<ConsoleConfiguration>();

        // Parse extension point information
        IExtensionRegistry extensionRegistry = Platform.getExtensionRegistry();
        IExtensionPoint commandsExtensionPoint = extensionRegistry.getExtensionPoint(COMMANDS_EXTENSION_POINT_ID);

        if (null != commandsExtensionPoint) {
            Map<String, IConfigurationElement> referenceableOptionConfigurations = parseExtensionPoint(
                    extensionRegistry.getExtensionPoint(OPTIONS_EXTENSION_POINT_ID), OPTION_ID_ATTRIBUTE_NAME);
            Map<String, IConfigurationElement> referenceableOptionGroupConfigurations = parseExtensionPoint(
                    extensionRegistry.getExtensionPoint(OPTION_GROUPS_EXTENSION_POINT_ID),
                    OPTION_GROUP_ID_ATTRIBUTE_NAME);

            for (IConfigurationElement commandConfiguration : commandsExtensionPoint.getConfigurationElements()) {
                String id = commandConfiguration.getAttribute(COMMAND_ID_ATTRIBUTE_NAME);

                try {
                    Options options = new Options();

                    String name = commandConfiguration.getAttribute(COMMAND_NAME_ATTRIBUTE_NAME);
                    String syntax = commandConfiguration.getAttribute(COMMAND_SYNTAX_ATTRIBUTE_NAME);
                    String header = null;
                    String footer = null;

                    for (IConfigurationElement childConfiguration : commandConfiguration.getChildren()) {
                        switch (childConfiguration.getName()) {
                        case OPTIONS_CONFIGURATION_NAME:
                            for (IConfigurationElement optionConfiguration : childConfiguration.getChildren()) {
                                Option option = getOptionByConfiguration(optionConfiguration,
                                        referenceableOptionConfigurations);
                                OptionGroup optionGroup = getOptionGroupByConfiguration(optionConfiguration,
                                        referenceableOptionGroupConfigurations, referenceableOptionConfigurations);

                                if (null != option) {
                                    options.addOption(option);
                                }

                                if (null != optionGroup) {
                                    options.addOptionGroup(optionGroup);
                                }
                            }
                            break;

                        case HEADER_CONFIGURATION_NAME:
                            header = childConfiguration.getValue();
                            break;

                        case FOOTER_CONFIGURATION_NAME:
                            footer = childConfiguration.getValue();
                            break;
                        }
                    }

                    if (options.getRequiredOptions().size() < 1) {
                        throw new IllegalArgumentException(String.format(NO_REQUIRED_OPTION_MESSAGE));
                    }

                    configurations.add(new ConsoleConfiguration(commandConfiguration, options, id, name, syntax,
                            header, footer));
                } catch (Exception exception) {
                    String message = String
                            .format(EXTENSION_POINT_PARSING_ERROR_MESSAGE + " " + exception.getMessage(), id);

                    log(new IllegalArgumentException(message, exception));
                }
            }
        }

        return configurations;
    }

    private Map<String, IConfigurationElement> parseExtensionPoint(IExtensionPoint extensionPoint,
            String keyAttributeName) {
        Map<String, IConfigurationElement> map = new HashMap<String, IConfigurationElement>();

        if (null != extensionPoint && null != keyAttributeName) {
            for (IConfigurationElement configurationElement : extensionPoint.getConfigurationElements()) {
                map.put(configurationElement.getAttribute(keyAttributeName), configurationElement);
            }
        }

        return map;
    }

    private Option getOptionByConfiguration(IConfigurationElement optionConfiguration,
            Map<String, IConfigurationElement> referenceableOptionConfigurations) {
        switch (optionConfiguration.getName()) {
        case OPTION_CONFIGURATION_NAME:
            return createOption(optionConfiguration);

        case OPTION_ID_CONFIGURATION_NAME:
            String referencedOptionId = optionConfiguration.getAttribute(OPTION_ID_OPTION_ID_ATTRIBUTE_NAME);
            IConfigurationElement referencedOptionConfiguration = referenceableOptionConfigurations
                    .get(referencedOptionId);

            if (null == referencedOptionConfiguration) {
                throw new IllegalArgumentException(String.format(OPTION_ID_NOT_FOUND_MESSAGE, referencedOptionId));
            }

            return createOption(referencedOptionConfiguration);

        default:
            return null;
        }
    }

    private OptionGroup getOptionGroupByConfiguration(IConfigurationElement optionConfiguration,
            Map<String, IConfigurationElement> referenceableOptionGroupConfigurations,
            Map<String, IConfigurationElement> referenceableOptionConfigurations) {
        switch (optionConfiguration.getName()) {
        case OPTION_GROUP_CONFIGURATION_NAME:
            return createOptionGroup(optionConfiguration, referenceableOptionConfigurations);

        case OPTION_GROUP_ID_CONFIGURATION_NAME:
            String referencedOptionGroupId = optionConfiguration
                    .getAttribute(OPTION_GROUP_OPTION_GROUP_ID_ATTRIBUTE_NAME);
            IConfigurationElement referencedOptionGroupConfiguration = referenceableOptionGroupConfigurations
                    .get(referencedOptionGroupId);

            if (null == referencedOptionGroupConfiguration) {
                throw new IllegalArgumentException(
                        String.format(OPTION_GROUP_ID_NOT_FOUND_MESSAGE, referencedOptionGroupId));
            }

            return createOptionGroup(referencedOptionGroupConfiguration, referenceableOptionConfigurations);

        default:
            return null;
        }
    }

    private Option createOption(IConfigurationElement optionConfiguration) {
        String shortName = optionConfiguration.getAttribute(OPTION_SHORT_NAME_ATTRIBUTE_NAME);
        String longName = optionConfiguration.getAttribute(OPTION_LONG_NAME_ATTRIBUTE_NAME);
        String description = optionConfiguration.getAttribute(OPTION_DESCRIPTION_ATTRIBUTE_NAME);
        String required = optionConfiguration.getAttribute(OPTION_REQUIRED_ATTRIBUTE_NAME);
        String argCount = optionConfiguration.getAttribute(OPTION_ARG_COUNT_ATTRIBUTE_NAME);
        String argName = optionConfiguration.getAttribute(OPTION_ARG_NAME_ATTRIBUTE_NAME);
        String hasOptionalArg = optionConfiguration.getAttribute(OPTION_HAS_OPTIONAL_ARG_ATTRIBUTE_NAME);
        String valueSeparator = optionConfiguration.getAttribute(OPTION_VALUE_SEPARATOR_ATTRIBUTE_NAME);

        Option option = new Option(shortName, description);
        option.setRequired(Boolean.parseBoolean(required));
        option.setArgs(Integer.parseInt(argCount));
        option.setOptionalArg(Boolean.parseBoolean(hasOptionalArg));

        if (null != longName) {
            option.setLongOpt(longName);
        }

        if (null != argName) {
            option.setArgName(argName);
        }

        if (null != valueSeparator) {
            option.setValueSeparator(valueSeparator.charAt(0));
        }

        return option;
    }

    private OptionGroup createOptionGroup(IConfigurationElement optionGroupConfiguration,
            Map<String, IConfigurationElement> referenceableOptionConfigurations) {
        String required = optionGroupConfiguration.getAttribute(OPTION_GROUP_REQUIRED_ATTRIBUTE_NAME);

        OptionGroup optionGroup = new OptionGroup();
        optionGroup.setRequired(Boolean.parseBoolean(required));

        for (IConfigurationElement optionConfiguration : optionGroupConfiguration.getChildren()) {
            Option option = getOptionByConfiguration(optionConfiguration, referenceableOptionConfigurations);

            if (null != option) {
                optionGroup.addOption(option);
            }
        }

        return optionGroup;
    }

    private class ConsoleConfiguration {
        public final IConfigurationElement commandConfiguration;
        public final Options options;
        public final String id;
        public final String name;
        public final String syntax;
        public final String header;
        public final String footer;

        public CommandLine commandLine;
        public String message;

        public ConsoleConfiguration(IConfigurationElement commandConfiguration, Options options, String id,
                String name, String syntax, String header, String footer, CommandLine commandLine, String message) {
            this.commandConfiguration = commandConfiguration;
            this.options = options;
            this.id = id;
            this.name = name;
            this.syntax = syntax;
            this.header = header;
            this.footer = footer;
            this.commandLine = commandLine;
            this.message = message;
        }

        public ConsoleConfiguration(IConfigurationElement commandConfiguration, Options options, String id,
                String name, String syntax, String header, String footer) {
            this(commandConfiguration, options, id, name, syntax, header, footer, null, null);
        }
    }

    /**
     * Converts an instance the CommandLine Object into an array of strings.
     *
     * @param commandLine
     *            instance
     * @return Arguments split into string array
     */
    public static String[] getArguments(CommandLine commandLine) {
        return getArguments(commandLine, false);
    }

    /**
     * Converts an instance the CommandLine Object into an array of strings.
     *
     * @param commandLine
     *            instance
     * @param keepIdOption
     *            if true the returned array will contain the ID option if available
     * @return Arguments split into string array
     */
    public static String[] getArguments(CommandLine commandLine, boolean keepIdOption) {
        Assert.isNotNull(commandLine);

        List<String> argumentsList = new ArrayList<String>();

        Option[] options = commandLine.getOptions();

        if (null != options) {
            for (Option option : options) {
                String optionString = option.getOpt();

                if (keepIdOption == false && SHORT_ID_OPTION.equals(optionString)) {
                    continue;
                }

                argumentsList.add("-" + optionString);

                String[] values = option.getValues();

                if (null != values) {
                    for (String value : values) {
                        argumentsList.add(value);
                    }
                }
            }
        }

        return argumentsList.toArray(new String[argumentsList.size()]);
    }

    /**
     * Prints a message to the console and wraps the string to the same width as the outputs done by the command executer.
     *
     * @param message
     *            the message to print
     * @param arguments
     *            Arguments referenced by the format specifiers in the message string. If there are more arguments than format specifiers,
     *            the extra arguments are ignored. The number of arguments is variable and may be zero.
     */
    public static void println(String message, Object... arguments) {
        Assert.isNotNull(message);

        String formattedString = String.format(message, arguments);
        String newLineString = String.format("%n");

        // Split message into single line strings because the wrap method cannot handle multi-line messages.
        for (String line : formattedString.split(newLineString)) {
            System.out.println(WordUtils.wrap(line, CONSOLE_WIDTH));
        }

        // A new-line character at the end gets lost because of the split method.
        if (formattedString.endsWith(newLineString)) {
            System.out.println();
        }
    }
}