Lots of progress

This commit is contained in:
Alexander Söderberg 2020-09-05 21:38:12 +02:00
parent a9b2524238
commit d4143246b7
No known key found for this signature in database
GPG key ID: C0207FF7EA146678
17 changed files with 606 additions and 84 deletions

1
commands-jline/README.md Normal file
View file

@ -0,0 +1 @@
Command implementation for [JLine](https://github.com/jline/jline3)

24
commands-jline/pom.xml Normal file
View file

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>Commands</artifactId>
<groupId>com.intellectualsites</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>commands-jline</artifactId>
<dependencies>
<dependency>
<groupId>org.jline</groupId>
<artifactId>jline</artifactId>
<version>3.16.0</version>
</dependency>
<dependency>
<groupId>com.intellectualsites</groupId>
<artifactId>Commands</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>

View file

@ -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<JLineCommandSender> 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<CommandTree<JLineCommandSender>, CommandExecutionCoordinator<JLineCommandSender>> executionCoordinatorFunction) {
super(executionCoordinatorFunction, CommandRegistrationHandler.NULL_COMMAND_REGISTRATION_HANDLER);
}
@Override
public void complete(@Nonnull final LineReader lineReader, @Nonnull final ParsedLine parsedLine, @Nonnull final List<Candidate> list) {
// TODO: Implement
}
}

View file

@ -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 {
}

View file

@ -7,7 +7,10 @@
<groupId>com.intellectualsites</groupId> <groupId>com.intellectualsites</groupId>
<artifactId>Commands</artifactId> <artifactId>Commands</artifactId>
<version>1.0-SNAPSHOT</version> <version>1.0-SNAPSHOT</version>
<packaging>jar</packaging> <modules>
<module>commands-jline</module>
</modules>
<packaging>pom</packaging>
<inceptionYear>2020</inceptionYear> <inceptionYear>2020</inceptionYear>
<licenses> <licenses>
<license> <license>

View file

