Basic bukkit support

This commit is contained in:
Alexander Söderberg 2020-09-06 17:55:46 +02:00
parent 762bdb7ff4
commit 3ec124aac4
No known key found for this signature in database
GPG key ID: C0207FF7EA146678
27 changed files with 516 additions and 47 deletions

View file

@ -0,0 +1,197 @@
//
// MIT License
//
// Copyright (c) 2020 IntellectualSites
//
// 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.intellectualsites.commands;
import com.intellectualsites.commands.components.CommandComponent;
import com.intellectualsites.commands.components.StaticComponent;
import com.intellectualsites.commands.execution.CommandExecutionHandler;
import com.intellectualsites.commands.sender.CommandSender;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
/**
* A command consists out of a chain of {@link com.intellectualsites.commands.components.CommandComponent command components}.
*
* @param <C> Command sender type
*/
public class Command<C extends CommandSender> {
@Nonnull private final List<CommandComponent<C, ?>> components;
@Nonnull private final CommandExecutionHandler<C> commandExecutionHandler;
@Nullable private final Class<? extends C> senderType;
protected Command(@Nonnull final List<CommandComponent<C, ?>> commandComponents,
@Nonnull final CommandExecutionHandler<C> commandExecutionHandler,
@Nullable final Class<? extends C> senderType) {
this.components = Objects.requireNonNull(commandComponents, "Command components may not be null");
if (this.components.size() == 0) {
throw new IllegalArgumentException("At least one command component is required");
}
// Enforce ordering of command components
boolean foundOptional = false;
for (final CommandComponent<C, ?> component : this.components) {
if (foundOptional && component.isRequired()) {
throw new IllegalArgumentException(
String.format("Command component '%s' cannot be placed after an optional component",
component.getName()));
} else if (!component.isRequired()) {
foundOptional = true;
}
}
this.commandExecutionHandler = commandExecutionHandler;
this.senderType = senderType;
}
/**
* Create a new command builder
*
* @param commandName Base command component
* @return Command builder
*/
@Nonnull
public static <C extends CommandSender> Builder<C> newBuilder(@Nonnull final String commandName) {
return new Builder<>(null, Collections.singletonList(StaticComponent.required(commandName)),
new CommandExecutionHandler.NullCommandExecutionHandler<>());
}
/**
* Return a copy of the command component array
*
* @return Copy of the command component array
*/
@Nonnull
public List<CommandComponent<C, ?>> getComponents() {
return Collections.unmodifiableList(this.components);
}
/**
* Get the command execution handler
*
* @return Command execution handler
*/
@Nonnull
public CommandExecutionHandler<C> getCommandExecutionHandler() {
return this.commandExecutionHandler;
}
/**
* Get the required sender type, if one has been specified
*
* @return Required sender type
*/
@Nonnull
public Optional<Class<? extends C>> getSenderType() {
return Optional.ofNullable(this.senderType);
}
/**
* Get the longest chain of similar components for
* two commands
*
* @return List containing the longest shared component chain
*/
public List<CommandComponent<C, ?>> getSharedComponentChain(@Nonnull final Command<C> other) {
final List<CommandComponent<C, ?>> commandComponents = new LinkedList<>();
for (int i = 0; i < this.components.size() && i < other.components.size(); i++) {
if (this.components.get(i).equals(other.components.get(i))) {
commandComponents.add(this.components.get(i));
} else {
break;
}
}
return commandComponents;
}
public static class Builder<C extends CommandSender> {
@Nonnull private final List<CommandComponent<C, ?>> commandComponents;
@Nonnull private final CommandExecutionHandler<C> commandExecutionHandler;
@Nullable private final Class<? extends C> senderType;
private Builder(@Nullable final Class<? extends C> senderType,
@Nonnull final List<CommandComponent<C, ?>> commandComponents,
@Nonnull final CommandExecutionHandler<C> commandExecutionHandler) {
this.commandComponents = commandComponents;
this.commandExecutionHandler = commandExecutionHandler;
this.senderType = senderType;
}
/**
* Add a new command component to the command
*
* @param component Component to add
* @param <T> Component type
* @return New builder instance with the command component inserted into the component list
*/
@Nonnull
public <T> Builder<C> withComponent(@Nonnull final CommandComponent<C, T> component) {
final List<CommandComponent<C, ?>> commandComponents = new LinkedList<>(this.commandComponents);
commandComponents.add(component);
return new Builder<>(this.senderType, commandComponents, this.commandExecutionHandler);
}
/**
* Specify the command execution handler
*
* @param commandExecutionHandler New execution handler
* @return New builder instance using the command execution handler
*/
@Nonnull
public Builder<C> withHandler(@Nonnull final CommandExecutionHandler<C> commandExecutionHandler) {
return new Builder<>(this.senderType, this.commandComponents, commandExecutionHandler);
}
/**
* Specify a required sender type
*
* @param senderType Required sender type
* @return New builder instance using the command execution handler
*/
@Nonnull
public Builder<C> withSenderType(@Nonnull final Class<? extends C> senderType) {
return new Builder<>(senderType, this.commandComponents, this.commandExecutionHandler);
}
/**
* Build a command using the builder instance
*
* @return Built command
*/
@Nonnull
public Command<C> build() {
return new Command<>(Collections.unmodifiableList(this.commandComponents),
this.commandExecutionHandler,
this.senderType);
}
}
}

