/* * Copyright (c) 2009-2015 * IT-Consulting Stephan Schloepke (http://www.schloepke.de/) * klemm software consulting Mirko Klemm (http://www.klemm-scs.com/) * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package org.jbasics.command; import org.jbasics.checker.ContractCheck; import org.jbasics.command.annotations.Command; import org.jbasics.command.annotations.Commands; import org.jbasics.pattern.builder.Builder; import org.jbasics.pattern.strategy.ContextualExecuteStrategy; import org.jbasics.pattern.strategy.ExecuteStrategy; import org.jbasics.text.AppendableWriter; import org.jbasics.text.JavaUtilLoggingWriter; import org.jbasics.types.factories.CollectionsFactory; import org.jbasics.utilities.DataUtilities; import java.io.IOException; import java.io.PrintWriter; import java.io.Writer; import java.lang.reflect.Method; import java.util.Collections; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; public class CommandExecutor implements ExecuteStrategy<Integer, CommandCall> { public static final Integer NO_ERROR_RETURN_CODE = Integer.valueOf(0); public static final Integer GENERAL_ERROR_RETURN_CODE = Integer.valueOf(-1); private final ContextualExecuteStrategy<Integer, CommandCall, CommandExecutor> interceptor; private final Map<String, CommandSpec> commands; private final Map<String, String> aliases; private final PrintWriter outChannel; private final PrintWriter errorChannel; private CommandExecutor(final Map<String, CommandSpec> commands, final Map<String, String> aliases, final PrintWriter outChannel, final PrintWriter errChannel, final ContextualExecuteStrategy<Integer, CommandCall, CommandExecutor> interceptor) { this.commands = ContractCheck.mustNotBeNullOrEmpty(commands, "commands"); //$NON-NLS-1$ this.aliases = ContractCheck.mustNotBeNullOrEmpty(aliases, "aliases"); //$NON-NLS-1$ this.outChannel = outChannel == null ? new PrintWriter(System.out) : outChannel; this.errorChannel = errChannel == null ? new PrintWriter(System.err) : errChannel; this.interceptor = interceptor; } public static CommandExecutorBuilder newBuilder() { return new CommandExecutorBuilder(); } public int execute(final String... args) { if (args == null || args.length == 0) { try { this.errorChannel.println("Missing command to execute"); this.outChannel.println("Usage:"); printCommand(this.outChannel); } catch (final IOException e) { this.errorChannel.println("IOException caught and no way to handle it"); e.printStackTrace(this.errorChannel); } } else { try { return DataUtilities.coalesce(this.interceptor != null ? this.interceptor.execute(CommandCall.create(args), this) : execute(CommandCall.create(args)), CommandExecutor.NO_ERROR_RETURN_CODE); } catch (final RuntimeException e) { this.errorChannel.println("Exception caught when executing command"); e.printStackTrace(this.errorChannel); } } return CommandExecutor.GENERAL_ERROR_RETURN_CODE; } public Appendable printCommand(final Appendable out) throws IOException { for (final CommandSpec spec : this.commands.values()) { out.append(spec.toString()); out.append('\n'); } return out; } @Override public Integer execute(final CommandCall commandCall) { String commandName = ContractCheck.mustNotBeNull(commandCall, "commandCall").getName(); //$NON-NLS-1$ final String aliasResolution = this.aliases.get(commandName); if (aliasResolution != null) { if (aliasResolution.indexOf(",") > 0) { //$NON-NLS-1$ throw new RuntimeException("Ambigious command alias '" + commandName + "' for commands '" + aliasResolution.substring(1) + "'"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } else { commandName = aliasResolution; } } final CommandSpec commandSpec = this.commands.get(commandName); if (commandSpec == null) { throw new CommandExecutionException("Unknown command '" + commandCall + "'", commandCall); //$NON-NLS-1$ //$NON-NLS-2$ } return commandSpec.execute(commandCall); } public static class CommandExecutorBuilder implements Builder<CommandExecutor> { private final Map<String, CommandSpec> commands; private final Map<String, String> aliases; private PrintWriter outChannel; private PrintWriter errChannel; private ContextualExecuteStrategy<Integer, CommandCall, CommandExecutor> interceptor; private CommandExecutorBuilder() { this.commands = CollectionsFactory.instance().newMapInstance(); this.aliases = CollectionsFactory.instance().newMapInstance(); } @Override public void reset() { this.commands.clear(); this.aliases.clear(); this.outChannel = null; this.errChannel = null; this.interceptor = null; } @Override public CommandExecutor build() { final Map<String, CommandSpec> temp = CollectionsFactory.instance().newMapInstance(); temp.putAll(this.commands); final Map<String, String> tempAliases = CollectionsFactory.instance().newMapInstance(); tempAliases.putAll(this.aliases); return new CommandExecutor(Collections.unmodifiableMap(temp), Collections.unmodifiableMap(tempAliases), this.outChannel, this.errChannel, this.interceptor); } public CommandExecutorBuilder withCommandTypes(final Class<?>... commandsTypes) { for (final Class<?> commandsType : commandsTypes) { scanCommandsType(commandsType); } return this; } protected void scanCommandsType(final Class<?> commandsType) { assert commandsType != null; final Commands cmds = commandsType.getAnnotation(Commands.class); String namespace = null; if (cmds != null) { namespace = cmds.value().trim(); if (namespace.length() == 0) { namespace = null; } } if (namespace == null) { namespace = commandsType.getSimpleName(); } scanCommandSpecs(namespace, commandsType); } private void scanCommandSpecs(final String namespace, final Class<?> scanType) { assert namespace != null && scanType != null; for (final Method m : scanType.getMethods()) { final Command cmd = m.getAnnotation(Command.class); if (cmd != null) { String cmdName = cmd.value().trim(); if (cmdName.length() == 0) { cmdName = m.getName(); } addSpec(new CommandSpec(namespace, cmdName, m, cmd.documentation())); } } } private void addSpec(final CommandSpec cmdSpec) { final String fullName = cmdSpec.getFullname(); if (!this.commands.containsKey(fullName)) { this.commands.put(fullName, cmdSpec); } else { throw new RuntimeException("Cannot add duplicate command in same namespace " + fullName); //$NON-NLS-1$ } final String name = cmdSpec.getName(); final String alias = this.aliases.get(name); if (alias == null) { this.aliases.put(name, fullName); } else { this.aliases.put(name, alias + "," + fullName); //$NON-NLS-1$ } } public CommandExecutorBuilder withCommandSpecs(final CommandSpec... commandsTypes) { for (final CommandSpec commandsType : commandsTypes) { addSpec(commandsType); } return this; } public CommandExecutorBuilder withOutputAppendable(final Appendable out) { return withOutputWriter(out != null ? new AppendableWriter(out) : null); } public CommandExecutorBuilder withOutputWriter(final Writer out) { return withOutputWriter(new PrintWriter(out)); } public CommandExecutorBuilder withOutputWriter(final PrintWriter out) { this.outChannel = out; return this; } public CommandExecutorBuilder withErrorAppendable(final Appendable out) { return withErrorWriter(out != null ? new AppendableWriter(out) : null); } public CommandExecutorBuilder withErrorWriter(final Writer out) { return withErrorWriter(new PrintWriter(out)); } public CommandExecutorBuilder withErrorWriter(final PrintWriter out) { this.errChannel = out; return this; } public CommandExecutorBuilder withJavaLoggingOutput(final Class<?> loggerType) { return withJavaLoggingOutput(ContractCheck.mustNotBeNull(loggerType, "loggerType").getName()); } public CommandExecutorBuilder withJavaLoggingOutput(final String loggerName) { return withJavaLoggingOutput(Logger.getLogger(ContractCheck.mustNotBeNull(loggerName, "loggerName"))); } public CommandExecutorBuilder withJavaLoggingOutput(final Logger logger) { return withOutputWriter(new JavaUtilLoggingWriter(ContractCheck.mustNotBeNull(logger, "logger"), Level.INFO)).withErrorWriter( new JavaUtilLoggingWriter(logger, Level.SEVERE)); } public CommandExecutorBuilder withInterceptor(final ContextualExecuteStrategy<Integer, CommandCall, CommandExecutor> interceptor) { this.interceptor = interceptor; return this; } } }