Make the brigadier mapper a bit smarter

This commit is contained in:
Alexander Söderberg 2020-09-15 17:27:41 +02:00
parent d78d64329b
commit c88b267758
No known key found for this signature in database
GPG key ID: C0207FF7EA146678
6 changed files with 160 additions and 42 deletions

View file

@ -67,4 +67,14 @@ public class InvalidSyntaxException extends CommandParseException {
return String.format("Invalid command syntax. Correct syntax is: %s", this.correctSyntax);
}
@Override
public final synchronized Throwable fillInStackTrace() {
return this;
}
@Override
public final synchronized Throwable initCause(final Throwable cause) {
return this;
}
}

View file

@ -67,4 +67,14 @@ public class NoPermissionException extends CommandParseException {
return this.missingPermission;
}
@Override
public final synchronized Throwable fillInStackTrace() {
return this;
}
@Override
public final synchronized Throwable initCause(final Throwable cause) {
return this;
}
}

View file

@ -75,4 +75,14 @@ public final class NoSuchCommandException extends CommandParseException {
return this.suppliedCommand;
}
@Override
public synchronized Throwable fillInStackTrace() {
return this;
}
@Override
public synchronized Throwable initCause(final Throwable cause) {
return this;
}
}

View file

@ -25,6 +25,7 @@ package com.intellectualsites.commands.brigadier;
import com.google.common.collect.Maps;
import com.google.common.reflect.TypeToken;
import com.intellectualsites.commands.CommandManager;
import com.intellectualsites.commands.CommandTree;
import com.intellectualsites.commands.components.CommandComponent;
import com.intellectualsites.commands.components.StaticComponent;
@ -35,21 +36,31 @@ import com.intellectualsites.commands.components.standard.FloatComponent;
import com.intellectualsites.commands.components.standard.IntegerComponent;
import com.intellectualsites.commands.components.standard.ShortComponent;
import com.intellectualsites.commands.components.standard.StringComponent;
import com.intellectualsites.commands.execution.preprocessor.CommandPreprocessingContext;
import com.intellectualsites.commands.sender.CommandSender;
import com.mojang.brigadier.LiteralMessage;
import com.mojang.brigadier.arguments.ArgumentType;
import com.mojang.brigadier.arguments.BoolArgumentType;
import com.mojang.brigadier.arguments.DoubleArgumentType;
import com.mojang.brigadier.arguments.FloatArgumentType;
import com.mojang.brigadier.arguments.IntegerArgumentType;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.builder.ArgumentBuilder;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.suggestion.SuggestionProvider;
import com.mojang.brigadier.tree.CommandNode;
import com.mojang.brigadier.suggestion.Suggestions;
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
import com.mojang.brigadier.tree.LiteralCommandNode;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.function.BiPredicate;
import java.util.function.Function;
import java.util.function.Supplier;
@ -69,13 +80,22 @@ public final class CloudBrigadierManager<C extends CommandSender, S> {
private final Map<Class<?>, Function<? extends CommandComponent<C, ?>,
? extends ArgumentType<?>>> mappers;
private final Map<Class<?>, Supplier<ArgumentType<?>>> defaultArgumentTypeSuppliers;
private final Supplier<com.intellectualsites.commands.context.CommandContext<C>> dummyContextProvider;
private final CommandManager<C, ?> commandManager;
/**
* Create a new cloud brigadier manager
*
* @param commandManager Command manager
* @param dummyContextProvider Provider of dummy context for completions
*/
public CloudBrigadierManager() {
public CloudBrigadierManager(@Nonnull final CommandManager<C, ?> commandManager,
@Nonnull final Supplier<com.intellectualsites.commands.context.CommandContext<C>>
dummyContextProvider) {
this.mappers = Maps.newHashMap();
this.defaultArgumentTypeSuppliers = Maps.newHashMap();
this.commandManager = commandManager;
this.dummyContextProvider = dummyContextProvider;
this.registerInternalMappings();
}
@ -195,25 +215,29 @@ public final class CloudBrigadierManager<C extends CommandSender, S> {
* @param <K> cloud component type (generic)
* @return Brigadier argument type
*/
@Nonnull
@Nullable
@SuppressWarnings("all")
public <T, K extends CommandComponent<?, ?>> ArgumentType<?> getArgument(@Nonnull final TypeToken<T> componentType,
private <T, K extends CommandComponent<?, ?>> Pair<ArgumentType<?>, Boolean> getArgument(
@Nonnull final TypeToken<T> componentType,
@Nonnull final K component) {
final CommandComponent<C, ?> commandComponent = (CommandComponent<C, ?>) component;
final Function function = this.mappers.getOrDefault(componentType.getRawType(), t ->
createDefaultMapper((CommandComponent<C, T>) component));
return (ArgumentType<?>) function.apply(commandComponent);
Function function = this.mappers.get(componentType.getRawType());
if (function == null) {
return this.createDefaultMapper(commandComponent);
}
return new Pair<>((ArgumentType<?>) function.apply(commandComponent), !component.getValueType().equals(String.class));
}
@Nonnull
private <T, K extends CommandComponent<C, T>> ArgumentType<?> createDefaultMapper(@Nonnull final CommandComponent<C, T>
private <T, K extends CommandComponent<C, T>> Pair<ArgumentType<?>, Boolean> createDefaultMapper(
@Nonnull final CommandComponent<C, T>
component) {
final Supplier<ArgumentType<?>> argumentTypeSupplier = this.defaultArgumentTypeSuppliers.get(component.getValueType());
if (argumentTypeSupplier != null) {
return argumentTypeSupplier.get();
return new Pair<>(argumentTypeSupplier.get(), true);
}
System.err.printf("Found not native mapping for '%s'\n", component.getValueType().getCanonicalName());
return StringArgumentType.string();
return new Pair<>(StringArgumentType.string(), false);
}
/**
@ -233,45 +257,106 @@ public final class CloudBrigadierManager<C extends CommandSender, S> {
@Nonnull final com.mojang.brigadier.Command<S> executor,
@Nonnull final BiPredicate<S, String> permissionChecker) {
final LiteralArgumentBuilder<S> literalArgumentBuilder = LiteralArgumentBuilder.<S>literal(root.getLiteral())
.requires(sender -> permissionChecker.test(sender, cloudCommand.getNodeMeta().getOrDefault("permission", "")));
if (cloudCommand.isLeaf() && cloudCommand.getValue() != null && cloudCommand.getValue().getOwningCommand() != null) {
literalArgumentBuilder.executes(executor);
}
.requires(sender -> permissionChecker.test(sender, cloudCommand.getNodeMeta().getOrDefault("permission", "")))
.executes(executor);
final LiteralCommandNode<S> constructedRoot = literalArgumentBuilder.build();
for (final CommandTree.Node<CommandComponent<C, ?>> child : cloudCommand.getChildren()) {
constructedRoot.addChild(this.constructCommandNode(child, permissionChecker, executor, suggestionProvider));
constructedRoot.addChild(this.constructCommandNode(child, permissionChecker, executor, suggestionProvider).build());
}
return constructedRoot;
}
private CommandNode<S> constructCommandNode(@Nonnull final CommandTree.Node<CommandComponent<C, ?>> root,
private ArgumentBuilder<S, ?> constructCommandNode(@Nonnull final CommandTree.Node<CommandComponent<C, ?>> root,
@Nonnull final BiPredicate<S, String> permissionChecker,
@Nonnull final com.mojang.brigadier.Command<S> executor,
@Nonnull final SuggestionProvider<S> suggestionProvider) {
CommandNode<S> commandNode;
ArgumentBuilder<S, ?> argumentBuilder;
if (root.getValue() instanceof StaticComponent) {
final LiteralArgumentBuilder<S> argumentBuilder = LiteralArgumentBuilder.<S>literal(root.getValue().getName())
.requires(sender -> permissionChecker.test(sender, root.getNodeMeta().getOrDefault("permission", "")));
if (root.isLeaf()) {
argumentBuilder.executes(executor);
}
commandNode = argumentBuilder.build();
argumentBuilder = LiteralArgumentBuilder.<S>literal(root.getValue().getName())
.requires(sender -> permissionChecker.test(sender, root.getNodeMeta().getOrDefault("permission", "")))
.executes(executor);
} else {
@SuppressWarnings("unchecked") final RequiredArgumentBuilder<S, Object> builder = RequiredArgumentBuilder
.<S, Object>argument(root.getValue().getName(),
(ArgumentType<Object>) getArgument(TypeToken.of(root.getValue().getClass()),
root.getValue()))
.suggests(suggestionProvider)
.requires(sender -> permissionChecker.test(sender, root.getNodeMeta().getOrDefault("permission", "")));
if (root.isLeaf() || !root.getValue().isRequired()) {
builder.executes(executor);
}
commandNode = builder.build();
final Pair<ArgumentType<?>, Boolean> pair = this.getArgument(TypeToken.of(root.getValue().getClass()),
root.getValue());
final SuggestionProvider<S> provider = pair.getRight() ? null : suggestionProvider;
argumentBuilder = RequiredArgumentBuilder
.<S, Object>argument(root.getValue().getName(), (ArgumentType<Object>) pair.getLeft())
.suggests(provider)
.requires(sender -> permissionChecker.test(sender, root.getNodeMeta().getOrDefault("permission", "")))
.executes(executor);
}
for (final CommandTree.Node<CommandComponent<C, ?>> node : root.getChildren()) {
commandNode.addChild(constructCommandNode(node, permissionChecker, executor, suggestionProvider));
argumentBuilder.then(constructCommandNode(node, permissionChecker, executor, suggestionProvider));
}
return commandNode;
return argumentBuilder;
}
@Nonnull
private CompletableFuture<Suggestions> buildSuggestions(@Nonnull final CommandComponent<C, ?> component,
@Nonnull final CommandContext<S> s,
@Nonnull final SuggestionsBuilder builder) {
final com.intellectualsites.commands.context.CommandContext<C> commandContext = this.dummyContextProvider.get();
final LinkedList<String> inputQueue = new LinkedList<>(Collections.singletonList(builder.getInput()));
final CommandPreprocessingContext<C> commandPreprocessingContext =
new CommandPreprocessingContext<>(commandContext, inputQueue);
/*
List<String> results = server.tabComplete(context.getSource().getBukkitSender(), builder.getInput(),
context.getSource().getWorld(), context.getSource().getPosition(), true);
*/
String command = builder.getInput();
if (command.startsWith("/") /* Minecraft specific */) {
command = command.substring(1);
}
final List<String> suggestions = this.commandManager.suggest(commandContext.getSender(), command);
/*component.getParser().suggestions(commandContext, builder.getInput());*/
for (final String suggestion : suggestions) {
System.out.printf("- %s\n", suggestion);
}
/*
System.out.println("Filtering out with: " + builder.getInput());
final CommandSuggestionProcessor<C> processor = this.commandManager.getCommandSuggestionProcessor();
final List<String> filteredSuggestions = processor.apply(commandPreprocessingContext, suggestions);
System.out.println("Current suggestions: ");
for (final String suggestion : filteredSuggestions) {
System.out.printf("- %s\n", suggestion);
}*/
for (final String suggestion : suggestions) {
String tooltip = component.getName();
if (!(component instanceof StaticComponent)) {
if (component.isRequired()) {
tooltip = '<' + tooltip + '>';
} else {
tooltip = '[' + tooltip + ']';
}
}
builder.suggest(suggestion, new LiteralMessage(tooltip));
}
return builder.buildFuture();
}
private static final class Pair<L, R> {
private final L left;
private final R right;
private Pair(@Nonnull final L left, @Nonnull final R right) {
this.left = left;
this.right = right;
}
@Nonnull
private L getLeft() {
return this.left;
}
@Nonnull
private R getRight() {
return this.right;
}
}
}

View file

@ -59,8 +59,10 @@ final class BukkitCommand<C extends com.intellectualsites.commands.sender.Comman
builder.toString())
.whenComplete(((commandResult, throwable) -> {
if (throwable != null) {
commandSender.sendMessage(ChatColor.RED + throwable.getMessage());
commandSender.sendMessage(ChatColor.RED + throwable.getCause().getMessage());
throwable.printStackTrace();
throwable.getCause().printStackTrace();
} else {
// Do something...
commandSender.sendMessage("All good!");

View file

@ -27,11 +27,11 @@ import com.destroystokyo.paper.brigadier.BukkitBrigadierCommandSource;
import com.destroystokyo.paper.event.brigadier.CommandRegisteredEvent;
import com.intellectualsites.commands.brigadier.CloudBrigadierManager;
import com.intellectualsites.commands.components.CommandComponent;
import com.intellectualsites.commands.context.CommandContext;
import com.intellectualsites.commands.sender.CommandSender;
import com.mojang.brigadier.arguments.ArgumentType;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.entity.EntityType;
import org.bukkit.event.EventHandler;
@ -50,15 +50,15 @@ class PaperBrigadierListener<C extends CommandSender> implements Listener {
PaperBrigadierListener(@Nonnull final PaperCommandManager<C> paperCommandManager) throws Exception {
this.paperCommandManager = paperCommandManager;
this.brigadierManager = new CloudBrigadierManager<>();
this.brigadierManager = new CloudBrigadierManager<>(this.paperCommandManager,
() -> new CommandContext<>(this.paperCommandManager.getCommandSenderMapper()
.apply(Bukkit.getConsoleSender())));
/* Register default mappings */
final String version = Bukkit.getServer().getClass().getPackage().getName();
this.nmsVersion = version.substring(version.lastIndexOf(".") + 1);
try {
/* Map UUID */
this.mapSimpleNMS(UUID.class, this.getNMSArgument("UUID").getConstructor());
/* Map World */
this.mapSimpleNMS(World.class, this.getNMSArgument("Dimension").getConstructor());
/* Map Enchantment */
this.mapSimpleNMS(Enchantment.class, this.getNMSArgument("Enchantment").getConstructor());
/* Map EntityType */
@ -120,7 +120,8 @@ class PaperBrigadierListener<C extends CommandSender> implements Listener {
event.getLiteral(),
event.getBrigadierCommand(),
event.getBrigadierCommand(),
(s, p) -> s.getBukkitSender().hasPermission(p)));
(s, p) -> p.isEmpty()
|| s.getBukkitSender().hasPermission(p)));
}
}