From 23f3c0f13347e6039c9192655d657e6b8615ab3c Mon Sep 17 00:00:00 2001 From: NorbiPeti Date: Fri, 14 Feb 2020 19:20:20 +0100 Subject: [PATCH] Add plugin & component to commands, unregister on disable #88 --- .../buttondevteam/core/ComponentCommand.java | 8 ++-- .../java/buttondevteam/core/MainPlugin.java | 6 +-- .../java/buttondevteam/core/TestPrepare.java | 4 +- .../lib/architecture/ButtonPlugin.java | 17 ++++++-- .../lib/architecture/Component.java | 29 ++++++++------ .../java/buttondevteam/lib/chat/Command2.java | 18 ++++++++- .../buttondevteam/lib/chat/Command2MC.java | 34 +++++++++++++--- .../buttondevteam/lib/chat/ICommand2.java | 7 +--- .../buttondevteam/lib/chat/ICommand2MC.java | 39 ++++++++++++++++--- 9 files changed, 119 insertions(+), 43 deletions(-) diff --git a/Chroma-Core/src/main/java/buttondevteam/core/ComponentCommand.java b/Chroma-Core/src/main/java/buttondevteam/core/ComponentCommand.java index f7b437e..55fbf6f 100644 --- a/Chroma-Core/src/main/java/buttondevteam/core/ComponentCommand.java +++ b/Chroma-Core/src/main/java/buttondevteam/core/ComponentCommand.java @@ -5,7 +5,6 @@ import buttondevteam.lib.architecture.ButtonPlugin; import buttondevteam.lib.architecture.Component; import buttondevteam.lib.chat.Command2; import buttondevteam.lib.chat.Command2.Subcommand; -import buttondevteam.lib.chat.Command2MC; import buttondevteam.lib.chat.CommandClass; import buttondevteam.lib.chat.ICommand2MC; import lombok.val; @@ -19,9 +18,8 @@ import java.util.Optional; "Component command", "Can be used to enable/disable/list components" }) -public class ComponentCommand extends ICommand2MC { - @Override - public void onRegister(Command2MC manager) { +public class ComponentCommand extends ICommand2MC { + public ComponentCommand() { getManager().addParamConverter(Plugin.class, arg -> Bukkit.getPluginManager().getPlugin(arg), "Plugin not found!"); } @@ -31,7 +29,7 @@ public class ComponentCommand extends ICommand2MC { }) public boolean enable(CommandSender sender, Plugin plugin, String component) { if (plugin instanceof ButtonPlugin) { - if (!((ButtonPlugin) plugin).justReload()) { + if (!((ButtonPlugin) plugin).justReload()) { sender.sendMessage("§cCouldn't reload config, check console."); return true; } diff --git a/Chroma-Core/src/main/java/buttondevteam/core/MainPlugin.java b/Chroma-Core/src/main/java/buttondevteam/core/MainPlugin.java index 6010576..b450bf4 100755 --- a/Chroma-Core/src/main/java/buttondevteam/core/MainPlugin.java +++ b/Chroma-Core/src/main/java/buttondevteam/core/MainPlugin.java @@ -44,7 +44,7 @@ import java.util.Optional; import java.util.UUID; import java.util.logging.Logger; -public class MainPlugin extends ButtonPlugin { +public class MainPlugin extends ButtonPlugin { public static MainPlugin Instance; public static Permission permission; @Nullable @@ -117,8 +117,8 @@ public class MainPlugin extends ButtonPlugin { if (Bukkit.getPluginManager().isPluginEnabled("Votifier") && economy != null) Component.registerComponent(this, new VotifierComponent(economy)); ComponentManager.enableComponents(); - getCommand2MC().registerCommand(new ComponentCommand()); - getCommand2MC().registerCommand(new ChromaCommand()); + registerCommand(new ComponentCommand()); + registerCommand(new ChromaCommand()); TBMCCoreAPI.RegisterEventsForExceptions(new PlayerListener(), this); TBMCCoreAPI.RegisterEventsForExceptions(getCommand2MC(), this); ChromaGamerBase.addConverter(commandSender -> Optional.ofNullable(commandSender instanceof ConsoleCommandSender || commandSender instanceof BlockCommandSender diff --git a/Chroma-Core/src/main/java/buttondevteam/core/TestPrepare.java b/Chroma-Core/src/main/java/buttondevteam/core/TestPrepare.java index 34521e7..13513b4 100755 --- a/Chroma-Core/src/main/java/buttondevteam/core/TestPrepare.java +++ b/Chroma-Core/src/main/java/buttondevteam/core/TestPrepare.java @@ -2,13 +2,13 @@ package buttondevteam.core; import buttondevteam.core.component.channel.Channel; import buttondevteam.core.component.channel.ChannelComponent; -import buttondevteam.lib.architecture.ButtonPlugin; import buttondevteam.lib.architecture.Component; import buttondevteam.lib.chat.Color; import buttondevteam.lib.chat.TBMCChatAPI; import org.bukkit.Bukkit; import org.bukkit.Server; import org.bukkit.plugin.PluginManager; +import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.scheduler.BukkitScheduler; import org.mockito.Mockito; import org.mockito.invocation.InvocationOnMock; @@ -42,7 +42,7 @@ public class TestPrepare { } })); //noinspection unchecked - Component.registerComponent(Mockito.mock(ButtonPlugin.class), new ChannelComponent()); + Component.registerComponent(Mockito.mock(JavaPlugin.class), new ChannelComponent()); TBMCChatAPI.RegisterChatChannel(Channel.GlobalChat = new Channel("§fg§f", Color.White, "g", null)); } } diff --git a/Chroma-Core/src/main/java/buttondevteam/lib/architecture/ButtonPlugin.java b/Chroma-Core/src/main/java/buttondevteam/lib/architecture/ButtonPlugin.java index e202f45..cf7db91 100644 --- a/Chroma-Core/src/main/java/buttondevteam/lib/architecture/ButtonPlugin.java +++ b/Chroma-Core/src/main/java/buttondevteam/lib/architecture/ButtonPlugin.java @@ -4,6 +4,7 @@ import buttondevteam.buttonproc.HasConfig; import buttondevteam.core.ComponentManager; import buttondevteam.lib.TBMCCoreAPI; import buttondevteam.lib.chat.Command2MC; +import buttondevteam.lib.chat.ICommand2MC; import lombok.AccessLevel; import lombok.Getter; import org.bukkit.configuration.InvalidConfigurationException; @@ -22,9 +23,9 @@ import java.util.Optional; import java.util.Stack; @HasConfig(global = true) -public abstract class ButtonPlugin> extends JavaPlugin { +public abstract class ButtonPlugin extends JavaPlugin { @Getter //Needs to be static as we don't know the plugin when a command is handled - private Command2MC command2MC = new Command2MC<>(); + private static Command2MC command2MC = new Command2MC(); @Getter(AccessLevel.PROTECTED) private IHaveConfig iConfig; private CommentedConfiguration yaml; @@ -84,7 +85,7 @@ public abstract class ButtonPlugin> extends JavaPlug if (ConfigData.saveNow(getConfig())) getLogger().info("Saved configuration changes."); iConfig = null; //Clearing the hashmap is not enough, we need to update the section as well - //TBMCChatAPI.RemoveCommands(this); - TODO + getCommand2MC().unregisterCommands(this); } catch (Exception e) { TBMCCoreAPI.SendException("Error while disabling plugin " + getName() + "!", e); } @@ -147,6 +148,16 @@ public abstract class ButtonPlugin> extends JavaPlug } } + /** + * Registers command and sets its plugin. + * + * @param command The command to register + */ + protected void registerCommand(ICommand2MC command) { + command.registerToPlugin(this); + getCommand2MC().registerCommand(command); + } + @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface ConfigOpts { diff --git a/Chroma-Core/src/main/java/buttondevteam/lib/architecture/Component.java b/Chroma-Core/src/main/java/buttondevteam/lib/architecture/Component.java index 66f01ec..ba9925f 100644 --- a/Chroma-Core/src/main/java/buttondevteam/lib/architecture/Component.java +++ b/Chroma-Core/src/main/java/buttondevteam/lib/architecture/Component.java @@ -23,8 +23,8 @@ import java.util.stream.Collectors; * Configuration is based on class name */ @HasConfig(global = false) //Used for obtaining javadoc -public abstract class Component { - private static HashMap, Component> components = new HashMap<>(); +public abstract class Component { + @SuppressWarnings("rawtypes") private static HashMap, Component> components = new HashMap<>(); @Getter private boolean enabled = false; @@ -44,11 +44,12 @@ public abstract class Component { * Registers a component checking it's dependencies and calling {@link #register(JavaPlugin)}.
* Make sure to register the dependencies first.
* The component will be enabled automatically, regardless of when it was registered.
+ * If not using {@link ButtonPlugin}, call {@link ComponentManager#unregComponents(ButtonPlugin)} on plugin disable. * * @param component The component to register * @return Whether the component is registered successfully (it may have failed to enable) */ - public static boolean registerComponent(T plugin, Component component) { + public static boolean registerComponent(T plugin, Component component) { return registerUnregisterComponent(plugin, component, true); } @@ -64,11 +65,11 @@ public abstract class Component { return registerUnregisterComponent(plugin, component, false); } - public static boolean registerUnregisterComponent(T plugin, Component component, boolean register) { + public static boolean registerUnregisterComponent(T plugin, Component component, boolean register) { try { val metaAnn = component.getClass().getAnnotation(ComponentMetadata.class); if (metaAnn != null) { - Class[] dependencies = metaAnn.depends(); + @SuppressWarnings("rawtypes") Class[] dependencies = metaAnn.depends(); for (val dep : dependencies) { //TODO: Support dependencies at enable/disable as well if (!components.containsKey(dep)) { plugin.getLogger().warning("Failed to " + (register ? "" : "un") + "register component " + component.getClassName() + " as a required dependency is missing/disabled: " + dep.getSimpleName()); @@ -85,7 +86,8 @@ public abstract class Component { updateConfig(plugin, component); component.register(plugin); components.put(component.getClass(), component); - plugin.getComponentStack().push(component); + if (plugin instanceof ButtonPlugin) + ((ButtonPlugin) plugin).getComponentStack().push(component); if (ComponentManager.areComponentsEnabled() && component.shouldBeEnabled().get()) { try { //Enable components registered after the previous ones getting enabled setComponentEnabled(component, true); @@ -138,8 +140,7 @@ public abstract class Component { //System.out.println("Done enabling "+component.getClassName()); } else { component.disable(); - //TBMCChatAPI.RemoveCommands(component); - TODO - component.getPlugin().getCommand2MC().unregisterCommand(); + ButtonPlugin.getCommand2MC().unregisterCommands(component); } } @@ -160,6 +161,7 @@ public abstract class Component { * * @return The currently registered components */ + @SuppressWarnings("rawtypes") public static Map, Component> getComponents() { return Collections.unmodifiableMap(components); } @@ -200,13 +202,16 @@ public abstract class Component { protected abstract void disable(); /** - * Registers a TBMCCommand to the component. Make sure to use {@link buttondevteam.lib.chat.CommandClass} and {@link buttondevteam.lib.chat.Command2.Subcommand}. + * Registers a command to the component. Make sure to use {@link buttondevteam.lib.chat.CommandClass} and {@link buttondevteam.lib.chat.Command2.Subcommand}. * You don't need to register the command in plugin.yml. * - * @param commandBase Custom coded command class + * @param command Custom coded command class */ - protected final > void registerCommand(ICommand2MC commandBase) { - getPlugin().getCommand2MC().registerCommand(commandBase); + protected final void registerCommand(ICommand2MC command) { + if (plugin instanceof ButtonPlugin) + command.registerToPlugin((ButtonPlugin) plugin); + command.registerToComponent(this); + ButtonPlugin.getCommand2MC().registerCommand(command); } /** diff --git a/Chroma-Core/src/main/java/buttondevteam/lib/chat/Command2.java b/Chroma-Core/src/main/java/buttondevteam/lib/chat/Command2.java index 36c3324..a9aead8 100644 --- a/Chroma-Core/src/main/java/buttondevteam/lib/chat/Command2.java +++ b/Chroma-Core/src/main/java/buttondevteam/lib/chat/Command2.java @@ -114,6 +114,8 @@ public abstract class Command2 private ArrayList commandHelp = new ArrayList<>(); //Mainly needed by Discord + private char commandChar; + /** * Adds a param converter that obtains a specific object from a string parameter. * The converter may return null. @@ -260,6 +262,7 @@ public abstract class Command2 public abstract void registerCommand(TC command); protected void registerCommand(TC command, @SuppressWarnings("SameParameterValue") char commandChar) { + this.commandChar = commandChar; val path = command.getCommandPath(); int x = path.indexOf(' '); val mainPath = commandChar + path.substring(0, x == -1 ? path.length() : x); @@ -357,8 +360,19 @@ public abstract class Command2 return Collections.unmodifiableSet(subcommands.keySet()); }*/ - public void unregisterCommand() { - + /** + * Unregisters all of the subcommands in the given command. + * + * @param command The command object + */ + public void unregisterCommand(ICommand2 command) { + var path = command.getCommandPath(); + for (val method : command.getClass().getMethods()) { + val ann = method.getAnnotation(Subcommand.class); + if (ann == null) continue; + val subcommand = commandChar + path + getCommandPath(method.getName(), ' '); + subcommands.remove(subcommand); + } } /** 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 ba4331e..ca7b917 100644 --- a/Chroma-Core/src/main/java/buttondevteam/lib/chat/Command2MC.java +++ b/Chroma-Core/src/main/java/buttondevteam/lib/chat/Command2MC.java @@ -2,6 +2,7 @@ package buttondevteam.lib.chat; import buttondevteam.core.MainPlugin; import buttondevteam.lib.architecture.ButtonPlugin; +import buttondevteam.lib.architecture.Component; import lombok.val; import org.bukkit.Bukkit; import org.bukkit.OfflinePlayer; @@ -18,11 +19,17 @@ import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.util.Arrays; import java.util.HashMap; +import java.util.Optional; import java.util.function.Function; -public class Command2MC> extends Command2, Command2MCSender> implements Listener { +public class Command2MC extends Command2 implements Listener { + /** + * Don't use directly, use the method in Component and ButtonPlugin to automatically unregister the command when needed. + * + * @param command The command to register + */ @Override - public void registerCommand(ICommand2MC command) { + public void registerCommand(ICommand2MC command) { super.registerCommand(command, '/'); var perm = "chroma.command." + command.getCommandPath().replace(' ', '.'); if (Bukkit.getPluginManager().getPermission(perm) == null) //Check needed for plugin reset @@ -47,11 +54,11 @@ public class Command2MC> extends Command2 command, Method method) { + public boolean hasPermission(Command2MCSender sender, ICommand2MC command, Method method) { return hasPermission(sender.getSender(), command, method); } - public boolean hasPermission(CommandSender sender, ICommand2MC command, Method method) { + public boolean hasPermission(CommandSender sender, ICommand2MC command, Method method) { if (sender instanceof ConsoleCommandSender) return true; //Always allow the console String pg; boolean p = true; @@ -81,7 +88,7 @@ public class Command2MC> extends Command2 command, Method method) { + private String permGroup(ICommand2MC command, Method method) { val sc = method.getAnnotation(Subcommand.class); if (sc != null && sc.permGroup().length() > 0) { return sc.permGroup(); @@ -120,6 +127,21 @@ public class Command2MC> extends Command2 sd.command).filter(cmd -> plugin.equals(cmd.getPlugin())).toArray(ICommand2MC[]::new); + for (var cmd : cmds) + unregisterCommand(cmd);*/ + subcommands.values().removeIf(sd -> plugin.equals(sd.command.getPlugin())); + } + + public void unregisterCommands(Component component) { + /*var cmds = subcommands.values().stream().map(sd -> sd.command).filter(cmd -> component.equals(cmd.getComponent())).toArray(ICommand2MC[]::new); + for (var cmd : cmds) + unregisterCommand(cmd);*/ + subcommands.values().removeIf(sd -> Optional.ofNullable(sd.command.getComponent()) + .map(comp -> component.getClass().getSimpleName().equals(comp.getClass().getSimpleName())).orElse(false)); + } + @EventHandler private void handleTabComplete(TabCompleteEvent event) { String commandline = event.getBuffer(); @@ -129,7 +151,7 @@ public class Command2MC> extends Command2> sd = subcommands.get(subcommand); //O(1) + SubcommandData sd = subcommands.get(subcommand); //O(1) if (sd == null) continue; //System.out.println("ht: " + Arrays.toString(sd.helpText)); Arrays.stream(sd.helpText).skip(1).map(ht -> new HashMap.SimpleEntry<>(ht, subcommands.get(ht))).filter(e -> e.getValue() != null) diff --git a/Chroma-Core/src/main/java/buttondevteam/lib/chat/ICommand2.java b/Chroma-Core/src/main/java/buttondevteam/lib/chat/ICommand2.java index a6cd34c..fc49c75 100644 --- a/Chroma-Core/src/main/java/buttondevteam/lib/chat/ICommand2.java +++ b/Chroma-Core/src/main/java/buttondevteam/lib/chat/ICommand2.java @@ -45,13 +45,10 @@ public abstract class ICommand2 { private final String path; @Getter - private Command2 manager; //TIL that if I use a raw type on a variable then none of the type args will work (including what's defined on a method, not on the type) + private final Command2 manager; //TIL that if I use a raw type on a variable then none of the type args will work (including what's defined on a method, not on the type) - public > ICommand2() { + public > ICommand2(Command2 manager) { path = getcmdpath(); - } - - public > void onRegister(Command2 manager) { this.manager = manager; } diff --git a/Chroma-Core/src/main/java/buttondevteam/lib/chat/ICommand2MC.java b/Chroma-Core/src/main/java/buttondevteam/lib/chat/ICommand2MC.java index 75121a1..af12d61 100644 --- a/Chroma-Core/src/main/java/buttondevteam/lib/chat/ICommand2MC.java +++ b/Chroma-Core/src/main/java/buttondevteam/lib/chat/ICommand2MC.java @@ -1,17 +1,46 @@ package buttondevteam.lib.chat; import buttondevteam.lib.architecture.ButtonPlugin; +import buttondevteam.lib.architecture.Component; +import lombok.Getter; + +import javax.annotation.Nullable; + +@SuppressWarnings("JavadocReference") +public abstract class ICommand2MC extends ICommand2 { + @Getter + private ButtonPlugin plugin; + @Getter + @Nullable + private Component component; -public abstract class ICommand2MC> extends ICommand2 { public ICommand2MC() { + super(ButtonPlugin.getCommand2MC()); } - public void onRegister(Command2MC manager) { + /** + * Called from {@link buttondevteam.lib.architecture.Component#registerCommand(ICommand2MC)} and {@link ButtonPlugin#registerCommand(ICommand2MC)} + */ + public void registerToPlugin(ButtonPlugin plugin) { + if (this.plugin == null) + this.plugin = plugin; + else + throw new IllegalStateException("The command is already assigned to a plugin!"); } - @Override + /** + * Called from {@link buttondevteam.lib.architecture.Component#registerCommand(ICommand2MC)} + */ + public void registerToComponent(Component component) { + if (this.component == null) + this.component = component; + else + throw new IllegalStateException("The command is already assigned to a component!"); + } + + /*@Override public > void onRegister(Command2 manager) { super.onRegister(manager); - onRegister((Command2MC) manager); - } + onRegister((Command2MC) manager); //If ICommand2 is inherited with the same type arg, this would fail but I don't want to add another type param to ICommand2 + } //For example: class IOffender extends ICommand2*/ }