From 8344adff1a61bb15587b5376b345142a564b2ce2 Mon Sep 17 00:00:00 2001 From: NorbiPeti Date: Thu, 19 Mar 2020 20:19:41 +0100 Subject: [PATCH] Fully implement argument suggestions Suggesting based on each annotation and the parameter type Moving the parameter name to the end of the suggestion list #82 --- .editorconfig | 2 + Chroma-Core/pom.xml | 54 ++++--- .../buttondevteam/core/ChromaCommand.java | 5 - .../buttondevteam/core/ComponentCommand.java | 5 + .../java/buttondevteam/core/MainPlugin.java | 7 +- .../core/component/members/MemberCommand.java | 3 - .../buttondevteam/lib/chat/Command2MC.java | 145 ++++++++++++++---- .../lib/chat/CustomTabCompleteMethod.java | 3 +- pom.xml | 2 +- 9 files changed, 159 insertions(+), 67 deletions(-) diff --git a/.editorconfig b/.editorconfig index a4b334f..b5628a0 100644 --- a/.editorconfig +++ b/.editorconfig @@ -13,3 +13,5 @@ tab_width=4 indent_style=space indent_size=2 +[*.xml] +indent_style = tab diff --git a/Chroma-Core/pom.xml b/Chroma-Core/pom.xml index f73c7d4..32357d5 100755 --- a/Chroma-Core/pom.xml +++ b/Chroma-Core/pom.xml @@ -39,8 +39,9 @@ - me.lucko:commodore - + me.lucko:commodore + org.javatuples:javatuples + @@ -170,33 +171,38 @@ com.github.TBMCPlugins.ChromaCore ButtonProcessor master-SNAPSHOT - compile + compile net.ess3 - EssentialsX - 2.17.1 + EssentialsX + 2.17.1 provided - - com.vexsoftware - nuvotifier-universal - 2.3.4 - provided - - - com.onarandombox.multiversecore - Multiverse-Core - 4.0.1 - provided - - - me.lucko - commodore - 1.7 - compile - - + + com.vexsoftware + nuvotifier-universal + 2.3.4 + provided + + + com.onarandombox.multiversecore + Multiverse-Core + 4.0.1 + provided + + + me.lucko + commodore + 1.7 + compile + + + org.javatuples + javatuples + 1.2 + + TBMCPlugins https://github.com/TBMCPlugins diff --git a/Chroma-Core/src/main/java/buttondevteam/core/ChromaCommand.java b/Chroma-Core/src/main/java/buttondevteam/core/ChromaCommand.java index 06fe7f4..499818f 100644 --- a/Chroma-Core/src/main/java/buttondevteam/core/ChromaCommand.java +++ b/Chroma-Core/src/main/java/buttondevteam/core/ChromaCommand.java @@ -25,9 +25,4 @@ public class ChromaCommand extends ICommand2MC { public void def(CommandSender sender) { sender.sendMessage(ButtonPlugin.getCommand2MC().getCommandsText()); } - - @Command2.Subcommand //TODO: Remove - public void test(CommandSender sender, char test) { - sender.sendMessage(test + ""); - } } diff --git a/Chroma-Core/src/main/java/buttondevteam/core/ComponentCommand.java b/Chroma-Core/src/main/java/buttondevteam/core/ComponentCommand.java index 1611276..d2b8bfd 100644 --- a/Chroma-Core/src/main/java/buttondevteam/core/ComponentCommand.java +++ b/Chroma-Core/src/main/java/buttondevteam/core/ComponentCommand.java @@ -67,6 +67,11 @@ public class ComponentCommand extends ICommand2MC { return getPluginComponents(plugin).map(c -> c.getClass().getSimpleName())::iterator; } + @CustomTabCompleteMethod(param = "plugin") + public Iterable list() { + return Arrays.stream(Bukkit.getPluginManager().getPlugins()).map(Plugin::getName)::iterator; + } + private boolean enable_disable(CommandSender sender, Plugin plugin, String component, boolean enable, boolean permanent) { try { val oc = getComponentOrError(plugin, component, sender); diff --git a/Chroma-Core/src/main/java/buttondevteam/core/MainPlugin.java b/Chroma-Core/src/main/java/buttondevteam/core/MainPlugin.java index 3f6cc76..bde8bdf 100755 --- a/Chroma-Core/src/main/java/buttondevteam/core/MainPlugin.java +++ b/Chroma-Core/src/main/java/buttondevteam/core/MainPlugin.java @@ -24,10 +24,12 @@ import lombok.Setter; import net.milkbowl.vault.economy.Economy; import net.milkbowl.vault.permission.Permission; import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; import org.bukkit.command.BlockCommandSender; import org.bukkit.command.Command; import org.bukkit.command.CommandSender; import org.bukkit.command.ConsoleCommandSender; +import org.bukkit.entity.HumanEntity; import org.bukkit.entity.Player; import org.bukkit.plugin.PluginDescriptionFile; import org.bukkit.plugin.RegisteredServiceProvider; @@ -36,10 +38,10 @@ import javax.annotation.Nullable; import java.io.File; import java.io.IOException; import java.nio.file.Files; -import java.nio.file.StandardCopyOption; import java.util.Arrays; import java.util.Optional; import java.util.UUID; +import java.util.function.Supplier; import java.util.logging.Logger; public class MainPlugin extends ButtonPlugin { @@ -135,6 +137,9 @@ public class MainPlugin extends ButtonPlugin { TBMCChatAPI.RegisterChatChannel(new ChatRoom("§aGREEN§f", Color.Green, "green")); TBMCChatAPI.RegisterChatChannel(new ChatRoom("§bBLUE§f", Color.Blue, "blue")); TBMCChatAPI.RegisterChatChannel(new ChatRoom("§5PURPLE§f", Color.DarkPurple, "purple")); + Supplier> playerSupplier = () -> Bukkit.getOnlinePlayers().stream().map(HumanEntity::getName)::iterator; + getCommand2MC().addParamConverter(OfflinePlayer.class, Bukkit::getOfflinePlayer, "Player not found!", playerSupplier); + getCommand2MC().addParamConverter(Player.class, Bukkit::getPlayer, "Online player not found!", playerSupplier); if (writePluginList().get()) { try { Files.write(new File("plugins", "plugins.txt").toPath(), Arrays.stream(Bukkit.getPluginManager().getPlugins()).map(p -> (CharSequence) p.getDataFolder().getName())::iterator); diff --git a/Chroma-Core/src/main/java/buttondevteam/core/component/members/MemberCommand.java b/Chroma-Core/src/main/java/buttondevteam/core/component/members/MemberCommand.java index 1cc3459..edd6261 100644 --- a/Chroma-Core/src/main/java/buttondevteam/core/component/members/MemberCommand.java +++ b/Chroma-Core/src/main/java/buttondevteam/core/component/members/MemberCommand.java @@ -7,7 +7,6 @@ import buttondevteam.lib.chat.ICommand2MC; import org.bukkit.Bukkit; import org.bukkit.OfflinePlayer; import org.bukkit.command.CommandSender; -import org.bukkit.entity.HumanEntity; import org.bukkit.entity.Player; import java.util.concurrent.TimeUnit; @@ -20,8 +19,6 @@ public class MemberCommand extends ICommand2MC { private final MemberComponent component; public MemberCommand(MemberComponent component) { - getManager().addParamConverter(OfflinePlayer.class, Bukkit::getOfflinePlayer, "Player not found!", - () -> Bukkit.getOnlinePlayers().stream().map(HumanEntity::getName)::iterator); this.component = component; } diff --git a/Chroma-Core/src/main/java/buttondevteam/lib/chat/Command2MC.java b/Chroma-Core/src/main/java/buttondevteam/lib/chat/Command2MC.java index 9d9d4ba..e119b40 100644 --- a/Chroma-Core/src/main/java/buttondevteam/lib/chat/Command2MC.java +++ b/Chroma-Core/src/main/java/buttondevteam/lib/chat/Command2MC.java @@ -26,15 +26,19 @@ import org.bukkit.event.Listener; import org.bukkit.event.server.TabCompleteEvent; import org.bukkit.permissions.Permission; import org.bukkit.permissions.PermissionDefault; +import org.javatuples.Triplet; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.lang.reflect.Parameter; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.function.Function; import java.util.function.Supplier; +import java.util.stream.Collectors; +import java.util.stream.Stream; public class Command2MC extends Command2 implements Listener { /** @@ -236,8 +240,19 @@ public class Command2MC extends Command2 implemen var scmd = subcmds.stream().filter(sd -> sd.method.getName().equals("def")).findAny().orElse(null); cmd = appendSubcommand(path[i], cmd, scmd); //Add each part of the path as a child of the previous one } + final var customTCmethods = Arrays.stream(command2MC.getClass().getDeclaredMethods()) //val doesn't recognize the type arguments + .flatMap(method -> Stream.of(Optional.ofNullable(method.getAnnotation(CustomTabCompleteMethod.class))) + .filter(Optional::isPresent).map(Optional::get) // Java 9 has .stream() + .flatMap(ctcm -> { + var paths = Optional.of(ctcm.subcommand()).filter(s -> s.length > 0) + .orElseGet(() -> new String[]{ + ButtonPlugin.getCommand2MC().getCommandPath(method.getName(), ' ').trim() + }); + return Arrays.stream(paths).map(name -> new Triplet<>(name, ctcm.param(), method)); + })).collect(Collectors.toList()); for (SubcommandData subcmd : subcmds) { - String[] subpath = ButtonPlugin.getCommand2MC().getCommandPath(subcmd.method.getName(), ' ').trim().split(" "); + String subpathAsOne = ButtonPlugin.getCommand2MC().getCommandPath(subcmd.method.getName(), ' ').trim(); + String[] subpath = subpathAsOne.split(" "); CommandNode scmd = cmd; if (subpath[0].length() > 0) { //If the method is def, it will contain one empty string for (String s : subpath) { @@ -249,47 +264,113 @@ public class Command2MC extends Command2 implemen Parameter parameter = parameters[i]; ArgumentType type; final Class ptype = parameter.getType(); - if (ptype == String.class) - if (parameter.isAnnotationPresent(TextArg.class)) - type = StringArgumentType.greedyString(); - else + final boolean customParamType; + { + boolean customParamTypeTemp = false; + if (ptype == String.class) + if (parameter.isAnnotationPresent(TextArg.class)) + type = StringArgumentType.greedyString(); + else + type = StringArgumentType.word(); + else if (ptype == int.class || ptype == Integer.class + || ptype == byte.class || ptype == Byte.class + || ptype == short.class || ptype == Short.class) + type = IntegerArgumentType.integer(); //TODO: Min, max + else if (ptype == long.class || ptype == Long.class) + type = LongArgumentType.longArg(); + else if (ptype == float.class || ptype == Float.class) + type = FloatArgumentType.floatArg(); + else if (ptype == double.class || ptype == Double.class) + type = DoubleArgumentType.doubleArg(); + else if (ptype == char.class || ptype == Character.class) type = StringArgumentType.word(); - else if (ptype == int.class || ptype == Integer.class - || ptype == byte.class || ptype == Byte.class - || ptype == short.class || ptype == Short.class) - type = IntegerArgumentType.integer(); //TODO: Min, max - else if (ptype == long.class || ptype == Long.class) - type = LongArgumentType.longArg(); - else if (ptype == float.class || ptype == Float.class) - type = FloatArgumentType.floatArg(); - else if (ptype == double.class || ptype == Double.class) - type = DoubleArgumentType.doubleArg(); - else if (ptype == char.class || ptype == Character.class) - type = StringArgumentType.word(); - else if (ptype == boolean.class || ptype == Boolean.class) - type = BoolArgumentType.bool(); - else //TODO: Custom parameter types - type = StringArgumentType.word(); + else if (ptype == boolean.class || ptype == Boolean.class) + type = BoolArgumentType.bool(); + else { + type = StringArgumentType.word(); + customParamTypeTemp = true; + } + customParamType = customParamTypeTemp; + } val param = subcmd.parameters[i - 1]; + val customTC = Optional.ofNullable(parameter.getAnnotation(CustomTabComplete.class)) + .map(CustomTabComplete::value); + final Optional customTCmethod = customTCmethods.stream().filter(t -> subpathAsOne.equalsIgnoreCase(t.getValue0())) + .filter(t -> param.replaceAll("[\\[\\]<>]", "").equalsIgnoreCase(t.getValue1())) + .map(Triplet::getValue2).findAny(); var argb = RequiredArgumentBuilder.argument(param, type) .suggests((SuggestionProvider) (context, builder) -> { - //TODO - return builder.suggest(param).buildFuture(); + if (customTC.isPresent()) + for (val ctc : customTC.get()) + builder.suggest(ctc); + if (customTCmethod.isPresent()) { + final var method = customTCmethod.get(); + val params = method.getParameters(); + val args = new Object[params.length]; + for (int j = 0, k = 0; j < args.length && k < subcmd.parameters.length; j++) { + val paramObj = params[j]; + if (CommandSender.class.isAssignableFrom(paramObj.getType())) { + args[j] = commodore.getBukkitSender(context.getSource()); + continue; + } + val paramValueString = context.getArgument(subcmd.parameters[k], String.class); + if (paramObj.getType() == String.class) { + args[j] = paramValueString; + continue; + } + val converter = ButtonPlugin.getCommand2MC().paramConverters.get(params[j].getType()); + if (converter == null) { + TBMCCoreAPI.SendException("Could not find a suitable converter for type " + params[j].getType().getSimpleName(), + new NullPointerException("converter is null")); + break; + } + val paramValue = converter.converter.apply(paramValueString); + if (paramValue == null) //For example, the player provided an invalid plugin name + break; + args[j] = paramValue; + k++; //Only increment if not CommandSender + } + if (args.length == 0 || args[args.length - 1] != null) { //Arguments filled entirely + try { + val suggestions = method.invoke(command2MC, args); + if (suggestions instanceof Iterable) { + //noinspection unchecked + for (Object suggestion : (Iterable) suggestions) + if (suggestion instanceof String) + builder.suggest((String) suggestion); + else + throw new ClassCastException("Bad return type! It should return an Iterable or a String[]."); + } else if (suggestions instanceof String[]) + for (String suggestion : (String[]) suggestions) + builder.suggest(suggestion); + else + throw new ClassCastException("Bad return type! It should return a String[] or an Iterable."); + } catch (Exception e) { + TBMCCoreAPI.SendException("Failed to run tabcomplete method " + method.getName() + " for command " + command2MC.getClass().getSimpleName(), e); + } + } + } + if (customParamType) { + val converter = ButtonPlugin.getCommand2MC().paramConverters.get(ptype); + if (converter == null) + TBMCCoreAPI.SendException("Could not find a suitable converter for type " + ptype.getSimpleName(), + new NullPointerException("converter is null")); + else { + var suggestions = converter.allSupplier.get(); + for (String suggestion : suggestions) + builder.suggest(suggestion); + } + } + if (ptype == boolean.class || ptype == Boolean.class) + builder.suggest("true").suggest("false"); + return builder.suggest(param).buildFuture().whenComplete((s, e) -> //The list is automatically ordered + Collections.swap(s.getList(), 0, s.getList().size() - 1)); //So we need to put the at the end after that }); var arg = argb.build(); scmd.addChild(arg); scmd = arg; } } - /*try { - Class.forName("net.minecraft.server.v1_15_R1.ArgumentRegistry").getMethod("a", String.class, Class.class, - Class.forName("net.minecraft.server.v1_15_R1.ArgumentSerializer")) - .invoke(null, "chroma:string", BetterStringArgumentType.class, - Class.forName("net.minecraft.server.v1_15_R1.ArgumentSerializerVoid").getConstructors()[0] - .newInstance((Supplier) BetterStringArgumentType::word)); - } catch (Exception e) { - Client log: Could not deserialize chroma:string - e.printStackTrace(); - }*/ commodore.register(maincmd); } } diff --git a/Chroma-Core/src/main/java/buttondevteam/lib/chat/CustomTabCompleteMethod.java b/Chroma-Core/src/main/java/buttondevteam/lib/chat/CustomTabCompleteMethod.java index 00a7a16..74c95aa 100644 --- a/Chroma-Core/src/main/java/buttondevteam/lib/chat/CustomTabCompleteMethod.java +++ b/Chroma-Core/src/main/java/buttondevteam/lib/chat/CustomTabCompleteMethod.java @@ -7,6 +7,7 @@ import java.lang.annotation.Target; /** * The method must return with {@link String}[] or {@link Iterable}<{@link String}> and may have the sender and preceding arguments as parameters. + * The predecing arguments must be in order, from first to whatever is needed. If the nth arg is needed, you need to specify n params. */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @@ -19,5 +20,5 @@ public @interface CustomTabCompleteMethod { /** * The subcommand(s) which have the parameter, by default the method's name */ - String[] subcommand() default ""; + String[] subcommand() default {}; } diff --git a/pom.xml b/pom.xml index e1bbf23..d1530af 100755 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ pom master-SNAPSHOT - 1.18.10 + 1.18.12 Chroma Parent