View file

@ -0,0 +1,116 @@
//
// MIT License
//
// Copyright (c) 2020 IntellectualSites
//
// 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.intellectualsites.commands;
import com.intellectualsites.commands.components.CommandSyntaxFormatter;
import com.intellectualsites.commands.components.StandardCommandSyntaxFormatter;
import com.intellectualsites.commands.context.CommandContext;
import com.intellectualsites.commands.execution.CommandExecutionCoordinator;
import com.intellectualsites.commands.execution.CommandResult;
import com.intellectualsites.commands.internal.CommandRegistrationHandler;
import com.intellectualsites.commands.sender.CommandSender;
import javax.annotation.Nonnull;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.StringTokenizer;
import java.util.concurrent.CompletableFuture;
import java.util.function.Function;
/**
* The manager is responsible for command registration, parsing delegation, etc.
*
* @param <C> Command sender type
*/
public abstract class CommandManager<C extends CommandSender> {
private final CommandExecutionCoordinator<C> commandExecutionCoordinator;
private final CommandRegistrationHandler commandRegistrationHandler;
private final CommandTree<C> commandTree;
private CommandSyntaxFormatter<C> commandSyntaxFormatter = new StandardCommandSyntaxFormatter<>();
protected CommandManager(@Nonnull final Function<CommandTree<C>, CommandExecutionCoordinator<C>> commandExecutionCoordinator,
@Nonnull final CommandRegistrationHandler commandRegistrationHandler) {
this.commandTree = CommandTree.newTree(this, commandRegistrationHandler);
this.commandExecutionCoordinator = commandExecutionCoordinator.apply(commandTree);
this.commandRegistrationHandler = commandRegistrationHandler;
}
public CompletableFuture<CommandResult> executeCommand(@Nonnull final C commandSender, @Nonnull final String input) {
final CommandContext<C> context = new CommandContext<>(commandSender);
return this.commandExecutionCoordinator.coordinateExecution(context, tokenize(input));
}
public List<String> suggest(@Nonnull final C commandSender, @Nonnull final String input) {
final CommandContext<C> context = new CommandContext<>(commandSender);
return this.commandTree.getSuggestions(context, tokenize(input));
}
@Nonnull
private Queue<String> tokenize(@Nonnull final String input) {
final StringTokenizer stringTokenizer = new StringTokenizer(input, " ");
final Queue<String> tokens = new LinkedList<>();
while (stringTokenizer.hasMoreElements()) {
tokens.add(stringTokenizer.nextToken());
}
return tokens;
}
/**
* Register a new command
*
* @param command Command to register
* @return The command manager instance
*/
public CommandManager<C> registerCommand(@Nonnull final Command<C> command) {
this.commandTree.insertCommand(command);
return this;
}
/**
* Get the command syntax formatter
*
* @return Command syntax formatter
*/
@Nonnull
public CommandSyntaxFormatter<C> getCommandSyntaxFormatter() {
return this.commandSyntaxFormatter;
}
/**
* Set the command syntax formatter
*
* @param commandSyntaxFormatter New formatter
*/
public void setCommandSyntaxFormatter(@Nonnull final CommandSyntaxFormatter commandSyntaxFormatter) {
this.commandSyntaxFormatter = commandSyntaxFormatter;
}
@Nonnull protected CommandRegistrationHandler getCommandRegistrationHandler() {
return this.commandRegistrationHandler;
}
}

View file

