diff --git a/src/main/java/net/pingex/dcf/commands/Command.java b/src/main/java/net/pingex/dcf/commands/Command.java new file mode 100644 index 0000000..dc740a2 --- /dev/null +++ b/src/main/java/net/pingex/dcf/commands/Command.java @@ -0,0 +1,191 @@ +package net.pingex.dcf.commands; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * Represents a single command. + */ +public abstract class Command implements ICommandExecutor +{ + /** + * Main name of the command + */ + private String name; + + /** + * Command can also be called using the following list of aliases + */ + private List aliases; + + /** + * Description of the command. + */ + private String description; + + /** + * Is the command enabled ? Can it be invoked ? + */ + private boolean isEnabled; + + /** + * Command usage help + */ + private String usage; + + /** + * Basic constructor. + * @param name Name of the command + * @param aliases Aliases, if any + * @param description Description of the command + * @param isEnabled Is the command enabled ? + * @param usage Command usage help + */ + public Command(String name, List aliases, String description, boolean isEnabled, String usage) + { + this.name = name; + this.aliases = aliases; + this.description = description; + this.isEnabled = isEnabled; + this.usage = usage; + } + + /** + * Contains Command default values + */ + public static class Defaults + { + public static final List ALIASES = Collections.emptyList(); + public static final String DESCRIPTION = "No command description provided."; + public static final boolean IS_ENABLED = true; + public static final String USAGE = "No command usage provided."; + } + + /** + * Builder for a command + */ + public static class Builder + { + /** + * Main name of the command + */ + private String name; + + /** + * Command can also be called using the following list of aliases + */ + private List aliases = Defaults.ALIASES; + + /** + * Description of the command. + */ + private String description = Defaults.DESCRIPTION; + + /** + * Is the command enabled ? Can it be invoked ? + */ + private boolean isEnabled = Defaults.IS_ENABLED; + + /** + * Command usage help + */ + private String usage = Defaults.USAGE; + + public Builder(String name) + { + this.name = name; + } + + public Builder aliases(List aliases) + { + this.aliases = aliases; + return this; + } + + /** + * Set aliases using varargs + * @param aliases Aliases varargs + */ + public Builder aliases(String... aliases) + { + this.aliases = Arrays.asList(aliases); + return this; + } + + public Builder description(String description) + { + this.description = description; + return this; + } + + public Builder enabled(boolean enabled) + { + isEnabled = enabled; + return this; + } + + public Builder usage(String usage) + { + this.usage = usage; + return this; + } + + /** + * Build a new Command using a supplied executor. + * @param toExecute The body of the command. + * @return Built command. + */ + public Command build(ICommandExecutor toExecute) + { + return new Command(name, aliases, description, isEnabled, usage) + { + @Override + public void execute(List arguments) + { + toExecute.execute(arguments); + } + }; + } + } + + /** + * Get a builder for a new command. + * @param name The name of the future command + * @return A new manipulable builder to build the command. + */ + public static Builder builder(String name) + { + return new Builder(name); + } + + public String getName() + { + return name; + } + + public List getAliases() + { + return aliases; + } + + public String getDescription() + { + return description; + } + + public boolean isEnabled() + { + return isEnabled; + } + + public String getUsage() + { + return usage; + } + + public void setEnabled(boolean enabled) + { + isEnabled = enabled; + } +} diff --git a/src/main/java/net/pingex/dcf/commands/CommandRegistry.java b/src/main/java/net/pingex/dcf/commands/CommandRegistry.java new file mode 100644 index 0000000..6a908ce --- /dev/null +++ b/src/main/java/net/pingex/dcf/commands/CommandRegistry.java @@ -0,0 +1,140 @@ +package net.pingex.dcf.commands; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.*; + +/** + * Command bank + */ +public class CommandRegistry +{ + /** + * Command main store + */ + private static Set commandSet = new HashSet<>(); + + /** + * Logger + */ + private static final Logger LOGGER = LogManager.getLogger(CommandRegistry.class); + + /** + * Register a command and its aliases to the bank + * @param toRegister Command to register + */ + public static void registerCommand(Command toRegister) + { + LOGGER.debug("Attempting to register command {}.", toRegister.getName()); + + if(commandExists(toRegister.getName())) + { + LOGGER.warn("Fail to add command: {} is already registered as a command.", toRegister.getName()); + return; + } + if(aliasExists(toRegister.getName())) LOGGER.info("Overriding registered alias {} for a new command with the same name.", toRegister.getName()); + + LOGGER.info("Registering command {}.", toRegister.getName()); + commandSet.add(toRegister); + + // Check for conflicting aliases + toRegister.getAliases().stream().filter(CommandRegistry::aliasExists).forEach(i -> + { + LOGGER.warn("Conflicting alias {} for command {}, removing. (original command: {})", i, toRegister.getName(), getAliasByName(i).get().getName()); + toRegister.getAliases().remove(i); + }); + } + + /** + * Remove a command from the bank. + * A command cannot be removed using one of its alias. + * @param commandName Target command + */ + public static void unregisterCommandByName(String commandName) + { + LOGGER.debug("Resolving command {}.", commandName); + Optional target = getCommandByName(commandName); + + if(target.isPresent()) unregisterCommand(target.get()); + else LOGGER.warn("Attempting to remove a command which is unknown to DCF."); + } + + /** + * Remove a command from the bank. + * @param toUnregister Target command + */ + public static void unregisterCommand(Command toUnregister) + { + LOGGER.debug("Attempting to remove command {} from the bank.", toUnregister.getName()); + if(!commandSet.contains(toUnregister)) + { + LOGGER.warn("Attempting to remove a command which is unknown to DCF."); + return; + } + + commandSet.remove(toUnregister); + LOGGER.info("Removed command {}.", toUnregister.getName()); + } + + /** + * Tells if the command is registered (excluding aliases). + * @param commandName Target command + * @return `true` if the command is registered, `false` otherwise + */ + public static boolean commandExists(String commandName) + { + return commandSet.stream().anyMatch(e -> e.getName().equals(commandName)); + } + + /** + * Tells if the alias is registered. + * @param aliasName Target alias + * @return `true` if the alias is registered, `false` otherwise + */ + public static boolean aliasExists(String aliasName) + { + return commandSet.stream().anyMatch(e -> e.getAliases().contains(aliasName)); + } + + /** + * Tells if the command exists (including aliases). + * Command have precedence over aliases. + * @param commandName Target command + * @return `true` if the command exists, `false` otherwise + */ + public static boolean exists(String commandName) + { + return commandExists(commandName) || aliasExists(commandName); + } + + /** + * Get a command or an alias designed by its name. Commands have precedence over aliases. + * @param commandName Target command + * @return {@code Optional.empty()} if no command was found, {@code Optional.of(Command)} otherwise + */ + public static Optional getCommandOrAliasByName(String commandName) + { + return commandExists(commandName) ? getCommandByName(commandName) : getAliasByName(commandName); + } + + /** + * Get a command by one of its aliases. + * @param aliasName Target command with this alias. + * @return {@code Optional.empty()} if no command was found, {@code Optional.of(Command)} otherwise + */ + public static Optional getAliasByName(String aliasName) + { + return commandSet.stream().filter(e -> e.getAliases().contains(aliasName)).findFirst(); + } + + /** + * Get a command designed by its name, excluding aliases. + * @param commandName Target command + * @return {@code Optional.empty()} if no command was found, {@code Optional.of(Command)} otherwise + */ + public static Optional getCommandByName(String commandName) + { + return commandSet.stream().filter(e -> e.getName().equals(commandName)).findFirst(); + } +} diff --git a/src/main/java/net/pingex/dcf/commands/ICommandExecutor.java b/src/main/java/net/pingex/dcf/commands/ICommandExecutor.java new file mode 100644 index 0000000..6a8f433 --- /dev/null +++ b/src/main/java/net/pingex/dcf/commands/ICommandExecutor.java @@ -0,0 +1,12 @@ +package net.pingex.dcf.commands; + +import java.util.List; + +/** + * The body of a command. + */ +@FunctionalInterface +public interface ICommandExecutor +{ + void execute(List arguments); +}