@ -24,33 +24,50 @@
package com.intellectualsites.commands; package com.intellectualsites.commands;
import com.intellectualsites.commands.components.CommandComponent; 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.Nonnull;
import java.util.LinkedList; import java.util.*;
import java.util.List;
import java.util.Objects;
/** /**
* A command consists out of a chain of {@link com.intellectualsites.commands.components.CommandComponent command components}. * A command consists out of a chain of {@link com.intellectualsites.commands.components.CommandComponent command components}.
*
* @param <C> Command sender type
*/ */
public class Command { public class Command<C extends CommandSender> {
private final CommandComponent<?>[] components; private final CommandComponent<C, ?>[] components;
private final CommandExecutionHandler<C> commandExecutionHandler;
private Command(@Nonnull final CommandComponent<?>[] commandComponents) { protected Command(@Nonnull final CommandComponent<C, ?>[] commandComponents, @Nonnull final CommandExecutionHandler<C> commandExecutionHandler) {
this.components = Objects.requireNonNull(commandComponents, "Command components may not be null"); 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"); throw new IllegalArgumentException("At least one command component is required");
} }
// Enforce ordering of command components // Enforce ordering of command components
boolean foundOptional = false; boolean foundOptional = false;
for (final CommandComponent<?> component : this.components) { for (final CommandComponent<C, ?> component : this.components) {
if (foundOptional && component.isRequired()) { if (foundOptional && component.isRequired()) {
throw new IllegalArgumentException(String.format("Command component '%s' cannot be placed after an optional component", component.getName())); throw new IllegalArgumentException(String.format("Command component '%s' cannot be placed after an optional component", component.getName()));
} else if (!component.isRequired()) { } else if (!component.isRequired()) {
foundOptional = true; foundOptional = true;
} }
} }
this.commandExecutionHandler = commandExecutionHandler;
}
/**
* 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<>(Collections.singletonList(StaticComponent.required(commandName)),
new CommandExecutionHandler.NullCommandExecutionHandler<>());
} }
/** /**
@ -58,10 +75,18 @@ public class Command {
* *
* @return Copy of the command component array * @return Copy of the command component array
*/ */
@Nonnull public CommandComponent<?>[] getComponents() { @Nonnull @SuppressWarnings("ALL")
final CommandComponent<?>[] commandComponents = new CommandComponent<?>[this.components.length]; public CommandComponent<C, ?>[] getComponents() {
System.arraycopy(this.components, 0, commandComponents, 0, this.components.length); return (CommandComponent<C, ?>[]) Arrays.asList(this.components).toArray();
return commandComponents; }
/**
* Get the command execution handler
*
* @return Command execution handler
*/
@Nonnull public CommandExecutionHandler<C> getCommandExecutionHandler() {
return this.commandExecutionHandler;
} }
/** /**
@ -70,8 +95,8 @@ public class Command {
* *
* @return List containing the longest shared component chain * @return List containing the longest shared component chain
*/ */
public List<CommandComponent<?>> getSharedComponentChain(@Nonnull final Command other) { public List<CommandComponent<C, ?>> getSharedComponentChain(@Nonnull final Command<C> other) {
final List<CommandComponent<?>> commandComponents = new LinkedList<>(); final List<CommandComponent<C, ?>> commandComponents = new LinkedList<>();
for (int i = 0; i < this.components.length && i < other.components.length; i++) { for (int i = 0; i < this.components.length && i < other.components.length; i++) {
if (this.components[i].equals(other.components[i])) { if (this.components[i].equals(other.components[i])) {
commandComponents.add(this.components[i]); commandComponents.add(this.components[i]);
@ -82,4 +107,52 @@ public class Command {
return commandComponents; return commandComponents;
} }
public static class Builder<C extends CommandSender> {
private final List<CommandComponent<C, ?>> commandComponents;
private final CommandExecutionHandler<C> commandExecutionHandler;
private Builder(@Nonnull final List<CommandComponent<C, ?>> commandComponents, @Nonnull final CommandExecutionHandler<C> commandExecutionHandler) {
this.commandComponents = commandComponents;
this.commandExecutionHandler = commandExecutionHandler;
}
/**
* 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<>(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.commandComponents, commandExecutionHandler);
}
/**
* Build a command using the builder instance
*
* @return Built command
*/
@Nonnull
public Command<C> build() {
return new Command<>(this.commandComponents.toArray(new CommandComponent[0]), this.commandExecutionHandler);
}
}
} }

View file

@ -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 <C> Command sender type
*/
public abstract class CommandManager<C extends CommandSender> {
private final CommandExecutionCoordinator<C> commandExecutionCoordinator;
private final CommandTree<C> commandTree;
protected CommandManager(@Nonnull final Function<CommandTree<C>, CommandExecutionCoordinator<C>> commandExecutionCoordinator,
@Nonnull final CommandRegistrationHandler commandRegistrationHandler) {
this.commandTree = CommandTree.newTree(commandRegistrationHandler);
this.commandExecutionCoordinator = commandExecutionCoordinator.apply(commandTree);
}
public CompletableFuture<CommandResult> executeCommand(@Nonnull final C commandSender, @Nonnull final String input) {
final StringTokenizer stringTokenizer = new StringTokenizer(input, " ");
final Queue<String> tokens = new LinkedList<>();
while (stringTokenizer.hasMoreElements()) {
tokens.add(stringTokenizer.nextToken());
}
final CommandContext<C> 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);
}
}

View file

@ -27,6 +27,7 @@ import com.google.common.base.Objects;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.intellectualsites.commands.components.CommandComponent; import com.intellectualsites.commands.components.CommandComponent;
import com.intellectualsites.commands.components.StaticComponent; import com.intellectualsites.commands.components.StaticComponent;
import com.intellectualsites.commands.context.CommandContext;
import com.intellectualsites.commands.exceptions.NoSuchCommandException; import com.intellectualsites.commands.exceptions.NoSuchCommandException;
import com.intellectualsites.commands.internal.CommandRegistrationHandler; import com.intellectualsites.commands.internal.CommandRegistrationHandler;
import com.intellectualsites.commands.parser.ComponentParseResult; import com.intellectualsites.commands.parser.ComponentParseResult;
@ -44,7 +45,7 @@ import java.util.stream.Collectors;
*/ */
public class CommandTree<C extends CommandSender> { public class CommandTree<C extends CommandSender> {
private final Node<CommandComponent<?>> internalTree = new Node<>(null); private final Node<CommandComponent<C, ?>> internalTree = new Node<>(null);
private final CommandRegistrationHandler commandRegistrationHandler; private final CommandRegistrationHandler commandRegistrationHandler;
private CommandTree(@Nonnull final CommandRegistrationHandler commandRegistrationHandler) { private CommandTree(@Nonnull final CommandRegistrationHandler commandRegistrationHandler) {
@ -63,26 +64,25 @@ public class CommandTree<C extends CommandSender> {
return new CommandTree<>(commandRegistrationHandler); return new CommandTree<>(commandRegistrationHandler);
} }
public Optional<Command> parse(@Nonnull final C commandSender, @Nonnull final String[] args) throws NoSuchCommandException { public Optional<Command<C>> parse(@Nonnull final CommandContext<C> commandContext, @Nonnull final Queue<String> args) throws NoSuchCommandException {
final Queue<String> commandQueue = new LinkedList<>(Arrays.asList(args)); return parseCommand(commandContext, args, this.internalTree);
return parseCommand(commandSender, commandQueue, this.internalTree);
} }
private Optional<Command> parseCommand(@Nonnull final C commandSender, @Nonnull final Queue<String> commandQueue, private Optional<Command<C>> parseCommand(@Nonnull final CommandContext<C> commandContext, @Nonnull final Queue<String> commandQueue,
@Nonnull final Node<CommandComponent<?>> root) throws NoSuchCommandException { @Nonnull final Node<CommandComponent<C, ?>> root) throws NoSuchCommandException {
final List<Node<CommandComponent<?>>> children = root.getChildren(); final List<Node<CommandComponent<C, ?>>> children = root.getChildren();
if (children.size() == 1 && !(children.get(0).getValue() instanceof StaticComponent)) { if (children.size() == 1 && !(children.get(0).getValue() instanceof StaticComponent)) {
// The value has to be a variable // The value has to be a variable
final Node<CommandComponent<?>> child = children.get(0); final Node<CommandComponent<C, ?>> child = children.get(0);
if (child.getValue() != null) { 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()) { if (result.getParsedValue().isPresent()) {
/* TODO: Add context */ /* TODO: Add context */
if (child.isLeaf()) { if (child.isLeaf()) {
return Optional.ofNullable(child.getValue().getOwningCommand()); return Optional.ofNullable(child.getValue().getOwningCommand());
} else { } else {
return this.parseCommand(commandSender, commandQueue, child); return this.parseCommand(commandContext, commandQueue, child);
} }
} else if (result.getFailure().isPresent()) { } else if (result.getFailure().isPresent()) {
/* TODO: Return error */ /* TODO: Return error */
@ -97,13 +97,12 @@ public class CommandTree<C extends CommandSender> {
return Optional.of(root.getValue().getOwningCommand()); return Optional.of(root.getValue().getOwningCommand());
} else { } else {
/* TODO: Indicate that we could not resolve the command here */ /* TODO: Indicate that we could not resolve the command here */
final List<CommandComponent<?>> components = this.getChain(root).stream().map(Node::getValue).collect(Collectors.toList()); final List<CommandComponent<C, ?>> components = this.getChain(root).stream().map(Node::getValue).collect(Collectors.toList());
} }
} else { } else {
/*
final String popped = commandQueue.poll(); final String popped = commandQueue.poll();
if (popped == null) { if (popped == null) {
/* Not enough arguments */
/* TODO: Send correct usage */
return Optional.empty(); return Optional.empty();
} }
@ -122,7 +121,6 @@ public class CommandTree<C extends CommandSender> {
} else if (comparison > 0) { } else if (comparison > 0) {
high = mid - 1; high = mid - 1;
} else { } else {
/* We found a match */
if (node.isLeaf()) { if (node.isLeaf()) {
return Optional.ofNullable(node.getValue().getOwningCommand()); return Optional.ofNullable(node.getValue().getOwningCommand());
} else { } else {
@ -130,26 +128,27 @@ public class CommandTree<C extends CommandSender> {
} }
} }
} }
*/
/* We could not find a match */ final Iterator<Node<CommandComponent<C, ?>>> childIterator = root.getChildren().iterator();
throw new NoSuchCommandException(commandSender, getChain(root).stream().map(Node::getValue).collect(Collectors.toList()), popped); if (childIterator.hasNext()) {
} while (childIterator.hasNext()) {
final Node<CommandComponent<C, ?>> child = childIterator.next();
/* if (child.getValue() != null) {
final Iterator<Node<CommandComponent<?>>> childIterator = root.getChildren().iterator(); final ComponentParseResult<?> result = child.getValue().getParser().parse(commandContext, commandQueue);
if (childIterator.hasNext()) { if (result.getParsedValue().isPresent()) {
while (childIterator.hasNext()) { return this.parseCommand(commandContext, commandQueue, child);
final Node<CommandComponent<?>> child = childIterator.next(); } else if (result.getFailure().isPresent() && root.children.size() == 1) {
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) {
} }
} }
} }
/* 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(); return Optional.empty();
} }
@ -159,10 +158,10 @@ public class CommandTree<C extends CommandSender> {
* *
* @param command Command to insert * @param command Command to insert
*/ */
public void insertCommand(@Nonnull final Command command) { public void insertCommand(@Nonnull final Command<C> command) {
Node<CommandComponent<?>> node = this.internalTree; Node<CommandComponent<C, ?>> node = this.internalTree;
for (final CommandComponent<?> component : command.getComponents()) { for (final CommandComponent<C, ?> component : command.getComponents()) {
Node<CommandComponent<?>> tempNode = node.getChild(component); Node<CommandComponent<C, ?>> tempNode = node.getChild(component);
if (tempNode == null) { if (tempNode == null) {
tempNode = node.addChild(component); tempNode = node.addChild(component);
} }
@ -202,12 +201,12 @@ public class CommandTree<C extends CommandSender> {
/* TODO: Figure out a way to register all combinations along a command component path */ /* TODO: Figure out a way to register all combinations along a command component path */
} }
private void checkAmbiguity(@Nonnull final Node<CommandComponent<?>> node) { private void checkAmbiguity(@Nonnull final Node<CommandComponent<C, ?>> node) {
if (node.isLeaf()) { if (node.isLeaf()) {
return; return;
} }
final int size = node.children.size(); final int size = node.children.size();
for (final Node<CommandComponent<?>> child : node.children) { for (final Node<CommandComponent<C, ?>> child : node.children) {
if (child.getValue() != null && !child.getValue().isRequired() && size > 1) { if (child.getValue() != null && !child.getValue().isRequired() && size > 1) {
// TODO: Use a custom exception type here // TODO: Use a custom exception type here
throw new IllegalStateException("Ambiguous command node found: " + node.getValue()); throw new IllegalStateException("Ambiguous command node found: " + node.getValue());
@ -216,8 +215,8 @@ public class CommandTree<C extends CommandSender> {
node.children.forEach(this::checkAmbiguity); node.children.forEach(this::checkAmbiguity);
} }
private List<CommandComponent<?>> getLeaves(@Nonnull final Node<CommandComponent<?>> node) { private List<CommandComponent<C, ?>> getLeaves(@Nonnull final Node<CommandComponent<C, ?>> node) {
final List<CommandComponent<?>> leaves = new LinkedList<>(); final List<CommandComponent<C, ?>> leaves = new LinkedList<>();
if (node.isLeaf()) { if (node.isLeaf()) {
if (node.getValue() != null) { if (node.getValue() != null) {
leaves.add(node.getValue()); leaves.add(node.getValue());
@ -228,9 +227,9 @@ public class CommandTree<C extends CommandSender> {
return leaves; return leaves;
} }
private List<Node<CommandComponent<?>>> getChain(@Nullable final Node<CommandComponent<?>> end) { private List<Node<CommandComponent<C, ?>>> getChain(@Nullable final Node<CommandComponent<C, ?>> end) {
final List<Node<CommandComponent<?>>> chain = new LinkedList<>(); final List<Node<CommandComponent<C, ?>>> chain = new LinkedList<>();
Node<CommandComponent<?>> tail = end; Node<CommandComponent<C, ?>> tail = end;
while (tail != null) { while (tail != null) {
chain.add(tail); chain.add(tail);
tail = end.getParent(); tail = end.getParent();

View file

@ -25,6 +25,7 @@ package com.intellectualsites.commands.components;
import com.intellectualsites.commands.Command; import com.intellectualsites.commands.Command;
import com.intellectualsites.commands.parser.ComponentParser; import com.intellectualsites.commands.parser.ComponentParser;
import com.intellectualsites.commands.sender.CommandSender;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@ -34,9 +35,10 @@ import java.util.regex.Pattern;
/** /**
* A component that belongs to a command * A component that belongs to a command
* *
* @param <C> Command sender type
* @param <T> The type that the component parses into * @param <T> The type that the component parses into
*/ */
public class CommandComponent<T> implements Comparable<CommandComponent<?>> { public class CommandComponent<C extends CommandSender, T> implements Comparable<CommandComponent<?, ?>> {
private static final Pattern NAME_PATTERN = Pattern.compile("[A-Za-z0-9]+"); private static final Pattern NAME_PATTERN = Pattern.compile("[A-Za-z0-9]+");
@ -57,12 +59,12 @@ public class CommandComponent<T> implements Comparable<CommandComponent<?>> {
* The parser that is used to parse the command input * The parser that is used to parse the command input
* into the corresponding command type * into the corresponding command type
*/ */
private final ComponentParser<T> parser; private final ComponentParser<C, T> parser;
private Command owningCommand; private Command<C> owningCommand;
CommandComponent(final boolean required, @Nonnull final String name, CommandComponent(final boolean required, @Nonnull final String name,
@Nonnull final ComponentParser<T> parser) { @Nonnull final ComponentParser<C, T> parser) {
this.required = required; this.required = required;
this.name = Objects.requireNonNull(name, "Name may not be null"); this.name = Objects.requireNonNull(name, "Name may not be null");
if (!NAME_PATTERN.asPredicate().test(name)) { if (!NAME_PATTERN.asPredicate().test(name)) {
@ -75,10 +77,11 @@ public class CommandComponent<T> implements Comparable<CommandComponent<?>> {
* Create a new command component * Create a new command component
* *
* @param clazz Argument class * @param clazz Argument class
* @param <C> Command sender type
* @param <T> Argument Type * @param <T> Argument Type
* @return Component builder * @return Component builder
*/ */
@Nonnull public static <T> CommandComponent.Builder<T> ofType(@Nonnull final Class<T> clazz) { @Nonnull public static <C extends CommandSender, T> CommandComponent.Builder<C, T> ofType(@Nonnull final Class<T> clazz) {
return new Builder<>(); return new Builder<>();
} }
@ -106,7 +109,7 @@ public class CommandComponent<T> implements Comparable<CommandComponent<?>> {
* *
* @return Command parser * @return Command parser
*/ */
@Nonnull public ComponentParser<T> getParser() { @Nonnull public ComponentParser<C, T> getParser() {
return this.parser; return this.parser;
} }
@ -119,7 +122,7 @@ public class CommandComponent<T> implements Comparable<CommandComponent<?>> {
* *
* @return Owning command * @return Owning command
*/ */
@Nullable public Command getOwningCommand() { @Nullable public Command<C> getOwningCommand() {
return this.owningCommand; return this.owningCommand;
} }
@ -128,7 +131,7 @@ public class CommandComponent<T> implements Comparable<CommandComponent<?>> {
* *
* @param owningCommand Owning command * @param owningCommand Owning command
*/ */
public void setOwningCommand(@Nonnull final Command owningCommand) { public void setOwningCommand(@Nonnull final Command<C> owningCommand) {
if (this.owningCommand != null) { if (this.owningCommand != null) {
throw new IllegalStateException("Cannot replace owning command"); throw new IllegalStateException("Cannot replace owning command");
} }
@ -143,7 +146,7 @@ public class CommandComponent<T> implements Comparable<CommandComponent<?>> {
if (o == null || getClass() != o.getClass()) { if (o == null || getClass() != o.getClass()) {
return false; 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()); return isRequired() == that.isRequired() && com.google.common.base.Objects.equal(getName(), that.getName());
} }
@ -153,7 +156,7 @@ public class CommandComponent<T> implements Comparable<CommandComponent<?>> {
} }
@Override @Override
public int compareTo(@Nonnull final CommandComponent<?> o) { public int compareTo(@Nonnull final CommandComponent<?, ?> o) {
if (this instanceof StaticComponent) { if (this instanceof StaticComponent) {
if (o instanceof StaticComponent) { if (o instanceof StaticComponent) {
return (this.getName().compareTo(o.getName())); return (this.getName().compareTo(o.getName()));
@ -170,11 +173,11 @@ public class CommandComponent<T> implements Comparable<CommandComponent<?>> {
} }
public static class Builder<T> { public static class Builder<C extends CommandSender, T> {
private String name; private String name;
private boolean required = true; private boolean required = true;
private ComponentParser<T> parser; private ComponentParser<C, T> parser;
private Builder() { private Builder() {
} }
@ -185,7 +188,7 @@ public class CommandComponent<T> implements Comparable<CommandComponent<?>> {
* @param name Alphanumeric component name * @param name Alphanumeric component name
* @return Builder instance * @return Builder instance
*/ */
@Nonnull public Builder<T> named(@Nonnull final String name) { @Nonnull public Builder<C, T> named(@Nonnull final String name) {
this.name = name; this.name = name;
return this; return this;
} }
@ -199,7 +202,7 @@ public class CommandComponent<T> implements Comparable<CommandComponent<?>> {
* *
* @return Builder instance * @return Builder instance
*/ */
@Nonnull public Builder<T> asRequired() { @Nonnull public Builder<C, T> asRequired() {
this.required = true; this.required = true;
return this; return this;
} }
@ -213,7 +216,7 @@ public class CommandComponent<T> implements Comparable<CommandComponent<?>> {
* *
* @return Builder instance * @return Builder instance
*/ */
@Nonnull public Builder<T> asOptional() { @Nonnull public Builder<C, T> asOptional() {
this.required = false; this.required = false;
return this; return this;
} }
@ -224,7 +227,7 @@ public class CommandComponent<T> implements Comparable<CommandComponent<?>> {
* @param parser Component parser * @param parser Component parser
* @return Builder instance * @return Builder instance
*/ */
@Nonnull public Builder<T> withParser(@Nonnull final ComponentParser<T> parser) { @Nonnull public Builder<C, T> withParser(@Nonnull final ComponentParser<C, T> parser) {
this.parser = Objects.requireNonNull(parser, "Parser may not be null"); this.parser = Objects.requireNonNull(parser, "Parser may not be null");
return this; return this;
} }
@ -234,7 +237,7 @@ public class CommandComponent<T> implements Comparable<CommandComponent<?>> {
* *
* @return Constructed component * @return Constructed component
*/ */
@Nonnull public CommandComponent<T> build() { @Nonnull public CommandComponent<C, T> build() {
return new CommandComponent<>(this.required, this.name, this.parser); return new CommandComponent<>(this.required, this.name, this.parser);
} }

View file

@ -23,6 +23,7 @@
// //
package com.intellectualsites.commands.components; package com.intellectualsites.commands.components;
import com.intellectualsites.commands.context.CommandContext;
import com.intellectualsites.commands.parser.ComponentParseResult; import com.intellectualsites.commands.parser.ComponentParseResult;
import com.intellectualsites.commands.parser.ComponentParser; import com.intellectualsites.commands.parser.ComponentParser;
import com.intellectualsites.commands.sender.CommandSender; import com.intellectualsites.commands.sender.CommandSender;
@ -33,14 +34,22 @@ import java.util.HashSet;
import java.util.Queue; import java.util.Queue;
import java.util.Set; import java.util.Set;
public final class StaticComponent extends CommandComponent<String> { public final class StaticComponent<C extends CommandSender> extends CommandComponent<C, String> {
private StaticComponent(final boolean required, @Nonnull final String name, @Nonnull final String ... aliases) { 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 <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 implements ComponentParser<String> { private static final class StaticComponentParser<C extends CommandSender> implements ComponentParser<C, String> {
private final String name; private final String name;
private final Set<String> acceptedStrings = new HashSet<>(); private final Set<String> acceptedStrings = new HashSet<>();
@ -50,7 +59,7 @@ public final class StaticComponent extends CommandComponent<String> {
this.acceptedStrings.addAll(Arrays.asList(aliases)); this.acceptedStrings.addAll(Arrays.asList(aliases));
} }
@Nonnull @Override public ComponentParseResult<String> parse(@Nonnull final CommandSender sender, @Nonnull final Queue<String> inputQueue) { @Nonnull @Override public ComponentParseResult<String> parse(@Nonnull final CommandContext<C> commandContext, @Nonnull final Queue<String> inputQueue) {
final String string = inputQueue.peek(); final String string = inputQueue.peek();
if (string == null) { if (string == null) {
return ComponentParseResult.failure(this.name); return ComponentParseResult.failure(this.name);

View file

@ -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<C extends CommandSender> {
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;
}
}

View file

@ -36,7 +36,7 @@ import java.util.List;
public class CommandParseException extends IllegalArgumentException { public class CommandParseException extends IllegalArgumentException {
private final CommandSender commandSender; private final CommandSender commandSender;
private final List<CommandComponent<?>> currentChain; private final List<CommandComponent<?, ?>> currentChain;
/** /**
* Construct a new command parse exception * Construct a new command parse exception
@ -44,7 +44,7 @@ public class CommandParseException extends IllegalArgumentException {
* @param commandSender Sender who executed the command * @param commandSender Sender who executed the command
* @param currentChain Chain leading up to the exception * @param currentChain Chain leading up to the exception
*/ */
protected CommandParseException(@Nonnull final CommandSender commandSender, @Nonnull final List<CommandComponent<?>> currentChain) { protected CommandParseException(@Nonnull final CommandSender commandSender, @Nonnull final List<CommandComponent<?, ?>> currentChain) {
this.commandSender = commandSender; this.commandSender = commandSender;
this.currentChain = currentChain; this.currentChain = currentChain;
} }
@ -65,7 +65,7 @@ public class CommandParseException extends IllegalArgumentException {
* @return Unmodifiable list of command components * @return Unmodifiable list of command components
*/ */
@Nonnull @Nonnull
public List<CommandComponent<?>> getCurrentChain() { public List<CommandComponent<?, ?>> getCurrentChain() {
return Collections.unmodifiableList(this.currentChain); return Collections.unmodifiableList(this.currentChain);
} }

View file

@ -44,7 +44,7 @@ public class NoSuchCommandException extends CommandParseException {
* @param currentChain Chain leading up to the exception * @param currentChain Chain leading up to the exception
* @param command Entered command (following the command chain) * @param command Entered command (following the command chain)
*/ */
public NoSuchCommandException(@Nonnull final CommandSender commandSender, @Nonnull final List<CommandComponent<?>> currentChain, public NoSuchCommandException(@Nonnull final CommandSender commandSender, @Nonnull final List<CommandComponent<?, ?>> currentChain,
@Nonnull final String command) { @Nonnull final String command) {
super(commandSender, currentChain); super(commandSender, currentChain);
this.suppliedCommand = command; this.suppliedCommand = command;

View file

@ -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 <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,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 <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

@ -23,20 +23,23 @@
// //
package com.intellectualsites.commands.parser; package com.intellectualsites.commands.parser;
import com.intellectualsites.commands.context.CommandContext;
import com.intellectualsites.commands.sender.CommandSender; import com.intellectualsites.commands.sender.CommandSender;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import java.util.Queue; import java.util.Queue;
@FunctionalInterface public interface ComponentParser<T> { @FunctionalInterface
public interface ComponentParser<C extends CommandSender, T> {
/** /**
* Parse command input into a command result * Parse command input into a command result
* *
* @param sender Sender who sent the command * @param commandContext Command context
* @param inputQueue The queue of arguments * @param inputQueue The queue of arguments
* @return Parsed command result * @return Parsed command result
*/ */
@Nonnull ComponentParseResult<T> parse(@Nonnull CommandSender sender, @Nonnull Queue<String> inputQueue); @Nonnull
ComponentParseResult<T> parse(@Nonnull CommandContext<C> commandContext, @Nonnull Queue<String> inputQueue);
} }