/*
* 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.managed.commands.CommandInfo;
import com.jcwhatever.nucleus.managed.commands.ICommand;
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.IInitializableCommand;
import com.jcwhatever.nucleus.managed.commands.mixins.ITabCompletable;
import com.jcwhatever.nucleus.managed.commands.mixins.IVisibleCommand;
import com.jcwhatever.nucleus.managed.commands.parameters.ICommandParameter;
import com.jcwhatever.nucleus.managed.commands.parameters.IFlagParameter;
import com.jcwhatever.nucleus.managed.commands.utils.ICommandUsageGenerator;
import com.jcwhatever.nucleus.managed.language.Localizable;
import com.jcwhatever.nucleus.managed.messaging.ChatPaginator;
import com.jcwhatever.nucleus.managed.messaging.IMessenger;
import com.jcwhatever.nucleus.providers.permissions.IPermission;
import com.jcwhatever.nucleus.providers.permissions.Permissions;
import com.jcwhatever.nucleus.utils.PreCon;
import com.jcwhatever.nucleus.utils.text.TextUtils;
import com.jcwhatever.nucleus.utils.text.TextUtils.FormatTemplate;
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.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* Base implementation of a command
*
* <p>The command implementation must have an {@link CommandInfo} annotation.</p>
*/
class RegisteredCommand implements IRegisteredCommand {
@Localizable static final String _COMMAND_PAGINATOR_TITLE = "Commands";
@Localizable static final String _STATIC_PARAMETER_HEADER = "Static Parameters";
@Localizable static final String _STATIC_PARAMETER_ITEM_LINE1 = "{GOLD}<{0: parameter name}>";
@Localizable static final String _STATIC_PARAMETER_ITEM_LINE2 = "{GRAY}{0: description}";
@Localizable static final String _FLOATING_PARAMETER_HEADER = "Floating Parameters";
@Localizable static final String _FLOATING_PARAMETER_ITEM_LINE1 = "{GOLD}--{0: parameter name} <value>";
@Localizable static final String _FLOATING_PARAMETER_ITEM_LINE2 = "{GRAY}{0: description}";
@Localizable static final String _FLAG_PARAMETER_HEADER = "Flags";
@Localizable static final String _FLAG_PARAMETER_ITEM_LINE1 = "{GOLD}-{0: flag name}";
@Localizable static final String _FLAG_PARAMETER_ITEM_LINE2 = "{GRAY}{0: description}";
private final Plugin _plugin;
private final ICommand _command;
private final CommandCollection _subCommands;
private final UsageGenerator _usageGenerator = new UsageGenerator();
private final PaginatorHelpCommands _paginCommands;
private RegisteredCommandInfo _info;
private CommandDispatcher _dispatcher;
private RegisteredCommand _parent;
private IPermission _permission;
protected IMessenger _msg;
private Set<Class<? extends ICommand>> _subCommandQueue = new HashSet<>(20);
/**
* Constructor.
*/
public RegisteredCommand(Plugin plugin, ICommand command, ICommandContainerFactory commandFactory) {
super();
PreCon.notNull(plugin);
PreCon.notNull(command);
PreCon.notNull(commandFactory);
_plugin = plugin;
_command = command;
_subCommands = new CommandCollection(plugin, commandFactory);
_paginCommands = new PaginatorHelpCommands(this);
if (_command instanceof IInitializableCommand) {
((IInitializableCommand) _command).init(this);
}
}
/**
* Get the encapsulated command handler.
*/
@Override
public ICommand getCommand() {
return _command;
}
@Override
public Plugin getPlugin() {
return _plugin;
}
@Override
public RegisteredCommandInfo getInfo() {
return _info;
}
@Override
public CommandDispatcher getDispatcher() {
return _dispatcher;
}
@Override
@Nullable
public RegisteredCommand getParent() {
return _parent;
}
/**
* Execute the command.
*/
public void execute(CommandSender sender, Arguments args)
throws CommandException {
if (_command instanceof IExecutableCommand)
((IExecutableCommand)_command).execute(sender, args);
}
/**
* Invoked to get a list of possible tab complete values from the command based
* on the current text.
*
* <p>Intended to be overridden by implementation if needed.</p>
*
* @param sender The command sender.
* @param arguments This command arguments currently entered by the command sender.
* not including the command and command path.
* @param completions The list of completions.
*/
public void onTabComplete(
CommandSender sender, String[] arguments, Collection<String> completions) {
if (_command instanceof ITabCompletable)
((ITabCompletable)_command).onTabComplete(sender, arguments, completions);
}
@Override
@Nullable
public RegisteredCommand getCommand(String subCommandName) {
PreCon.notNullOrEmpty(subCommandName);
return _subCommands.getCommand(subCommandName);
}
@Override
public Collection<IRegisteredCommand> getCommands() {
return _subCommands.getCommands();
}
@Override
public <T extends Collection<IRegisteredCommand>> T getCommands(T output) {
return _subCommands.getCommands(output);
}
@Override
public Collection<String> getCommandNames() {
return _subCommands.getCommandNames();
}
@Override
public <T extends Collection<String>> T getCommandNames(T output) {
return _subCommands.getCommandNames(output);
}
@Override
public boolean registerCommand(Class<? extends ICommand> subCommandClass) {
PreCon.notNull(subCommandClass);
if (subCommandClass.equals(_command.getClass())) {
throw new IllegalStateException("Cannot register a command as a sub command of itself.");
}
// add sub command to registration queue if not ready to register it yet
if (getInfo() == null) {
_subCommandQueue.add(subCommandClass);
return true;
}
String commandName = _subCommands.addCommand(subCommandClass);
if (commandName == null) {
NucMsg.debug(getPlugin(),
"Failed to register command '{0}' as a sub command of '{1}' possibly because " +
"another command with the same name is already registered and no alternative " +
"command names were provided.", subCommandClass.getName(), getClass().getName());
return false;
}
RegisteredCommand command = _subCommands.getCommand(commandName);
if (command == null)
throw new AssertionError();
// set the instance's parent command
command._parent = this;
// set the instance's command handler
if (_dispatcher != null)
command.setDispatcher(_dispatcher, getInfo().getRoot() != null ? getInfo().getRoot() : this);
// Sanity check. Make sure the parent specified by the command is this command
if (!command.getInfo().getParentName().isEmpty() &&
!isCommandMatch(command.getInfo().getParentName(), getInfo().getCommandNames())) {
NucMsg.debug(getPlugin(),
"Failed to register sub command '{0}'. Registered with incorrect parent: {1}",
command.getClass().getName(), this.getClass().getName());
_subCommands.removeAll(command);
return false;
}
return true;
}
@Override
public boolean unregisterCommand(Class<? extends ICommand> commandClass) {
CommandInfo commandInfo = commandClass.getAnnotation(CommandInfo.class);
if (commandInfo == null) {
throw new RuntimeException(
"Could not find required CommandInfo annotation for command class: " +
commandClass.getName());
}
for (String commandName : commandInfo.command())
_subCommands.remove(commandName.trim().toLowerCase());
return true;
}
@Override
public IPermission getPermission() {
// lazy loaded
if (_permission == null) {
List<String> permissions = new ArrayList<>(20);
RegisteredCommand parent = this;
permissions.add(getInfo().getName().toLowerCase());
while((parent = parent.getParent()) != null) {
permissions.add(parent.getInfo().getName().toLowerCase());
}
if (permissions.get(permissions.size() - 1).equals("about"))
permissions.remove(permissions.size() - 1);
permissions.add(getPlugin().getName().toLowerCase());
Collections.reverse(permissions);
String permissionName = TextUtils.concat(permissions, ".");
_permission = Permissions.register(permissionName, getInfo().getPermissionDefault(), true);
}
return _permission;
}
/**
* Show help for a commands parameters.
*
* @param sender The command sender to show the help to.
* @param page The help page.
*/
public void showDetailedHelp(CommandSender sender, int page) {
List<ICommandParameter> staticParams = getInfo().getStaticParams();
List<ICommandParameter> floatingParams = getInfo().getFloatingParams();
List<IFlagParameter> flagParams = getInfo().getFlagParams();
ParameterDescriptions paramDescriptions = getInfo().getParamDescriptions();
ChatPaginator pagin = new ChatPaginator(getPlugin(), 6, _paginCommands,
NucLang.get(getPlugin(), _COMMAND_PAGINATOR_TITLE));
String description = getInfo().getLongDescription().isEmpty()
? getInfo().getDescription()
: getInfo().getLongDescription();
String template = paramDescriptions.isEmpty()
? UsageGenerator.HELP_USAGE
: UsageGenerator.PARAMETER_HELP;
// add usage and description
pagin.add(_usageGenerator.generate(this, template), description);
if (!paramDescriptions.isEmpty()) {
// add static parameter descriptions
if (!staticParams.isEmpty()) {
pagin.addFormatted(FormatTemplate.SUB_HEADER, NucLang.get(getPlugin(), _STATIC_PARAMETER_HEADER));
for (ICommandParameter parameter : staticParams) {
ParameterDescription paramDesc = paramDescriptions.get(parameter.getName());
if (paramDesc == null) {
_msg.debug("Missing static parameter description for '{0}' in command '{1}'",
parameter.getName(), getClass().getName());
continue;
}
pagin.addFormatted(NucLang.get(getPlugin(), _STATIC_PARAMETER_ITEM_LINE1,
paramDesc.getName(), paramDesc.getDescription()));
pagin.addFormatted(NucLang.get(getPlugin(), _STATIC_PARAMETER_ITEM_LINE2,
paramDesc.getDescription()));
}
}
// add floating parameter descriptions
if (!floatingParams.isEmpty()) {
pagin.addFormatted(FormatTemplate.SUB_HEADER, NucLang.get(getPlugin(), _FLOATING_PARAMETER_HEADER));
for (ICommandParameter parameter : floatingParams) {
ParameterDescription paramDesc = paramDescriptions.get(parameter.getName());
if (paramDesc == null) {
_msg.debug("Missing floating parameter description for '{0}' in command '{1}'",
parameter.getName(), getClass().getName());
continue;
}
pagin.addFormatted(NucLang.get(getPlugin(), _FLOATING_PARAMETER_ITEM_LINE1,
paramDesc.getName(), paramDesc.getDescription()));
pagin.addFormatted(NucLang.get(getPlugin(), _FLOATING_PARAMETER_ITEM_LINE2,
paramDesc.getDescription()));
}
}
// add flag parameter descriptions
if (!flagParams.isEmpty()) {
pagin.addFormatted(FormatTemplate.SUB_HEADER, NucLang.get(getPlugin(), _FLAG_PARAMETER_HEADER));
for (IFlagParameter parameter : flagParams) {
ParameterDescription paramDesc = paramDescriptions.get(parameter.getName());
if (paramDesc == null) {
_msg.debug("Missing flag description for '{0}' in command '{1}'",
parameter.getName(), getClass().getName());
continue;
}
pagin.addFormatted(NucLang.get(getPlugin(), _FLAG_PARAMETER_ITEM_LINE1,
paramDesc.getName(), paramDesc.getDescription()));
pagin.addFormatted(NucLang.get(getPlugin(), _FLAG_PARAMETER_ITEM_LINE2,
paramDesc.getDescription()));
}
}
}
pagin.show(sender, page, FormatTemplate.CONSTANT_DEFINITION);
}
/**
* Display the commands help info in a paginated list that includes
* the sub command help to the specified {@link CommandSender}
*/
public void showHelp(CommandSender sender, int page) {
PreCon.notNull(sender);
PreCon.positiveNumber(page);
ChatPaginator pagin = new ChatPaginator(getPlugin(), 6, _paginCommands,
NucLang.get(getPlugin(), _COMMAND_PAGINATOR_TITLE));
if (_command instanceof IExecutableCommand && isHelpVisible(sender)) {
// add command to paginator
pagin.add(
_usageGenerator.generate(this, ICommandUsageGenerator.HELP_USAGE),
getInfo().getDescription());
}
List<RegisteredCommand> subCommands = new ArrayList<>(20);
for (IRegisteredCommand regcmd : getCommands()) {
RegisteredCommand cmd = (RegisteredCommand)regcmd;
// Determine if the command has its own own sub commands
// and put aside so it can be displayed at the end of the
// help list
if (cmd.getCommands().size() > 0) {
subCommands.add(cmd);
continue;
}
if (!cmd.isHelpVisible(sender)) {
continue;
}
RegisteredCommandInfo info = cmd.getInfo();
// add command to paginator
pagin.add(_usageGenerator.generate(cmd), info.getDescription());
}
// Add commands that were set aside because they have sub commands
// and render differently
for (RegisteredCommand cmd : subCommands) {
if (!cmd.isHelpVisible(sender)) {
continue;
}
RegisteredCommandInfo info = cmd.getInfo();
// add info to get sub commands help to paginator
pagin.add(_usageGenerator.generate(cmd), info.getDescription());
}
// show paginator to CommandSender
pagin.show(sender, page, FormatTemplate.CONSTANT_DEFINITION);
}
/**
* Compare command names
* Used for alphabetical sorting of commands.
*/
@Override
public int compareTo(IRegisteredCommand o) {
return getInfo().getName().compareTo(o.getInfo().getName());
}
/**
* Get the command collection.
*/
protected CommandCollection getCommandCollection() {
return _subCommands;
}
/*
* Determine if the supplied command name matches one of the
* command names of the this command.
*/
private boolean isCommandMatch(@Nullable String parentName, String[] possibleNames) {
PreCon.notNull(possibleNames);
if (parentName == null)
return false;
for (String possibleName : possibleNames) {
if (parentName.equalsIgnoreCase(possibleName))
return true;
}
return false;
}
/*
* Set the commands dispatcher. Initializes command.
*/
final void setDispatcher(CommandDispatcher dispatcher, @Nullable RegisteredCommand rootCommand) {
PreCon.notNull(dispatcher);
if (_dispatcher != null)
return;
_dispatcher = dispatcher;
_msg = Nucleus.getMessengerFactory().create(dispatcher.getPlugin());
_info = new RegisteredCommandInfo(this, rootCommand);
// register queued sub commands
for (Class<? extends ICommand> commandClass : _subCommandQueue) {
registerCommand(commandClass);
}
// remove queue
_subCommandQueue = null;
// set command handler in sub commands
for (IRegisteredCommand subCommand : getCommands()) {
if (subCommand.getDispatcher() == null) {
((RegisteredCommand)subCommand)
.setDispatcher(_dispatcher, rootCommand != null ? rootCommand : this);
}
}
// register permission
getPermission();
}
/**
* Determine if a command sender can see the command in help.
*/
protected boolean isHelpVisible(CommandSender sender) {
// determine if the CommandSender has permission to use the command
if (sender instanceof Player
&& !Permissions.has((Player) sender, getPermission().getName())) {
return false;
}
// determine if the commands is visible in help
return getInfo().isHelpVisible()
&& (!(_command instanceof IVisibleCommand)
|| ((IVisibleCommand) _command).isVisible(sender));
}
}