@ -0,0 +1,411 @@
//
// MIT License
//
// Copyright (c) 2020 IntellectualSites
//
// 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.intellectualsites.commands;
import com.intellectualsites.commands.components.CommandComponent;
import com.intellectualsites.commands.components.StaticComponent;
import com.intellectualsites.commands.context.CommandContext;
import com.intellectualsites.commands.exceptions.InvalidSyntaxException;
import com.intellectualsites.commands.exceptions.NoSuchCommandException;
import com.intellectualsites.commands.internal.CommandRegistrationHandler;
import com.intellectualsites.commands.parser.ComponentParseResult;
import com.intellectualsites.commands.sender.CommandSender;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Queue;
import java.util.stream.Collectors;
/**
* Tree containing all commands and command paths
*
* @param <C> Command sender type
*/
public class CommandTree<C extends CommandSender> {
private final Node<CommandComponent<C, ?>> internalTree = new Node<>(null);
private final CommandManager<C> commandManager;
private final CommandRegistrationHandler commandRegistrationHandler;
private CommandTree(@Nonnull final CommandManager<C> commandManager,
@Nonnull final CommandRegistrationHandler commandRegistrationHandler) {
this.commandManager = commandManager;
this.commandRegistrationHandler = commandRegistrationHandler;
}
/**
* Create a new command tree instance
*
* @param commandManager Command manager
* @param commandRegistrationHandler Command registration handler
* @param <C> Command sender type
* @return New command tree
*/
@Nonnull
public static <C extends CommandSender> CommandTree<C> newTree(@Nonnull final CommandManager<C> commandManager,
@Nonnull
final CommandRegistrationHandler commandRegistrationHandler) {
return new CommandTree<>(commandManager, commandRegistrationHandler);
}
public Optional<Command<C>> parse(@Nonnull final CommandContext<C> commandContext, @Nonnull final Queue<String> args) throws
NoSuchCommandException {
return parseCommand(commandContext, args, this.internalTree);
}
private Optional<Command<C>> parseCommand(@Nonnull final CommandContext<C> commandContext,
@Nonnull final Queue<String> commandQueue,
@Nonnull final Node<CommandComponent<C, ?>> root) throws NoSuchCommandException {
final List<Node<CommandComponent<C, ?>>> children = root.getChildren();
if (children.size() == 1 && !(children.get(0).getValue() instanceof StaticComponent)) {
// The value has to be a variable
final Node<CommandComponent<C, ?>> child = children.get(0);
if (child.getValue() != null) {
if (commandQueue.isEmpty()) {
if (child.isLeaf()) {
/* Not enough arguments */
throw new InvalidSyntaxException(this.commandManager.getCommandSyntaxFormatter()
.apply(Objects.requireNonNull(
child.getValue().getOwningCommand()).getComponents()),
commandContext.getCommandSender(), this.getChain(root)
.stream()
.map(Node::getValue)
.collect(Collectors.toList()));
} else {
throw new NoSuchCommandException(commandContext.getCommandSender(),
this.getChain(root)
.stream()
.map(Node::getValue)
.collect(Collectors.toList()),
"");
}
}
final ComponentParseResult<?> result = child.getValue().getParser().parse(commandContext, commandQueue);
if (result.getParsedValue().isPresent()) {
commandContext.store(child.getValue().getName(), result.getParsedValue().get());
if (child.isLeaf()) {
if (commandQueue.isEmpty()) {
return Optional.ofNullable(child.getValue().getOwningCommand());
} else {
/* Too many arguments. We have a unique path, so we can send the entire context */
throw new InvalidSyntaxException(this.commandManager.getCommandSyntaxFormatter()
.apply(Objects.requireNonNull(child.getValue()
.getOwningCommand())
.getComponents()),
commandContext.getCommandSender(), this.getChain(root)
.stream()
.map(Node::getValue)
.collect(
Collectors.toList()));
}
} else {
return this.parseCommand(commandContext, commandQueue, child);
}
} else if (result.getFailure().isPresent()) {
/* TODO: Return error */
}
}
}
/* There are 0 or more static components as children. No variable child components are present */
if (children.isEmpty()) {
/* We are at the bottom. Check if there's a command attached, in which case we're done */
if (root.getValue() != null && root.getValue().getOwningCommand() != null) {
if (commandQueue.isEmpty()) {
return Optional.of(root.getValue().getOwningCommand());
} else {
/* Too many arguments. We have a unique path, so we can send the entire context */
throw new InvalidSyntaxException(this.commandManager.getCommandSyntaxFormatter()
.apply(root.getValue()
.getOwningCommand()
.getComponents()),
commandContext.getCommandSender(), this.getChain(root)
.stream()
.map(Node::getValue)
.collect(Collectors.toList()));
}
} else {
/* TODO: Indicate that we could not resolve the command here */
final List<CommandComponent<C, ?>> components = this.getChain(root)
.stream()
.map(Node::getValue)
.collect(Collectors.toList());
}
} else {
final Iterator<Node<CommandComponent<C, ?>>> childIterator = root.getChildren().iterator();
if (childIterator.hasNext()) {
while (childIterator.hasNext()) {
final Node<CommandComponent<C, ?>> child = childIterator.next();
if (child.getValue() != null) {
final ComponentParseResult<?> result = child.getValue().getParser().parse(commandContext, commandQueue);
if (result.getParsedValue().isPresent()) {
return this.parseCommand(commandContext, commandQueue, child);
} else if (result.getFailure().isPresent() && root.children.size() == 1) {
}
}
}
}
/* We could not find a match */
throw new NoSuchCommandException(commandContext.getCommandSender(),
getChain(root).stream().map(Node::getValue).collect(Collectors.toList()),
stringOrEmpty(commandQueue.peek()));
}
return Optional.empty();
}
public List<String> getSuggestions(@Nonnull final CommandContext<C> context, @Nonnull final Queue<String> commandQueue) {
return getSuggestions(context, commandQueue, this.internalTree);
}
public List<String> getSuggestions(@Nonnull final CommandContext<C> commandContext,
@Nonnull final Queue<String> commandQueue,
@Nonnull final Node<CommandComponent<C, ?>> root) {
final List<Node<CommandComponent<C, ?>>> children = root.getChildren();
if (children.size() == 1 && !(children.get(0).getValue() instanceof StaticComponent)) {
// The value has to be a variable
final Node<CommandComponent<C, ?>> child = children.get(0);
if (child.getValue() != null) {
if (commandQueue.isEmpty()) {
if (child.isLeaf()) {
/* Child is leaf, and so no suggestions should be sent */
return Collections.emptyList();
} else {
/* Send all suggestions */
return child.getValue().getParser().suggestions(commandContext, "");
}
}
final ComponentParseResult<?> result = child.getValue().getParser().parse(commandContext, commandQueue);
if (result.getParsedValue().isPresent()) {
if (child.isLeaf()) {
/* Child is leaf, and so no suggestions should be sent */
return Collections.emptyList();
}
commandContext.store(child.getValue().getName(), result.getParsedValue().get());
return this.getSuggestions(commandContext, commandQueue, child);
} else if (result.getFailure().isPresent()) {
/* TODO: Return error */
return Collections.emptyList();
}
}
}
/* There are 0 or more static components as children. No variable child components are present */
if (children.isEmpty()) {
return Collections.emptyList();
} else {
final Iterator<Node<CommandComponent<C, ?>>> childIterator = root.getChildren().iterator();
if (childIterator.hasNext()) {
while (childIterator.hasNext()) {
final Node<CommandComponent<C, ?>> child = childIterator.next();
if (child.getValue() != null) {
final ComponentParseResult<?> result = child.getValue().getParser().parse(commandContext, commandQueue);
if (result.getParsedValue().isPresent()) {
return this.getSuggestions(commandContext, commandQueue, child);
} else if (result.getFailure().isPresent() && root.children.size() == 1) {
}
}
}
}
final List<String> suggestions = new LinkedList<>();
for (final Node<CommandComponent<C, ?>> component : root.getChildren()) {
if (component.getValue() == null) {
continue;
}
suggestions.addAll(
component.getValue().getParser().suggestions(commandContext, stringOrEmpty(commandQueue.peek())));
}
return suggestions;
}
}
@Nonnull
private String stringOrEmpty(@Nullable final String string) {
if (string == null) {
return "";
}
return string;
}
/**
* Insert a new command into the command tree
*
* @param command Command to insert
*/
public void insertCommand(@Nonnull final Command<C> command) {
Node<CommandComponent<C, ?>> node = this.internalTree;
for (final CommandComponent<C, ?> component : command.getComponents()) {
Node<CommandComponent<C, ?>> tempNode = node.getChild(component);
if (tempNode == null) {
tempNode = node.addChild(component);
}
if (node.children.size() > 0) {
node.children.sort(Comparator.comparing(Node::getValue));
}
tempNode.setParent(node);
node = tempNode;
}
if (node.getValue() != null) {
node.getValue().setOwningCommand(command);
}
// Verify the command structure every time we add a new command
this.verifyAndRegister();
}
/**
* Go through all commands and register them, and verify the
* command tree contracts
*/
public void verifyAndRegister() {
// All top level commands are supposed to be registered in the command manager
this.internalTree.children.stream().map(Node::getValue).forEach(commandComponent -> {
if (!(commandComponent instanceof StaticComponent)) {
throw new IllegalStateException("Top level command component cannot be a variable");
}
});
this.checkAmbiguity(this.internalTree);
// Verify that all leaf nodes have command registered
this.getLeaves(this.internalTree).forEach(leaf -> {
if (leaf.getOwningCommand() == null) {
// TODO: Custom exception type
throw new IllegalStateException("Leaf node does not have associated owning command");
} else {
this.commandRegistrationHandler.registerCommand(leaf.getOwningCommand());
}
});
/* TODO: Figure out a way to register all combinations along a command component path */
}
private void checkAmbiguity(@Nonnull final Node<CommandComponent<C, ?>> node) {
if (node.isLeaf()) {
return;
}
final int size = node.children.size();
for (final Node<CommandComponent<C, ?>> child : node.children) {
if (child.getValue() != null && !child.getValue().isRequired() && size > 1) {
// TODO: Use a custom exception type here
throw new IllegalStateException("Ambiguous command node found: " + node.getValue());
}
}
node.children.forEach(this::checkAmbiguity);
}
private List<CommandComponent<C, ?>> getLeaves(@Nonnull final Node<CommandComponent<C, ?>> node) {
final List<CommandComponent<C, ?>> leaves = new LinkedList<>();
if (node.isLeaf()) {
if (node.getValue() != null) {
leaves.add(node.getValue());
}
} else {
node.children.forEach(child -> leaves.addAll(getLeaves(child)));
}
return leaves;
}
private List<Node<CommandComponent<C, ?>>> getChain(@Nullable final Node<CommandComponent<C, ?>> end) {
final List<Node<CommandComponent<C, ?>>> chain = new LinkedList<>();
Node<CommandComponent<C, ?>> tail = end;
while (tail != null) {
chain.add(tail);
tail = tail.getParent();
}
Collections.reverse(chain);
return chain;
}
private static final class Node<T> {
private final List<Node<T>> children = new LinkedList<>();
private final T value;
private Node<T> parent;
private Node(@Nullable final T value) {
this.value = value;
}
private List<Node<T>> getChildren() {
return Collections.unmodifiableList(this.children);
}
@Nonnull
private Node<T> addChild(@Nonnull final T child) {
final Node<T> node = new Node<>(child);
this.children.add(node);
return node;
}
@Nullable
private Node<T> getChild(@Nonnull final T type) {
for (final Node<T> child : this.children) {
if (type.equals(child.getValue())) {
return child;
}
}
return null;
}
private boolean isLeaf() {
return this.children.isEmpty();
}
@Nullable
public T getValue() {
return this.value;
}
@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final Node<?> node = (Node<?>) o;
return Objects.equals(getChildren(), node.getChildren()) &&
Objects.equals(getValue(), node.getValue());
}
@Override
public int hashCode() {
return Objects.hash(getChildren(), getValue());
}
@Nullable
public Node<T> getParent() {
return this.parent;
}
public void setParent(@Nullable final Node<T> parent) {
this.parent = parent;
}
}
}

