Some minor code cleanup
This commit is contained in:
parent
514aa6c725
commit
f2aeae334c
9 changed files with 267 additions and 66 deletions
|
|
@ -45,8 +45,9 @@ import java.util.function.Function;
|
|||
* The manager is responsible for command registration, parsing delegation, etc.
|
||||
*
|
||||
* @param <C> Command sender type
|
||||
* @param <M> Commamd meta type
|
||||
* @param <M> Command meta type
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public abstract class CommandManager<C extends CommandSender, M extends CommandMeta> {
|
||||
|
||||
private final CommandContextFactory<C> commandContextFactory = new StandardCommandContextFactory<>();
|
||||
|
|
|
|||
|
|
@ -27,8 +27,10 @@ import com.intellectualsites.commands.components.CommandComponent;
|
|||
import com.intellectualsites.commands.components.StaticComponent;
|
||||
import com.intellectualsites.commands.components.parser.ComponentParseResult;
|
||||
import com.intellectualsites.commands.context.CommandContext;
|
||||
import com.intellectualsites.commands.exceptions.AmbiguousNodeException;
|
||||
import com.intellectualsites.commands.exceptions.ComponentParseException;
|
||||
import com.intellectualsites.commands.exceptions.InvalidSyntaxException;
|
||||
import com.intellectualsites.commands.exceptions.NoCommandInLeafException;
|
||||
import com.intellectualsites.commands.exceptions.NoPermissionException;
|
||||
import com.intellectualsites.commands.exceptions.NoSuchCommandException;
|
||||
import com.intellectualsites.commands.internal.CommandRegistrationHandler;
|
||||
|
|
@ -61,10 +63,10 @@ public class CommandTree<C extends CommandSender, M extends CommandMeta> {
|
|||
|
||||
private final Node<CommandComponent<C, ?>> internalTree = new Node<>(null);
|
||||
private final CommandManager<C, M> commandManager;
|
||||
private final CommandRegistrationHandler commandRegistrationHandler;
|
||||
private final CommandRegistrationHandler<M> commandRegistrationHandler;
|
||||
|
||||
private CommandTree(@Nonnull final CommandManager<C, M> commandManager,
|
||||
@Nonnull final CommandRegistrationHandler commandRegistrationHandler) {
|
||||
@Nonnull final CommandRegistrationHandler<M> commandRegistrationHandler) {
|
||||
this.commandManager = commandManager;
|
||||
this.commandRegistrationHandler = commandRegistrationHandler;
|
||||
}
|
||||
|
|
@ -81,13 +83,23 @@ public class CommandTree<C extends CommandSender, M extends CommandMeta> {
|
|||
@Nonnull
|
||||
public static <C extends CommandSender, M extends CommandMeta> CommandTree<C, M> newTree(
|
||||
@Nonnull final CommandManager<C, M> commandManager,
|
||||
@Nonnull final CommandRegistrationHandler commandRegistrationHandler) {
|
||||
@Nonnull final CommandRegistrationHandler<M> commandRegistrationHandler) {
|
||||
return new CommandTree<>(commandManager, commandRegistrationHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to parse string input into a command
|
||||
*
|
||||
* @param commandContext Command context instance
|
||||
* @param args Input
|
||||
* @return Parsed command, if one could be found
|
||||
* @throws NoSuchCommandException If there is no command matching the input
|
||||
* @throws NoPermissionException If the sender lacks permission to execute the command
|
||||
* @throws InvalidSyntaxException If the command syntax is invalid
|
||||
*/
|
||||
public Optional<Command<C, M>> parse(@Nonnull final CommandContext<C> commandContext,
|
||||
@Nonnull final Queue<String> args) throws
|
||||
NoSuchCommandException {
|
||||
NoSuchCommandException, NoPermissionException, InvalidSyntaxException {
|
||||
return parseCommand(commandContext, args, this.internalTree);
|
||||
}
|
||||
|
||||
|
|
@ -101,6 +113,68 @@ public class CommandTree<C extends CommandSender, M extends CommandMeta> {
|
|||
.map(Node::getValue)
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
final Optional<Command<C, M>> parsedChild = this.attemptParseUnambiguousChild(commandContext, root, commandQueue);
|
||||
// noinspection all
|
||||
if (parsedChild != null) {
|
||||
return parsedChild;
|
||||
}
|
||||
|
||||
/* There are 0 or more static components as children. No variable child components are present */
|
||||
if (root.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.ofNullable(this.cast(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 {
|
||||
/* Too many arguments. We have a unique path, so we can send the entire context */
|
||||
throw new InvalidSyntaxException(this.commandManager.getCommandSyntaxFormatter()
|
||||
.apply(Objects.requireNonNull(
|
||||
Objects.requireNonNull(root.getValue())
|
||||
.getOwningCommand())
|
||||
.getComponents()),
|
||||
commandContext.getCommandSender(), 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()));
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Optional<Command<C, M>> attemptParseUnambiguousChild(@Nonnull final CommandContext<C> commandContext,
|
||||
@Nonnull final Node<CommandComponent<C, ?>> root,
|
||||
@Nonnull final Queue<String> commandQueue) {
|
||||
String permission;
|
||||
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
|
||||
|
|
@ -166,60 +240,16 @@ public class CommandTree<C extends CommandSender, M extends CommandMeta> {
|
|||
}
|
||||
}
|
||||
}
|
||||
/* 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.ofNullable(this.cast(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 {
|
||||
/* Too many arguments. We have a unique path, so we can send the entire context */
|
||||
throw new InvalidSyntaxException(this.commandManager.getCommandSyntaxFormatter()
|
||||
.apply(Objects.requireNonNull(
|
||||
Objects.requireNonNull(root.getValue())
|
||||
.getOwningCommand())
|
||||
.getComponents()),
|
||||
commandContext.getCommandSender(), 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()));
|
||||
}
|
||||
// noinspection all
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public List<String> getSuggestions(@Nonnull final CommandContext<C> context, @Nonnull final Queue<String> commandQueue) {
|
||||
return getSuggestions(context, commandQueue, this.internalTree);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public List<String> getSuggestions(@Nonnull final CommandContext<C> commandContext,
|
||||
@Nonnull final Queue<String> commandQueue,
|
||||
@Nonnull final Node<CommandComponent<C, ?>> root) {
|
||||
|
|
@ -268,8 +298,8 @@ public class CommandTree<C extends CommandSender, M extends CommandMeta> {
|
|||
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) {
|
||||
}
|
||||
} /* else if (result.getFailure().isPresent() && root.children.size() == 1) {
|
||||
}*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -330,7 +360,7 @@ public class CommandTree<C extends CommandSender, M extends CommandMeta> {
|
|||
return sender.hasPermission(
|
||||
Objects.requireNonNull(Objects.requireNonNull(node.value, "node.value").getOwningCommand(),
|
||||
"owning command").getCommandPermission())
|
||||
? null : node.value.getOwningCommand().getCommandPermission();
|
||||
? null : Objects.requireNonNull(node.value.getOwningCommand(), "owning command").getCommandPermission();
|
||||
}
|
||||
/*
|
||||
if any of the children would permit the execution, then the sender has a valid
|
||||
|
|
@ -359,14 +389,17 @@ public class CommandTree<C extends CommandSender, M extends CommandMeta> {
|
|||
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");
|
||||
throw new NoCommandInLeafException(leaf);
|
||||
} else {
|
||||
this.commandRegistrationHandler.registerCommand(leaf.getOwningCommand());
|
||||
// noinspection unchecked
|
||||
final Command<C, M> owningCommand = (Command<C, M>) leaf.getOwningCommand();
|
||||
this.commandRegistrationHandler.registerCommand(owningCommand);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -391,18 +424,18 @@ public class CommandTree<C extends CommandSender, M extends CommandMeta> {
|
|||
}
|
||||
}
|
||||
});
|
||||
/* TODO: Figure out a way to register all combinations along a command component path */
|
||||
}
|
||||
|
||||
private void checkAmbiguity(@Nonnull final Node<CommandComponent<C, ?>> node) {
|
||||
private void checkAmbiguity(@Nonnull final Node<CommandComponent<C, ?>> node) throws AmbiguousNodeException {
|
||||
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());
|
||||
throw new AmbiguousNodeException(node.getValue(),
|
||||
child.getValue(),
|
||||
node.getChildren().stream().map(Node::getValue).collect(Collectors.toList()));
|
||||
}
|
||||
}
|
||||
node.children.forEach(this::checkAmbiguity);
|
||||
|
|
@ -443,12 +476,12 @@ public class CommandTree<C extends CommandSender, M extends CommandMeta> {
|
|||
return chain;
|
||||
}
|
||||
|
||||
@Nullable private Command<C, M> cast(@Nullable final Command<C, ?> command) {
|
||||
@Nullable
|
||||
private Command<C, M> cast(@Nullable final Command<C, ?> command) {
|
||||
if (command == null) {
|
||||
return null;
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
final Command<C, M> casted = (Command<C, M>) command;
|
||||
@SuppressWarnings("unchecked") final Command<C, M> casted = (Command<C, M>) command;
|
||||
return casted;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,108 @@
|
|||
//
|
||||
// MIT License
|
||||
//
|
||||
// Copyright (c) 2020 Alexander Söderberg
|
||||
//
|
||||
// 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 javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Exception thrown when a {@link com.intellectualsites.commands.context.CommandContext}
|
||||
* is being inserted into a {@link com.intellectualsites.commands.CommandTree} and an ambiguity
|
||||
* is detected.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public final class AmbiguousNodeException extends IllegalStateException {
|
||||
|
||||
private final CommandComponent<?, ?> parentNode;
|
||||
private final CommandComponent<?, ?> ambiguousNode;
|
||||
private final List<CommandComponent<?, ?>> children;
|
||||
|
||||
/**
|
||||
* Construct a new ambiguous node exception
|
||||
*
|
||||
* @param parentNode Parent node
|
||||
* @param ambiguousNode Node that caused exception
|
||||
* @param children All children of the parent
|
||||
*/
|
||||
public AmbiguousNodeException(@Nullable final CommandComponent<?, ?> parentNode,
|
||||
@Nonnull final CommandComponent<?, ?> ambiguousNode,
|
||||
@Nonnull final List<CommandComponent<?, ?>> children) {
|
||||
this.parentNode = parentNode;
|
||||
this.ambiguousNode = ambiguousNode;
|
||||
this.children = children;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the parent node
|
||||
*
|
||||
* @return Parent node
|
||||
*/
|
||||
@Nullable
|
||||
public CommandComponent<?, ?> getParentNode() {
|
||||
return this.parentNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the ambiguous node
|
||||
*
|
||||
* @return Ambiguous node
|
||||
*/
|
||||
@Nonnull
|
||||
public CommandComponent<?, ?> getAmbiguousNode() {
|
||||
return this.ambiguousNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all children of the parent
|
||||
*
|
||||
* @return Child nodes
|
||||
*/
|
||||
@Nonnull
|
||||
public List<CommandComponent<?, ?>> getChildren() {
|
||||
return this.children;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
final StringBuilder stringBuilder = new StringBuilder("Ambiguous Node: ")
|
||||
.append(ambiguousNode.getName())
|
||||
.append(" cannot be added as a child to ")
|
||||
.append(parentNode == null ? "<root>" : parentNode.getName())
|
||||
.append(" (All children: ");
|
||||
final Iterator<CommandComponent<?, ?>> childIterator = this.children.iterator();
|
||||
while (childIterator.hasNext()) {
|
||||
stringBuilder.append(childIterator.next().getName());
|
||||
if (childIterator.hasNext()) {
|
||||
stringBuilder.append(", ");
|
||||
}
|
||||
}
|
||||
return stringBuilder.append(")")
|
||||
.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -33,6 +33,7 @@ import java.util.List;
|
|||
/**
|
||||
* Exception thrown when parsing user input into a command
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public class CommandParseException extends IllegalArgumentException {
|
||||
|
||||
private final CommandSender commandSender;
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ import java.util.List;
|
|||
/**
|
||||
* Exception sent when a {@link CommandSender} inputs invalid command syntax
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public class InvalidSyntaxException extends CommandParseException {
|
||||
|
||||
private final String correctSyntax;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,54 @@
|
|||
//
|
||||
// MIT License
|
||||
//
|
||||
// Copyright (c) 2020 Alexander Söderberg
|
||||
//
|
||||
// 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 javax.annotation.Nonnull;
|
||||
|
||||
/**
|
||||
* Thrown when a {@link com.intellectualsites.commands.components.CommandComponent}
|
||||
* that is registered as a leaf node, does not contain an owning {@link com.intellectualsites.commands.Command}
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public final class NoCommandInLeafException extends IllegalStateException {
|
||||
|
||||
private final CommandComponent<?, ?> commandComponent;
|
||||
|
||||
public NoCommandInLeafException(@Nonnull final CommandComponent<?, ?> commandComponent) {
|
||||
super(String.format("Leaf node '%s' does not have associated owning command", commandComponent.getName()));
|
||||
this.commandComponent = commandComponent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the command component
|
||||
*
|
||||
* @return Command component
|
||||
*/
|
||||
@Nonnull
|
||||
public CommandComponent<?, ?> getCommandComponent() {
|
||||
return this.commandComponent;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -33,6 +33,7 @@ import java.util.List;
|
|||
* Exception thrown when a {@link CommandSender} misses a permission required
|
||||
* to execute a {@link com.intellectualsites.commands.Command}
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public class NoPermissionException extends CommandParseException {
|
||||
|
||||
private final String missingPermission;
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ import java.util.List;
|
|||
* Exception thrown when a command sender tries to execute
|
||||
* a command that doesn't exist
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public class NoSuchCommandException extends CommandParseException {
|
||||
|
||||
private final String suppliedCommand;
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ import java.util.Optional;
|
|||
/**
|
||||
* A simple immutable string-string map containing command meta
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public class SimpleCommandMeta extends CommandMeta {
|
||||
|
||||
private final Map<String, String> metaMap;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue