/*
* This file is part of NucleusFramework for Bukkit, licensed under the MIT License (MIT).
*
* Copyright (c) JCThePants (www.jcwhatever.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 com.jcwhatever.nucleus.internal.managed.commands;
import com.jcwhatever.nucleus.Nucleus;
import com.jcwhatever.nucleus.internal.NucLang;
import com.jcwhatever.nucleus.internal.NucMsg;
import com.jcwhatever.nucleus.internal.managed.commands.CommandCollection.ICommandContainerFactory;
import com.jcwhatever.nucleus.internal.managed.commands.CommandParser.ParsedCommand;
import com.jcwhatever.nucleus.internal.managed.commands.CommandParser.ParsedTabComplete;
import com.jcwhatever.nucleus.managed.commands.ICommand;
import com.jcwhatever.nucleus.managed.commands.ICommandDispatcher;
import com.jcwhatever.nucleus.managed.commands.IRegisteredCommand;
import com.jcwhatever.nucleus.managed.commands.exceptions.CommandException;
import com.jcwhatever.nucleus.managed.commands.mixins.IExecutableCommand;
import com.jcwhatever.nucleus.managed.commands.mixins.IVisibleCommand;
import com.jcwhatever.nucleus.managed.commands.utils.AbstractCommand;
import com.jcwhatever.nucleus.managed.language.Localizable;
import com.jcwhatever.nucleus.managed.messaging.IMessenger;
import com.jcwhatever.nucleus.providers.permissions.Permissions;
import com.jcwhatever.nucleus.utils.ArrayUtils;
import com.jcwhatever.nucleus.utils.PreCon;
import com.jcwhatever.nucleus.utils.text.TextUtils;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* Dispatches Bukkit commands to the proper {@link AbstractCommand} for execution.
*
* <p>Also handles tab completion.</p>
*/
class CommandDispatcher implements ICommandDispatcher {
private static final String ERROR_TEMPLATE = "{RED}{0}";
@Localizable static final String _ACCESS_DENIED = "{RED}Access denied.";
@Localizable static final String _UNAVAILABLE = "{RED}Command unavailable.";
@Localizable static final String _COMMAND_INCOMPLETE =
"{RED}Command incomplete. Type '{0: usage}' for help.";
@Localizable static final String _COMMAND_NOT_FOUND =
"{RED}Command not found. Type '{0: usage}' for help.";
private final Plugin _plugin;
private RegisteredCommand _defaultRoot;
private CommandCollection _rootCommands;
private final IMessenger _msg;
private final Set<String> _pluginCommands;
private final UsageGenerator _usageGenerator;
/**
* Constructor.
*
* @param plugin The owning plugin.
*/
public CommandDispatcher(Plugin plugin, ICommandContainerFactory commandFactory) {
PreCon.notNull(plugin);
_plugin = plugin;
_rootCommands = new CommandCollection(plugin, commandFactory);
_msg = Nucleus.getMessengerFactory().create(plugin);
_usageGenerator = new UsageGenerator();
if (plugin.getDescription().getCommands() == null) {
NucMsg.warning(plugin, "Plugin has no commands registered in its plugin.yml file.");
_pluginCommands = new HashSet<>(2);
}
else {
_pluginCommands = new HashSet<>(plugin.getDescription().getCommands().keySet());
}
_defaultRoot = new RegisteredCommand(getPlugin(), new AboutCommand(), commandFactory);
_defaultRoot.setDispatcher(this, null);
}
@Override
public Plugin getPlugin() {
return _plugin;
}
@Override
public boolean onCommand(CommandSender sender, Command cmd, String rootName, String[] rootArguments) {
RegisteredCommand rootCommand = _rootCommands.getCommand(cmd.getName());
if (rootCommand == null)
rootCommand = _defaultRoot;
rootCommand.getInfo().setCurrentAlias(rootName);
CommandParser parser = new CommandParser(rootCommand);
ParsedCommand parsed = parser.parseCommand(rootCommand.getCommandCollection(), rootArguments);
if (parsed == null) {
// command not found
NucMsg.tell(getPlugin(), sender, ERROR_TEMPLATE, NucLang.get(_COMMAND_NOT_FOUND, rootName));
return true; // finish
}
RegisteredCommand command = parsed.getCommand();
String[] rawArguments = parsed.getArguments();
// Check if the player has permissions to run the command
if (sender instanceof Player && !Permissions.has((Player)sender, command.getPermission().getName())) {
NucMsg.tell(getPlugin(), sender, ERROR_TEMPLATE, NucLang.get(_ACCESS_DENIED));
return true;
}
// make sure the command is visible to the command sender
if (command.getCommand() instanceof IVisibleCommand
&& !((IVisibleCommand) command.getCommand()).isVisible(sender)) {
NucMsg.tell(getPlugin(), sender, ERROR_TEMPLATE, NucLang.get(_UNAVAILABLE));
return true;
}
// handle command help, display if the command argument is '?' or 'help'
if (isCommandHelp(rawArguments)) {
int page = TextUtils.parseInt(
ArrayUtils.get(rawArguments, 1, null), 1);
command.showHelp(sender, page);
return true; // finished
}
if (isDetailedHelp(rawArguments)) {
int page = TextUtils.parseInt(
ArrayUtils.get(rawArguments, 1, null), 1);
command.showDetailedHelp(sender, page);
return true; // finished
}
// Determine if the command can execute or if it requires sub commands
if (!(command.getCommand() instanceof IExecutableCommand)) {
NucMsg.tell(getPlugin(), sender,
ERROR_TEMPLATE, NucLang.get(_COMMAND_INCOMPLETE,
_usageGenerator.generate(command, UsageGenerator.INLINE_HELP)));
return true; // finished
}
// Parse command arguments
Arguments arguments;
try {
arguments = new Arguments(command, rawArguments);
} catch (CommandException e) {
NucMsg.tell(getPlugin(), sender, ERROR_TEMPLATE, e.getMessage());
return true; // finished
}
// execute the command
try {
command.execute(sender, arguments);
}
catch (CommandException e) {
NucMsg.tell(getPlugin(), sender, ERROR_TEMPLATE, e.getMessage());
}
return true;
}
@Override
public List<String> onTabComplete(CommandSender sender, Command cmd, String s, String[] args) {
if (args.length == 0)
return new ArrayList<>(0);
RegisteredCommand rootCommand = _rootCommands.getCommand(cmd.getName());
if (rootCommand == null) {
rootCommand = _defaultRoot;
}
CommandParser _parser = new CommandParser(rootCommand);
ParsedTabComplete parsed = _parser.parseTabComplete(rootCommand, sender, args);
RegisteredCommand command = parsed.getCommand();
String[] arguments = parsed.getArguments();
List<String> matches = parsed.getMatches();
if (command != null) {
// give the command the opportunity to modify the list
command.onTabComplete(
sender,
arguments,
matches);
}
// add the command help
if (command != null && matches.size() > 1 &&
(arguments.length == 0 ||
(arguments.length == 1 && arguments[0].isEmpty()))) {
parsed.getMatches().add("?");
}
return parsed.getMatches();
}
@Override
public boolean registerCommand(Class<? extends ICommand> commandClass) {
PreCon.notNull(commandClass);
String rootName = _rootCommands.addCommand(commandClass);
if (rootName == null) {
_msg.debug("Failed to register command '{0}' possibly because another command with the " +
"same name is already registered and no alternative command names were provided.",
commandClass.getName());
return false;
}
RegisteredCommand command = _rootCommands.getCommand(rootName);
if (command == null)
throw new AssertionError();
if (!_pluginCommands.contains(rootName)) {
_rootCommands.removeAll(command);
if (!_defaultRoot.registerCommand(commandClass))
return false;
command = _defaultRoot.getCommandCollection().getCommand(commandClass);
if (command == null)
throw new AssertionError();
}
command.setDispatcher(this, null);
return true;
}
@Override
public boolean unregisterCommand(Class<? extends ICommand> commandClass) {
if (!_rootCommands.unregisterCommand(commandClass) &&
!_defaultRoot.unregisterCommand(commandClass)) {
return false;
}
return true;
}
@Nullable
@Override
public IRegisteredCommand getCommand(String commandName) {
RegisteredCommand result = _rootCommands.getCommand(commandName);
if (result == null) {
result = _defaultRoot.getCommand(commandName);
}
return result;
}
/**
* Get all root commands.
*/
@Override
public Collection<IRegisteredCommand> getCommands() {
return getCommands(new ArrayList<IRegisteredCommand>(_rootCommands.size() + 1));
}
@Override
public <T extends Collection<IRegisteredCommand>> T getCommands(T output) {
_rootCommands.getCommands(output);
output.add(_defaultRoot);
return output;
}
/**
* Get the names of the root commands.
*/
@Override
public Collection<String> getCommandNames() {
return _rootCommands.getCommandNames();
}
@Override
public <T extends Collection<String>> T getCommandNames(T output) {
return _rootCommands.getCommandNames(output);
}
// determine if the arguments provided are
// to view a commands help.
private boolean isCommandHelp(String[] args) {
return args.length > 0 &&
((args[0].equals("?")) || args[0].equalsIgnoreCase("help"));
}
// determine if the arguments provided are
// to view a commands detailed help.
private boolean isDetailedHelp(String[] args) {
return args.length > 0 &&
((args[0].equals("??")));
}
}