View file

@ -0,0 +1,257 @@
//
// MIT License
//
// Copyright (c) 2020 IntellectualSites
//
// 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.intellectualsites.commands.components;
import com.intellectualsites.commands.Command;
import com.intellectualsites.commands.parser.ComponentParser;
import com.intellectualsites.commands.sender.CommandSender;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Objects;
import java.util.regex.Pattern;
/**
* A component that belongs to a command
*
* @param <C> Command sender type
* @param <T> The type that the component parses into
*/
public class CommandComponent<C extends CommandSender, T> implements Comparable<CommandComponent<?, ?>> {
private static final Pattern NAME_PATTERN = Pattern.compile("[A-Za-z0-9]+");
/**
* Indicates whether or not the component is required
* or not. All components prior to any other required
* component must also be required, such that the predicate
* ( c_i required)({c_0, ..., c_i-1} required) holds true,
* where {c_0, ..., c_n-1} is the set of command components.
*/
private final boolean required;
/**
* The command component name. This might be exposed
* to command senders and so should be chosen carefully.
*/
private final String name;
/**
* The parser that is used to parse the command input
* into the corresponding command type
*/
private final ComponentParser<C, T> parser;
private Command<C> owningCommand;
CommandComponent(final boolean required, @Nonnull final String name,
@Nonnull final ComponentParser<C, T> parser) {
this.required = required;
this.name = Objects.requireNonNull(name, "Name may not be null");
if (!NAME_PATTERN.asPredicate().test(name)) {
throw new IllegalArgumentException("Name must be alphanumeric");
}
this.parser = Objects.requireNonNull(parser, "Parser may not be null");
}
/**
* Create a new command component
*
* @param clazz Argument class
* @param <C> Command sender type
* @param <T> Argument Type
* @return Component builder
*/
@Nonnull
public static <C extends CommandSender, T> CommandComponent.Builder<C, T> ofType(@Nonnull final Class<T> clazz) {
return new Builder<>();
}
/**
* Check whether or not the command component is required
*
* @return {@code true} if the component is required, {@code false} if not
*/
public boolean isRequired() {
return this.required;
}
/**
* Get the command component name;
*
* @return Component name
*/
@Nonnull
public String getName() {
return this.name;
}
/**
* Get the parser that is used to parse the command input
* into the corresponding command type
*
* @return Command parser
*/
@Nonnull
public ComponentParser<C, T> getParser() {
return this.parser;
}
@Nonnull
@Override
public String toString() {
return String.format("CommandComponent{name=%s}", this.name);
}
/**
* Get the owning command
*
* @return Owning command
*/
@Nullable
public Command<C> getOwningCommand() {
return this.owningCommand;
}
/**
* Set the owning command
*
* @param owningCommand Owning command
*/
public void setOwningCommand(@Nonnull final Command<C> owningCommand) {
if (this.owningCommand != null) {
throw new IllegalStateException("Cannot replace owning command");
}
this.owningCommand = owningCommand;
}
@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final CommandComponent<?, ?> that = (CommandComponent<?, ?>) o;
return isRequired() == that.isRequired() && Objects.equals(getName(), that.getName());
}
@Override
public int hashCode() {
return Objects.hash(isRequired(), getName());
}
@Override
public int compareTo(@Nonnull final CommandComponent<?, ?> o) {
if (this instanceof StaticComponent) {
if (o instanceof StaticComponent) {
return (this.getName().compareTo(o.getName()));
} else {
return -1;
}
} else {
if (o instanceof StaticComponent) {
return 1;
} else {
return 0;
}
}
}
public static class Builder<C extends CommandSender, T> {
private String name;
private boolean required = true;
private ComponentParser<C, T> parser;
private Builder() {
}
/**
* Set the component name. Must be alphanumeric
*
* @param name Alphanumeric component name
* @return Builder instance
*/
@Nonnull
public Builder<C, T> named(@Nonnull final String name) {
this.name = name;
return this;
}
/**
* Indicates that the component is required.
* All components prior to any other required
* component must also be required, such that the predicate
* ( c_i required)({c_0, ..., c_i-1} required) holds true,
* where {c_0, ..., c_n-1} is the set of command components.
*
* @return Builder instance
*/
@Nonnull
public Builder<C, T> asRequired() {
this.required = true;
return this;
}
/**
* Indicates that the component is optional.
* All components prior to any other required
* component must also be required, such that the predicate
* ( c_i required)({c_0, ..., c_i-1} required) holds true,
* where {c_0, ..., c_n-1} is the set of command components.
*
* @return Builder instance
*/
@Nonnull
public Builder<C, T> asOptional() {
this.required = false;
return this;
}
/**
* Set the component parser
*
* @param parser Component parser
* @return Builder instance
*/
@Nonnull
public Builder<C, T> withParser(@Nonnull final ComponentParser<C, T> parser) {
this.parser = Objects.requireNonNull(parser, "Parser may not be null");
return this;
}
/**
* Construct a command component from the builder settings
*
* @return Constructed component
*/
@Nonnull
public CommandComponent<C, T> build() {
return new CommandComponent<>(this.required, this.name, this.parser);
}
}
}

