diff --git a/commands-jline/README.md b/commands-jline/README.md new file mode 100644 index 00000000..6ceed81a --- /dev/null +++ b/commands-jline/README.md @@ -0,0 +1 @@ +Command implementation for [JLine](https://github.com/jline/jline3) diff --git a/commands-jline/pom.xml b/commands-jline/pom.xml new file mode 100644 index 00000000..4870c29c --- /dev/null +++ b/commands-jline/pom.xml @@ -0,0 +1,24 @@ + + + + Commands + com.intellectualsites + 1.0-SNAPSHOT + + 4.0.0 + commands-jline + + + org.jline + jline + 3.16.0 + + + com.intellectualsites + Commands + 1.0-SNAPSHOT + + + diff --git a/commands-jline/src/main/java/com/intellectualsites/commands/jline/JLineCommandManager.java b/commands-jline/src/main/java/com/intellectualsites/commands/jline/JLineCommandManager.java new file mode 100644 index 00000000..006aaa7f --- /dev/null +++ b/commands-jline/src/main/java/com/intellectualsites/commands/jline/JLineCommandManager.java @@ -0,0 +1,74 @@ +// +// 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.jline; + +import com.intellectualsites.commands.Command; +import com.intellectualsites.commands.CommandManager; +import com.intellectualsites.commands.CommandTree; +import com.intellectualsites.commands.execution.CommandExecutionCoordinator; +import com.intellectualsites.commands.internal.CommandRegistrationHandler; +import org.jline.reader.*; +import org.jline.terminal.Terminal; +import org.jline.terminal.TerminalBuilder; + +import javax.annotation.Nonnull; +import java.util.List; +import java.util.function.Function; + +/** + * Command manager for use with JLine + */ +public class JLineCommandManager extends CommandManager implements Completer { + + public static void main(String[] args) throws Exception { + // TODO: REMOVE THIS!!!! + final JLineCommandManager jLineCommandManager = new JLineCommandManager(CommandExecutionCoordinator.simpleCoordinator()); + final Terminal terminal = TerminalBuilder.builder().dumb(true).build(); + LineReader lineReader = LineReaderBuilder.builder() + .completer(jLineCommandManager).terminal(terminal).appName("Test").build(); + boolean[] shouldStop = new boolean[] { false }; + jLineCommandManager.registerCommand(Command.newBuilder("stop").withHandler(commandContext -> + shouldStop[0] = true).build()); + while (!shouldStop[0]) { + final String line = lineReader.readLine(); + if (line == null || line.isEmpty() || !line.startsWith("/")) { + continue; + } + jLineCommandManager.executeCommand(new JLineCommandSender(), line.substring(1)).join(); + if (shouldStop[0]) { + System.out.println("Stopping."); + } + } + } + + public JLineCommandManager(@Nonnull final Function, CommandExecutionCoordinator> executionCoordinatorFunction) { + super(executionCoordinatorFunction, CommandRegistrationHandler.NULL_COMMAND_REGISTRATION_HANDLER); + } + + @Override + public void complete(@Nonnull final LineReader lineReader, @Nonnull final ParsedLine parsedLine, @Nonnull final List list) { + // TODO: Implement + } + +} diff --git a/commands-jline/src/main/java/com/intellectualsites/commands/jline/JLineCommandSender.java b/commands-jline/src/main/java/com/intellectualsites/commands/jline/JLineCommandSender.java new file mode 100644 index 00000000..e4f7aaf5 --- /dev/null +++ b/commands-jline/src/main/java/com/intellectualsites/commands/jline/JLineCommandSender.java @@ -0,0 +1,29 @@ +// +// 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.jline; + +import com.intellectualsites.commands.sender.CommandSender; + +public class JLineCommandSender implements CommandSender { +} diff --git a/pom.xml b/pom.xml index 5211bac2..ad5a1636 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,10 @@ com.intellectualsites Commands 1.0-SNAPSHOT - jar + + commands-jline + + pom 2020 diff --git a/src/main/java/com/intellectualsites/commands/Command.java b/src/main/java/com/intellectualsites/commands/Command.java index d5879b9c..1fe8ed7a 100644 --- a/src/main/java/com/intellectualsites/commands/Command.java +++ b/src/main/java/com/intellectualsites/commands/Command.java @@ -24,33 +24,50 @@ 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 java.util.LinkedList; -import java.util.List; -import java.util.Objects; +import java.util.*; /** * A command consists out of a chain of {@link com.intellectualsites.commands.components.CommandComponent command components}. + * + * @param Command sender type */ -public class Command { +public class Command { - private final CommandComponent[] components; + private final CommandComponent[] components; + private final CommandExecutionHandler commandExecutionHandler; - private Command(@Nonnull final CommandComponent[] commandComponents) { + protected Command(@Nonnull final CommandComponent[] commandComponents, @Nonnull final CommandExecutionHandler commandExecutionHandler) { this.components = Objects.requireNonNull(commandComponents, "Command components may not be null"); - if (this.components.length == 0){ + if (this.components.length == 0) { throw new IllegalArgumentException("At least one command component is required"); } // Enforce ordering of command components boolean foundOptional = false; - for (final CommandComponent component : this.components) { + for (final CommandComponent 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; + } + + /** + * Create a new command builder + * + * @param commandName Base command component + * @return Command builder + */ + @Nonnull + public static Builder newBuilder(@Nonnull final String commandName) { + return new Builder<>(Collections.singletonList(StaticComponent.required(commandName)), + new CommandExecutionHandler.NullCommandExecutionHandler<>()); } /** @@ -58,10 +75,18 @@ public class Command { * * @return Copy of the command component array */ - @Nonnull public CommandComponent[] getComponents() { - final CommandComponent[] commandComponents = new CommandComponent[this.components.length]; - System.arraycopy(this.components, 0, commandComponents, 0, this.components.length); - return commandComponents; + @Nonnull @SuppressWarnings("ALL") + public CommandComponent[] getComponents() { + return (CommandComponent[]) Arrays.asList(this.components).toArray(); + } + + /** + * Get the command execution handler + * + * @return Command execution handler + */ + @Nonnull public CommandExecutionHandler getCommandExecutionHandler() { + return this.commandExecutionHandler; } /** @@ -70,8 +95,8 @@ public class Command { * * @return List containing the longest shared component chain */ - public List> getSharedComponentChain(@Nonnull final Command other) { - final List> commandComponents = new LinkedList<>(); + public List> getSharedComponentChain(@Nonnull final Command other) { + final List> commandComponents = new LinkedList<>(); for (int i = 0; i < this.components.length && i < other.components.length; i++) { if (this.components[i].equals(other.components[i])) { commandComponents.add(this.components[i]); @@ -82,4 +107,52 @@ public class Command { return commandComponents; } + + public static class Builder { + + private final List> commandComponents; + private final CommandExecutionHandler commandExecutionHandler; + + private Builder(@Nonnull final List> commandComponents, @Nonnull final CommandExecutionHandler commandExecutionHandler) { + this.commandComponents = commandComponents; + this.commandExecutionHandler = commandExecutionHandler; + } + + /** + * Add a new command component to the command + * + * @param component Component to add + * @param Component type + * @return New builder instance with the command component inserted into the component list + */ + @Nonnull + public Builder withComponent(@Nonnull final CommandComponent component) { + final List> commandComponents = new LinkedList<>(this.commandComponents); + commandComponents.add(component); + return new Builder<>(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 withHandler(@Nonnull final CommandExecutionHandler commandExecutionHandler) { + return new Builder<>(this.commandComponents, commandExecutionHandler); + } + + /** + * Build a command using the builder instance + * + * @return Built command + */ + @Nonnull + public Command build() { + return new Command<>(this.commandComponents.toArray(new CommandComponent[0]), this.commandExecutionHandler); + } + + } + } diff --git a/src/main/java/com/intellectualsites/commands/CommandManager.java b/src/main/java/com/intellectualsites/commands/CommandManager.java new file mode 100644 index 00000000..b008be1a --- /dev/null +++ b/src/main/java/com/intellectualsites/commands/CommandManager.java @@ -0,0 +1,74 @@ +// +// 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.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.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 Command sender type + */ +public abstract class CommandManager { + + private final CommandExecutionCoordinator commandExecutionCoordinator; + private final CommandTree commandTree; + + protected CommandManager(@Nonnull final Function, CommandExecutionCoordinator> commandExecutionCoordinator, + @Nonnull final CommandRegistrationHandler commandRegistrationHandler) { + this.commandTree = CommandTree.newTree(commandRegistrationHandler); + this.commandExecutionCoordinator = commandExecutionCoordinator.apply(commandTree); + } + + public CompletableFuture executeCommand(@Nonnull final C commandSender, @Nonnull final String input) { + final StringTokenizer stringTokenizer = new StringTokenizer(input, " "); + final Queue tokens = new LinkedList<>(); + while (stringTokenizer.hasMoreElements()) { + tokens.add(stringTokenizer.nextToken()); + } + final CommandContext context = new CommandContext<>(commandSender); + return this.commandExecutionCoordinator.coordinateExecution(context, tokens); + } + + /** + * Register a new command + * + * @param command Command to register + */ + public void registerCommand(@Nonnull final Command command) { + this.commandTree.insertCommand(command); + } + +} diff --git a/src/main/java/com/intellectualsites/commands/CommandTree.java b/src/main/java/com/intellectualsites/commands/CommandTree.java index f0bd1588..36e43158 100644 --- a/src/main/java/com/intellectualsites/commands/CommandTree.java +++ b/src/main/java/com/intellectualsites/commands/CommandTree.java @@ -27,6 +27,7 @@ import com.google.common.base.Objects; import com.google.common.collect.Lists; import com.intellectualsites.commands.components.CommandComponent; import com.intellectualsites.commands.components.StaticComponent; +import com.intellectualsites.commands.context.CommandContext; import com.intellectualsites.commands.exceptions.NoSuchCommandException; import com.intellectualsites.commands.internal.CommandRegistrationHandler; import com.intellectualsites.commands.parser.ComponentParseResult; @@ -44,7 +45,7 @@ import java.util.stream.Collectors; */ public class CommandTree { - private final Node> internalTree = new Node<>(null); + private final Node> internalTree = new Node<>(null); private final CommandRegistrationHandler commandRegistrationHandler; private CommandTree(@Nonnull final CommandRegistrationHandler commandRegistrationHandler) { @@ -63,26 +64,25 @@ public class CommandTree { return new CommandTree<>(commandRegistrationHandler); } - public Optional parse(@Nonnull final C commandSender, @Nonnull final String[] args) throws NoSuchCommandException { - final Queue commandQueue = new LinkedList<>(Arrays.asList(args)); - return parseCommand(commandSender, commandQueue, this.internalTree); + public Optional> parse(@Nonnull final CommandContext commandContext, @Nonnull final Queue args) throws NoSuchCommandException { + return parseCommand(commandContext, args, this.internalTree); } - private Optional parseCommand(@Nonnull final C commandSender, @Nonnull final Queue commandQueue, - @Nonnull final Node> root) throws NoSuchCommandException { + private Optional> parseCommand(@Nonnull final CommandContext commandContext, @Nonnull final Queue commandQueue, + @Nonnull final Node> root) throws NoSuchCommandException { - final List>> children = root.getChildren(); + final List>> children = root.getChildren(); if (children.size() == 1 && !(children.get(0).getValue() instanceof StaticComponent)) { // The value has to be a variable - final Node> child = children.get(0); + final Node> child = children.get(0); if (child.getValue() != null) { - final ComponentParseResult result = child.getValue().getParser().parse(commandSender, commandQueue); + final ComponentParseResult result = child.getValue().getParser().parse(commandContext, commandQueue); if (result.getParsedValue().isPresent()) { /* TODO: Add context */ if (child.isLeaf()) { return Optional.ofNullable(child.getValue().getOwningCommand()); } else { - return this.parseCommand(commandSender, commandQueue, child); + return this.parseCommand(commandContext, commandQueue, child); } } else if (result.getFailure().isPresent()) { /* TODO: Return error */ @@ -97,13 +97,12 @@ public class CommandTree { return Optional.of(root.getValue().getOwningCommand()); } else { /* TODO: Indicate that we could not resolve the command here */ - final List> components = this.getChain(root).stream().map(Node::getValue).collect(Collectors.toList()); + final List> components = this.getChain(root).stream().map(Node::getValue).collect(Collectors.toList()); } } else { + /* final String popped = commandQueue.poll(); if (popped == null) { - /* Not enough arguments */ - /* TODO: Send correct usage */ return Optional.empty(); } @@ -122,7 +121,6 @@ public class CommandTree { } else if (comparison > 0) { high = mid - 1; } else { - /* We found a match */ if (node.isLeaf()) { return Optional.ofNullable(node.getValue().getOwningCommand()); } else { @@ -130,26 +128,27 @@ public class CommandTree { } } } + */ - /* We could not find a match */ - throw new NoSuchCommandException(commandSender, getChain(root).stream().map(Node::getValue).collect(Collectors.toList()), popped); - } - - /* - final Iterator>> childIterator = root.getChildren().iterator(); - if (childIterator.hasNext()) { - while (childIterator.hasNext()) { - final Node> child = childIterator.next(); - if (child.getValue() != null) { - final ComponentParseResult result = child.getValue().getParser().parse(commandSender, commandQueue); - if (result.getParsedValue().isPresent()) { - return this.parseCommand(commandSender, commandQueue, child); - } else if (result.getFailure().isPresent() && root.children.size() == 1) { + final Iterator>> childIterator = root.getChildren().iterator(); + if (childIterator.hasNext()) { + while (childIterator.hasNext()) { + final Node> 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()), + java.util.Objects.requireNonNull(commandQueue.peek())); } - */ return Optional.empty(); } @@ -159,10 +158,10 @@ public class CommandTree { * * @param command Command to insert */ - public void insertCommand(@Nonnull final Command command) { - Node> node = this.internalTree; - for (final CommandComponent component : command.getComponents()) { - Node> tempNode = node.getChild(component); + public void insertCommand(@Nonnull final Command command) { + Node> node = this.internalTree; + for (final CommandComponent component : command.getComponents()) { + Node> tempNode = node.getChild(component); if (tempNode == null) { tempNode = node.addChild(component); } @@ -202,12 +201,12 @@ public class CommandTree { /* TODO: Figure out a way to register all combinations along a command component path */ } - private void checkAmbiguity(@Nonnull final Node> node) { + private void checkAmbiguity(@Nonnull final Node> node) { if (node.isLeaf()) { return; } final int size = node.children.size(); - for (final Node> child : node.children) { + for (final Node> 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()); @@ -216,8 +215,8 @@ public class CommandTree { node.children.forEach(this::checkAmbiguity); } - private List> getLeaves(@Nonnull final Node> node) { - final List> leaves = new LinkedList<>(); + private List> getLeaves(@Nonnull final Node> node) { + final List> leaves = new LinkedList<>(); if (node.isLeaf()) { if (node.getValue() != null) { leaves.add(node.getValue()); @@ -228,9 +227,9 @@ public class CommandTree { return leaves; } - private List>> getChain(@Nullable final Node> end) { - final List>> chain = new LinkedList<>(); - Node> tail = end; + private List>> getChain(@Nullable final Node> end) { + final List>> chain = new LinkedList<>(); + Node> tail = end; while (tail != null) { chain.add(tail); tail = end.getParent(); diff --git a/src/main/java/com/intellectualsites/commands/components/CommandComponent.java b/src/main/java/com/intellectualsites/commands/components/CommandComponent.java index e5b2ebfe..1d4575af 100644 --- a/src/main/java/com/intellectualsites/commands/components/CommandComponent.java +++ b/src/main/java/com/intellectualsites/commands/components/CommandComponent.java @@ -25,6 +25,7 @@ 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; @@ -34,9 +35,10 @@ import java.util.regex.Pattern; /** * A component that belongs to a command * + * @param Command sender type * @param The type that the component parses into */ -public class CommandComponent implements Comparable> { +public class CommandComponent implements Comparable> { private static final Pattern NAME_PATTERN = Pattern.compile("[A-Za-z0-9]+"); @@ -57,12 +59,12 @@ public class CommandComponent implements Comparable> { * The parser that is used to parse the command input * into the corresponding command type */ - private final ComponentParser parser; + private final ComponentParser parser; - private Command owningCommand; + private Command owningCommand; CommandComponent(final boolean required, @Nonnull final String name, - @Nonnull final ComponentParser parser) { + @Nonnull final ComponentParser parser) { this.required = required; this.name = Objects.requireNonNull(name, "Name may not be null"); if (!NAME_PATTERN.asPredicate().test(name)) { @@ -75,10 +77,11 @@ public class CommandComponent implements Comparable> { * Create a new command component * * @param clazz Argument class + * @param Command sender type * @param Argument Type * @return Component builder */ - @Nonnull public static CommandComponent.Builder ofType(@Nonnull final Class clazz) { + @Nonnull public static CommandComponent.Builder ofType(@Nonnull final Class clazz) { return new Builder<>(); } @@ -106,7 +109,7 @@ public class CommandComponent implements Comparable> { * * @return Command parser */ - @Nonnull public ComponentParser getParser() { + @Nonnull public ComponentParser getParser() { return this.parser; } @@ -119,7 +122,7 @@ public class CommandComponent implements Comparable> { * * @return Owning command */ - @Nullable public Command getOwningCommand() { + @Nullable public Command getOwningCommand() { return this.owningCommand; } @@ -128,7 +131,7 @@ public class CommandComponent implements Comparable> { * * @param owningCommand Owning command */ - public void setOwningCommand(@Nonnull final Command owningCommand) { + public void setOwningCommand(@Nonnull final Command owningCommand) { if (this.owningCommand != null) { throw new IllegalStateException("Cannot replace owning command"); } @@ -143,7 +146,7 @@ public class CommandComponent implements Comparable> { if (o == null || getClass() != o.getClass()) { return false; } - final CommandComponent that = (CommandComponent) o; + final CommandComponent that = (CommandComponent) o; return isRequired() == that.isRequired() && com.google.common.base.Objects.equal(getName(), that.getName()); } @@ -153,7 +156,7 @@ public class CommandComponent implements Comparable> { } @Override - public int compareTo(@Nonnull final CommandComponent o) { + public int compareTo(@Nonnull final CommandComponent o) { if (this instanceof StaticComponent) { if (o instanceof StaticComponent) { return (this.getName().compareTo(o.getName())); @@ -170,11 +173,11 @@ public class CommandComponent implements Comparable> { } - public static class Builder { + public static class Builder { private String name; private boolean required = true; - private ComponentParser parser; + private ComponentParser parser; private Builder() { } @@ -185,7 +188,7 @@ public class CommandComponent implements Comparable> { * @param name Alphanumeric component name * @return Builder instance */ - @Nonnull public Builder named(@Nonnull final String name) { + @Nonnull public Builder named(@Nonnull final String name) { this.name = name; return this; } @@ -199,7 +202,7 @@ public class CommandComponent implements Comparable> { * * @return Builder instance */ - @Nonnull public Builder asRequired() { + @Nonnull public Builder asRequired() { this.required = true; return this; } @@ -213,7 +216,7 @@ public class CommandComponent implements Comparable> { * * @return Builder instance */ - @Nonnull public Builder asOptional() { + @Nonnull public Builder asOptional() { this.required = false; return this; } @@ -224,7 +227,7 @@ public class CommandComponent implements Comparable> { * @param parser Component parser * @return Builder instance */ - @Nonnull public Builder withParser(@Nonnull final ComponentParser parser) { + @Nonnull public Builder withParser(@Nonnull final ComponentParser parser) { this.parser = Objects.requireNonNull(parser, "Parser may not be null"); return this; } @@ -234,7 +237,7 @@ public class CommandComponent implements Comparable> { * * @return Constructed component */ - @Nonnull public CommandComponent build() { + @Nonnull public CommandComponent build() { return new CommandComponent<>(this.required, this.name, this.parser); } diff --git a/src/main/java/com/intellectualsites/commands/components/StaticComponent.java b/src/main/java/com/intellectualsites/commands/components/StaticComponent.java index a7c22f94..029891ab 100644 --- a/src/main/java/com/intellectualsites/commands/components/StaticComponent.java +++ b/src/main/java/com/intellectualsites/commands/components/StaticComponent.java @@ -23,6 +23,7 @@ // 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; @@ -33,14 +34,22 @@ import java.util.HashSet; import java.util.Queue; import java.util.Set; -public final class StaticComponent extends CommandComponent { +public final class StaticComponent extends CommandComponent { private StaticComponent(final boolean required, @Nonnull final String name, @Nonnull final String ... aliases) { - super(required, name, new StaticComponentParser(name, aliases)); + super(required, name, new StaticComponentParser<>(name, aliases)); + } + + @Nonnull public static StaticComponent required(@Nonnull final String name, @Nonnull final String ... aliases) { + return new StaticComponent<>(true, name, aliases); + } + + @Nonnull public static StaticComponent optional(@Nonnull final String name, @Nonnull final String ... aliases) { + return new StaticComponent<>(false, name, aliases); } - private static final class StaticComponentParser implements ComponentParser { + private static final class StaticComponentParser implements ComponentParser { private final String name; private final Set acceptedStrings = new HashSet<>(); @@ -50,7 +59,7 @@ public final class StaticComponent extends CommandComponent { this.acceptedStrings.addAll(Arrays.asList(aliases)); } - @Nonnull @Override public ComponentParseResult parse(@Nonnull final CommandSender sender, @Nonnull final Queue inputQueue) { + @Nonnull @Override public ComponentParseResult parse(@Nonnull final CommandContext commandContext, @Nonnull final Queue inputQueue) { final String string = inputQueue.peek(); if (string == null) { return ComponentParseResult.failure(this.name); diff --git a/src/main/java/com/intellectualsites/commands/context/CommandContext.java b/src/main/java/com/intellectualsites/commands/context/CommandContext.java new file mode 100644 index 00000000..02c2ca00 --- /dev/null +++ b/src/main/java/com/intellectualsites/commands/context/CommandContext.java @@ -0,0 +1,47 @@ +// +// 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; + +public class CommandContext { + + 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; + } + +} diff --git a/src/main/java/com/intellectualsites/commands/exceptions/CommandParseException.java b/src/main/java/com/intellectualsites/commands/exceptions/CommandParseException.java index 36042c35..3584c188 100644 --- a/src/main/java/com/intellectualsites/commands/exceptions/CommandParseException.java +++ b/src/main/java/com/intellectualsites/commands/exceptions/CommandParseException.java @@ -36,7 +36,7 @@ import java.util.List; public class CommandParseException extends IllegalArgumentException { private final CommandSender commandSender; - private final List> currentChain; + private final List> currentChain; /** * Construct a new command parse exception @@ -44,7 +44,7 @@ public class CommandParseException extends IllegalArgumentException { * @param commandSender Sender who executed the command * @param currentChain Chain leading up to the exception */ - protected CommandParseException(@Nonnull final CommandSender commandSender, @Nonnull final List> currentChain) { + protected CommandParseException(@Nonnull final CommandSender commandSender, @Nonnull final List> currentChain) { this.commandSender = commandSender; this.currentChain = currentChain; } @@ -65,7 +65,7 @@ public class CommandParseException extends IllegalArgumentException { * @return Unmodifiable list of command components */ @Nonnull - public List> getCurrentChain() { + public List> getCurrentChain() { return Collections.unmodifiableList(this.currentChain); } diff --git a/src/main/java/com/intellectualsites/commands/exceptions/NoSuchCommandException.java b/src/main/java/com/intellectualsites/commands/exceptions/NoSuchCommandException.java index 07bc3b68..92b025b3 100644 --- a/src/main/java/com/intellectualsites/commands/exceptions/NoSuchCommandException.java +++ b/src/main/java/com/intellectualsites/commands/exceptions/NoSuchCommandException.java @@ -44,7 +44,7 @@ public class NoSuchCommandException extends CommandParseException { * @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> currentChain, + public NoSuchCommandException(@Nonnull final CommandSender commandSender, @Nonnull final List> currentChain, @Nonnull final String command) { super(commandSender, currentChain); this.suppliedCommand = command; diff --git a/src/main/java/com/intellectualsites/commands/execution/CommandExecutionCoordinator.java b/src/main/java/com/intellectualsites/commands/execution/CommandExecutionCoordinator.java new file mode 100644 index 00000000..c6e6fa17 --- /dev/null +++ b/src/main/java/com/intellectualsites/commands/execution/CommandExecutionCoordinator.java @@ -0,0 +1,100 @@ +// +// 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 Command sender type + */ +public abstract class CommandExecutionCoordinator { + + private final CommandTree commandTree; + + /** + * Construct a new command execution coordinator + * + * @param commandTree Command tree + */ + public CommandExecutionCoordinator(@Nonnull final CommandTree commandTree) { + this.commandTree = commandTree; + } + + /** + * Returns a simple command execution coordinator that executes all commands immediately, on the calling thread + * + * @param Command sender type + * @return New coordinator instance + */ + public static Function, CommandExecutionCoordinator> simpleCoordinator() { + return SimpleCoordinator::new; + } + + public abstract CompletableFuture coordinateExecution(@Nonnull final CommandContext commandContext, + @Nonnull final Queue input); + + /** + * Get the command tree + * + * @return Command tree + */ + @Nonnull protected CommandTree getCommandTree() { + return this.commandTree; + } + + + public static class SimpleCoordinator extends CommandExecutionCoordinator { + + private SimpleCoordinator(@Nonnull final CommandTree commandTree) { + super(commandTree); + } + + @Override + public CompletableFuture coordinateExecution(@Nonnull CommandContext commandContext, @Nonnull Queue input) { + final CompletableFuture 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; + } + + } + +} diff --git a/src/main/java/com/intellectualsites/commands/execution/CommandExecutionHandler.java b/src/main/java/com/intellectualsites/commands/execution/CommandExecutionHandler.java new file mode 100644 index 00000000..7b805ac2 --- /dev/null +++ b/src/main/java/com/intellectualsites/commands/execution/CommandExecutionHandler.java @@ -0,0 +1,56 @@ +// +// 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 Command sender type + */ +@FunctionalInterface public interface CommandExecutionHandler { + + /** + * Handle command execution + * + * @param commandContext Command context + */ + void execute(@Nonnull final CommandContext commandContext); + + + class NullCommandExecutionHandler implements CommandExecutionHandler { + + @Override + public void execute(@Nonnull final CommandContext commandContext) { + + } + + } + +} diff --git a/src/main/java/com/intellectualsites/commands/execution/CommandResult.java b/src/main/java/com/intellectualsites/commands/execution/CommandResult.java new file mode 100644 index 00000000..7a09cd68 --- /dev/null +++ b/src/main/java/com/intellectualsites/commands/execution/CommandResult.java @@ -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 { +} diff --git a/src/main/java/com/intellectualsites/commands/parser/ComponentParser.java b/src/main/java/com/intellectualsites/commands/parser/ComponentParser.java index 276da6f5..64d600cf 100644 --- a/src/main/java/com/intellectualsites/commands/parser/ComponentParser.java +++ b/src/main/java/com/intellectualsites/commands/parser/ComponentParser.java @@ -23,20 +23,23 @@ // package com.intellectualsites.commands.parser; +import com.intellectualsites.commands.context.CommandContext; import com.intellectualsites.commands.sender.CommandSender; import javax.annotation.Nonnull; import java.util.Queue; -@FunctionalInterface public interface ComponentParser { +@FunctionalInterface +public interface ComponentParser { /** * Parse command input into a command result * - * @param sender Sender who sent the command - * @param inputQueue The queue of arguments + * @param commandContext Command context + * @param inputQueue The queue of arguments * @return Parsed command result */ - @Nonnull ComponentParseResult parse(@Nonnull CommandSender sender, @Nonnull Queue inputQueue); + @Nonnull + ComponentParseResult parse(@Nonnull CommandContext commandContext, @Nonnull Queue inputQueue); }