Some minor code cleanup

This commit is contained in:
Alexander Söderberg 2020-09-11 09:54:32 +02:00
parent 514aa6c725
commit f2aeae334c
9 changed files with 267 additions and 66 deletions

View file

@ -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<>();

View file

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

View file

@ -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();
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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