View file

@ -0,0 +1,39 @@
//
// MIT License
//
// Copyright (c) 2020 IntellectualSites
//
// 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.intellectualsites.commands.components;
import com.intellectualsites.commands.sender.CommandSender;
import javax.annotation.Nonnull;
import java.util.List;
import java.util.function.Function;
@FunctionalInterface
public interface CommandSyntaxFormatter<C extends CommandSender> extends Function<List<CommandComponent<C, ?>>, String> {
@Override
@Nonnull
String apply(@Nonnull List<CommandComponent<C, ?>> commandComponents);
}

View file

@ -0,0 +1,60 @@
//
// MIT License
//
// Copyright (c) 2020 IntellectualSites
//
// 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.intellectualsites.commands.components;
import com.intellectualsites.commands.sender.CommandSender;
import javax.annotation.Nonnull;
import java.util.Iterator;
import java.util.List;
public class StandardCommandSyntaxFormatter<C extends CommandSender> implements CommandSyntaxFormatter<C> {
public StandardCommandSyntaxFormatter() {
}
@Nonnull
@Override
public String apply(@Nonnull final List<CommandComponent<C, ?>> commandComponents) {
final StringBuilder stringBuilder = new StringBuilder();
final Iterator<CommandComponent<C, ?>> iterator = commandComponents.iterator();
while (iterator.hasNext()) {
final CommandComponent<?, ?> commandComponent = iterator.next();
if (commandComponent instanceof StaticComponent) {
stringBuilder.append(commandComponent.getName());
} else {
if (commandComponent.isRequired()) {
stringBuilder.append("<").append(commandComponent.getName()).append(">");
} else {
stringBuilder.append("[").append(commandComponent.getName()).append("]");
}
}
if (iterator.hasNext()) {
stringBuilder.append(" ");
}
}
return stringBuilder.toString();
}
}

View file

@ -0,0 +1,98 @@
//
// MIT License
//
// Copyright (c) 2020 IntellectualSites
//
// 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.intellectualsites.commands.components;
import com.intellectualsites.commands.context.CommandContext;
import com.intellectualsites.commands.parser.ComponentParseResult;
import com.intellectualsites.commands.parser.ComponentParser;
import com.intellectualsites.commands.sender.CommandSender;
import javax.annotation.Nonnull;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Queue;
import java.util.Set;
public final class StaticComponent<C extends CommandSender> extends CommandComponent<C, String> {
private StaticComponent(final boolean required, @Nonnull final String name, @Nonnull final String... aliases) {
super(required, name, new StaticComponentParser<>(name, aliases));
}
@Nonnull
public static <C extends CommandSender> StaticComponent<C> required(@Nonnull final String name,
@Nonnull final String... aliases) {
return new StaticComponent<>(true, name, aliases);
}
@Nonnull
public static <C extends CommandSender> StaticComponent<C> optional(@Nonnull final String name,
@Nonnull final String... aliases) {
return new StaticComponent<>(false, name, aliases);
}
private static final class StaticComponentParser<C extends CommandSender> implements ComponentParser<C, String> {
private final String name;
private final Set<String> acceptedStrings = new HashSet<>();
private StaticComponentParser(@Nonnull final String name, @Nonnull final String... aliases) {
this.acceptedStrings.add(this.name = name);
this.acceptedStrings.addAll(Arrays.asList(aliases));
}
@Nonnull
@Override
public ComponentParseResult<String> parse(@Nonnull final CommandContext<C> commandContext,
@Nonnull final Queue<String> inputQueue) {
final String string = inputQueue.peek();
if (string == null) {
return ComponentParseResult.failure(this.name);
}
for (final String acceptedString : this.acceptedStrings) {
if (string.equalsIgnoreCase(acceptedString)) {
// Remove the head of the queue
inputQueue.remove();
return ComponentParseResult.success(this.name);
}
}
return ComponentParseResult.failure(this.name);
}
@Nonnull
@Override
public List<String> suggestions(@Nonnull final CommandContext<C> commandContext, @Nonnull final String input) {
if (this.name.toLowerCase(Locale.ENGLISH).startsWith(input)) {
return Collections.singletonList(this.name);
}
return Collections.emptyList();
}
}
}

View file

@ -0,0 +1,80 @@
//
// MIT License
//
// Copyright (c) 2020 IntellectualSites
//
// 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.intellectualsites.commands.context;
import com.intellectualsites.commands.sender.CommandSender;
import javax.annotation.Nonnull;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
public class CommandContext<C extends CommandSender> {
private final Map<String, Object> internalStorage = new HashMap<>();
private final C commandSender;
public CommandContext(@Nonnull final C commandSender) {
this.commandSender = commandSender;
}
/**
* Get the sender that executed the command
*
* @return Command sender
*/
@Nonnull
public C getCommandSender() {
return this.commandSender;
}
/**
* Store a value in the context map
*
* @param key Key
* @param value Value
* @param <T> Value type
*/
public <T> void store(@Nonnull final String key, @Nonnull final T value) {
this.internalStorage.put(key, value);
}
/**
* Get a value from its key
*
* @param key Key
* @param <T> Value type
* @return Value
*/
public <T> Optional<T> get(@Nonnull final String key) {
final Object value = this.internalStorage.get(key);
if (value != null) {
@SuppressWarnings("ALL") final T castedValue = (T) value;
return Optional.of(castedValue);
} else {
return Optional.empty();
}
}
}

View file

@ -0,0 +1,73 @@
//
// MIT License
//
// Copyright (c) 2020 IntellectualSites
//
// 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.intellectualsites.commands.exceptions;
import com.intellectualsites.commands.components.CommandComponent;
import com.intellectualsites.commands.sender.CommandSender;
import javax.annotation.Nonnull;
import java.util.Collections;
import java.util.List;
/**
* Exception thrown when parsing user input into a command
*/
public class CommandParseException extends IllegalArgumentException {
private final CommandSender commandSender;
private final List<CommandComponent<?, ?>> currentChain;
/**
* Construct a new command parse exception
*
* @param commandSender Sender who executed the command
* @param currentChain Chain leading up to the exception
*/
protected CommandParseException(@Nonnull final CommandSender commandSender,
@Nonnull final List<CommandComponent<?, ?>> currentChain) {
this.commandSender = commandSender;
this.currentChain = currentChain;
}
/**
* Get the command sender
*
* @return Command sender
*/
@Nonnull
public CommandSender getCommandSender() {
return this.commandSender;
}
/**
* Get the command chain leading up to the exception
*
* @return Unmodifiable list of command components
*/
@Nonnull
public List<CommandComponent<?, ?>> getCurrentChain() {
return Collections.unmodifiableList(this.currentChain);
}
}

View file

@ -0,0 +1,53 @@
//
// MIT License
//
// Copyright (c) 2020 IntellectualSites
//
// 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.intellectualsites.commands.exceptions;
import com.intellectualsites.commands.components.CommandComponent;
import com.intellectualsites.commands.sender.CommandSender;
import javax.annotation.Nonnull;
import java.util.List;
public class InvalidSyntaxException extends CommandParseException {
private final String correctSyntax;
public InvalidSyntaxException(@Nonnull final String correctSyntax,
@Nonnull final CommandSender commandSender,
@Nonnull final List<CommandComponent<?, ?>> currentChain) {
super(commandSender, currentChain);
this.correctSyntax = correctSyntax;
}
@Nonnull
public String getCorrectSyntax() {
return this.correctSyntax;
}
@Override
public String getMessage() {
return String.format("Invalid command syntax. Correct syntax is: %s", this.correctSyntax);
}
}

View file

@ -0,0 +1,64 @@
//
// MIT License
//
// Copyright (c) 2020 IntellectualSites
//
// 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.intellectualsites.commands.exceptions;
import com.intellectualsites.commands.components.CommandComponent;
import com.intellectualsites.commands.sender.CommandSender;
import javax.annotation.Nonnull;
import java.util.List;
/**
* Exception thrown when a command sender tries to execute
* a command that doesn't exist
*/
public class NoSuchCommandException extends CommandParseException {
private final String suppliedCommand;
/**
* Construct a no such command exception
*
* @param commandSender Sender who executed the command
* @param currentChain Chain leading up to the exception
* @param command Entered command (following the command chain)
*/
public NoSuchCommandException(@Nonnull final CommandSender commandSender,
@Nonnull final List<CommandComponent<?, ?>> currentChain,
@Nonnull final String command) {
super(commandSender, currentChain);
this.suppliedCommand = command;
}
/**
* Get the supplied command
*
* @return Supplied command
*/
@Nonnull
public String getSuppliedCommand() {
return this.suppliedCommand;
}
}

View file

@ -0,0 +1,102 @@
//
// MIT License
//
// Copyright (c) 2020 IntellectualSites
//
// 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.intellectualsites.commands.execution;
import com.intellectualsites.commands.CommandTree;
import com.intellectualsites.commands.context.CommandContext;
import com.intellectualsites.commands.sender.CommandSender;
import javax.annotation.Nonnull;
import java.util.Queue;
import java.util.concurrent.CompletableFuture;
import java.util.function.Function;
/**
* The command execution coordinator is responsible for
* coordinating command execution. This includes determining
* what thread the command should be executed on, whether or
* not command may be executed in parallel, etc.
*
* @param <C> Command sender type
*/
public abstract class CommandExecutionCoordinator<C extends CommandSender> {
private final CommandTree<C> commandTree;
/**
* Construct a new command execution coordinator
*
* @param commandTree Command tree
*/
public CommandExecutionCoordinator(@Nonnull final CommandTree<C> commandTree) {
this.commandTree = commandTree;
}
/**
* Returns a simple command execution coordinator that executes all commands immediately, on the calling thread
*
* @param <C> Command sender type
* @return New coordinator instance
*/
public static <C extends CommandSender> Function<CommandTree<C>, CommandExecutionCoordinator<C>> simpleCoordinator() {
return SimpleCoordinator::new;
}
public abstract CompletableFuture<CommandResult> coordinateExecution(@Nonnull final CommandContext<C> commandContext,
@Nonnull final Queue<String> input);
/**
* Get the command tree
*
* @return Command tree
*/
@Nonnull
protected CommandTree<C> getCommandTree() {
return this.commandTree;
}
public static class SimpleCoordinator<C extends CommandSender> extends CommandExecutionCoordinator<C> {
private SimpleCoordinator(@Nonnull final CommandTree<C> commandTree) {
super(commandTree);
}
@Override
public CompletableFuture<CommandResult> coordinateExecution(@Nonnull CommandContext<C> commandContext,
@Nonnull Queue<String> input) {
final CompletableFuture<CommandResult> completableFuture = new CompletableFuture<>();
try {
this.getCommandTree().parse(commandContext, input).ifPresent(
command -> command.getCommandExecutionHandler().execute(commandContext));
completableFuture.complete(new CommandResult());
} catch (final Exception e) {
completableFuture.completeExceptionally(e);
}
return completableFuture;
}
}
}

View file

@ -0,0 +1,57 @@
//
// MIT License
//
// Copyright (c) 2020 IntellectualSites
//
// 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.intellectualsites.commands.execution;
import com.intellectualsites.commands.context.CommandContext;
import com.intellectualsites.commands.sender.CommandSender;
import javax.annotation.Nonnull;
/**
* Handler that is invoked whenever a {@link com.intellectualsites.commands.Command} is executed
* by a {@link com.intellectualsites.commands.sender.CommandSender}
*
* @param <C> Command sender type
*/
@FunctionalInterface
public interface CommandExecutionHandler<C extends CommandSender> {
/**
* Handle command execution
*
* @param commandContext Command context
*/
void execute(@Nonnull final CommandContext<C> commandContext);
class NullCommandExecutionHandler<C extends CommandSender> implements CommandExecutionHandler<C> {
@Override
public void execute(@Nonnull final CommandContext<C> commandContext) {
}
}
}

View file

@ -0,0 +1,27 @@
//
// MIT License
//
// Copyright (c) 2020 IntellectualSites
//
// 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.intellectualsites.commands.execution;
public class CommandResult {
}

View file

@ -0,0 +1,65 @@
//
// MIT License
//
// Copyright (c) 2020 IntellectualSites
//
// 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.intellectualsites.commands.internal;
import com.intellectualsites.commands.Command;
import javax.annotation.Nonnull;
/**
* Utility that registers commands natively for whatever
* platform the library is used in. This can do nothing, if
* the target platform does not have its own concept of commands
*/
@FunctionalInterface
public interface CommandRegistrationHandler {
/**
* Command registration handler that does nothing
*/
NullCommandRegistrationHandler NULL_COMMAND_REGISTRATION_HANDLER = new NullCommandRegistrationHandler();
/**
* Attempt to register the command
*
* @param command Command to register
* @return {@code true} if the command was registered successfully,
* else {@code false}
*/
boolean registerCommand(@Nonnull final Command<?> command);
final class NullCommandRegistrationHandler implements CommandRegistrationHandler {
private NullCommandRegistrationHandler() {
}
@Override
public boolean registerCommand(@Nonnull final Command<?> command) {
return true;
}
}
}

View file

@ -0,0 +1,95 @@
//
// MIT License
//
// Copyright (c) 2020 IntellectualSites
//
// 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.intellectualsites.commands.parser;
import javax.annotation.Nonnull;
import java.util.Optional;
public abstract class ComponentParseResult<T> {
private ComponentParseResult() {
}
@Nonnull
public static <T> ComponentParseResult<T> failure(@Nonnull final String failure) {
return new ParseFailure<>(failure);
}
@Nonnull
public static <T> ComponentParseResult<T> success(@Nonnull final T value) {
return new ParseSuccess<>(value);
}
@Nonnull
public abstract Optional<T> getParsedValue();
@Nonnull
public abstract Optional<String> getFailure();
private static final class ParseSuccess<T> extends ComponentParseResult<T> {
private final T value;
private ParseSuccess(@Nonnull final T value) {
this.value = value;
}
@Nonnull
@Override
public Optional<T> getParsedValue() {
return Optional.of(this.value);
}
@Nonnull
@Override
public Optional<String> getFailure() {
return Optional.empty();
}
}
private static final class ParseFailure<T> extends ComponentParseResult<T> {
private final String failure;
private ParseFailure(@Nonnull final String failure) {
this.failure = failure;
}
@Nonnull
@Override
public Optional<T> getParsedValue() {
return Optional.empty();
}
@Nonnull
@Override
public Optional<String> getFailure() {
return Optional.of(this.failure);
}
}
}

View file

@ -0,0 +1,52 @@
//
// MIT License
//
// Copyright (c) 2020 IntellectualSites
//
// 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.intellectualsites.commands.parser;
import com.intellectualsites.commands.context.CommandContext;
import com.intellectualsites.commands.sender.CommandSender;
import javax.annotation.Nonnull;
import java.util.Collections;
import java.util.List;
import java.util.Queue;
@FunctionalInterface
public interface ComponentParser<C extends CommandSender, T> {
/**
* Parse command input into a command result
*
* @param commandContext Command context
* @param inputQueue The queue of arguments
* @return Parsed command result
*/
@Nonnull
ComponentParseResult<T> parse(@Nonnull CommandContext<C> commandContext, @Nonnull Queue<String> inputQueue);
@Nonnull
default List<String> suggestions(@Nonnull final CommandContext<C> commandContext, @Nonnull final String input) {
return Collections.emptyList();
}
}

View file

@ -0,0 +1,27 @@
//
// MIT License
//
// Copyright (c) 2020 IntellectualSites
//
// 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.intellectualsites.commands.sender;
public interface CommandSender {
}