diff --git a/ButtonCore/src/main/java/buttondevteam/core/ComponentCommand.java b/ButtonCore/src/main/java/buttondevteam/core/ComponentCommand.java index 655192c..4aaceac 100644 --- a/ButtonCore/src/main/java/buttondevteam/core/ComponentCommand.java +++ b/ButtonCore/src/main/java/buttondevteam/core/ComponentCommand.java @@ -2,7 +2,7 @@ package buttondevteam.core; import buttondevteam.lib.TBMCCoreAPI; import buttondevteam.lib.architecture.Component; -import buttondevteam.lib.chat.Command2; +import buttondevteam.lib.chat.Command2MC; import buttondevteam.lib.chat.CommandClass; import lombok.val; import org.bukkit.Bukkit; @@ -15,7 +15,7 @@ import java.util.Optional; "§6---- Component command ----", "Can be used to enable/disable/list components" }) -public class ComponentCommand extends Command2 { +public class ComponentCommand extends Command2MC { public ComponentCommand() { addParamConverter(Plugin.class, arg -> Bukkit.getPluginManager().getPlugin(arg)); diff --git a/ButtonCore/src/main/java/buttondevteam/core/ComponentManager.java b/ButtonCore/src/main/java/buttondevteam/core/ComponentManager.java index 842badb..9a84ddd 100644 --- a/ButtonCore/src/main/java/buttondevteam/core/ComponentManager.java +++ b/ButtonCore/src/main/java/buttondevteam/core/ComponentManager.java @@ -1,6 +1,7 @@ package buttondevteam.core; import buttondevteam.lib.TBMCCoreAPI; +import buttondevteam.lib.architecture.ButtonPlugin; import buttondevteam.lib.architecture.Component; import lombok.val; @@ -31,16 +32,11 @@ public final class ComponentManager { } /** - * Disables all components that are enabled + * Unregister all components of a plugin that are enabled - called on {@link ButtonPlugin} disable */ - public static void disableComponents() { - Component.getComponents().values().stream().filter(Component::isEnabled).forEach(c -> { - try { - Component.setComponentEnabled(c, false); - } catch (Exception e) { - TBMCCoreAPI.SendException("Failed to disable one of the components: " + c.getClass().getSimpleName(), e); - } - }); + public static void unregComponents(ButtonPlugin plugin) { + while (!plugin.getComponentStack().empty()) //Unregister in reverse order + Component.unregisterComponent(plugin, plugin.getComponentStack().pop()); //Components are pushed on register componentsEnabled = false; } @@ -56,7 +52,7 @@ public final class ComponentManager { } /** - * Will also return false if the component is not registered. + * Will also return null if the component is not registered. * * @param cl The component class * @return The component if it's registered and enabled diff --git a/ButtonCore/src/main/java/buttondevteam/core/MainPlugin.java b/ButtonCore/src/main/java/buttondevteam/core/MainPlugin.java index 65966d8..d15d2a9 100755 --- a/ButtonCore/src/main/java/buttondevteam/core/MainPlugin.java +++ b/ButtonCore/src/main/java/buttondevteam/core/MainPlugin.java @@ -14,7 +14,7 @@ import buttondevteam.lib.architecture.ButtonPlugin; import buttondevteam.lib.architecture.Component; import buttondevteam.lib.architecture.ConfigData; import buttondevteam.lib.chat.Color; -import buttondevteam.lib.chat.Command2; +import buttondevteam.lib.chat.Command2MC; import buttondevteam.lib.chat.TBMCChatAPI; import buttondevteam.lib.player.ChromaGamerBase; import buttondevteam.lib.player.TBMCPlayer; @@ -69,7 +69,7 @@ public class MainPlugin extends ButtonPlugin { Component.registerComponent(this, new MemberComponent()); Component.registerComponent(this, new TownyComponent()); ComponentManager.enableComponents(); - Command2.registerCommand(new ComponentCommand()); + Command2MC.registerCommand(new ComponentCommand()); TBMCCoreAPI.RegisterEventsForExceptions(new PlayerListener(), this); ChromaGamerBase.addConverter(commandSender -> Optional.ofNullable(commandSender instanceof ConsoleCommandSender || commandSender instanceof BlockCommandSender ? TBMCPlayer.getPlayer(new UUID(0, 0), TBMCPlayer.class) : null)); //Console & cmdblocks @@ -101,7 +101,6 @@ public class MainPlugin extends ButtonPlugin { @Override public void pluginDisable() { - ComponentManager.disableComponents(); logger.info("Saving player data..."); TBMCPlayerBase.savePlayers(); logger.info("Player data saved."); diff --git a/ButtonCore/src/main/java/buttondevteam/core/PlayerListener.java b/ButtonCore/src/main/java/buttondevteam/core/PlayerListener.java index 91448ba..c5146cd 100755 --- a/ButtonCore/src/main/java/buttondevteam/core/PlayerListener.java +++ b/ButtonCore/src/main/java/buttondevteam/core/PlayerListener.java @@ -3,7 +3,7 @@ package buttondevteam.core; import buttondevteam.lib.TBMCCommandPreprocessEvent; import buttondevteam.lib.TBMCCoreAPI; import buttondevteam.lib.TBMCSystemChatEvent; -import buttondevteam.lib.chat.Command2; +import buttondevteam.lib.chat.Command2MC; import buttondevteam.lib.player.TBMCPlayerBase; import lombok.val; import org.bukkit.Bukkit; @@ -64,7 +64,7 @@ public class PlayerListener implements Listener { public void onTBMCPreprocess(TBMCCommandPreprocessEvent event) { if (event.isCancelled()) return; try { - event.setCancelled(Command2.handleCommand(event.getSender(), event.getMessage())); + event.setCancelled(Command2MC.handleCommand(event.getSender(), event.getMessage())); } catch (Exception e) { TBMCCoreAPI.SendException("Command processing failed for sender '" + event.getSender() + "' and message '" + event.getMessage() + "'", e); } diff --git a/ButtonCore/src/main/java/buttondevteam/core/TestPrepare.java b/ButtonCore/src/main/java/buttondevteam/core/TestPrepare.java index b0b9610..67b0a33 100755 --- a/ButtonCore/src/main/java/buttondevteam/core/TestPrepare.java +++ b/ButtonCore/src/main/java/buttondevteam/core/TestPrepare.java @@ -8,6 +8,7 @@ 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; @@ -40,7 +41,7 @@ public class TestPrepare { return cl.isAssignableFrom(invocation.getMethod().getReturnType()); } })); - Component.registerComponent(Mockito.mock(MainPlugin.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/ButtonCore/src/main/java/buttondevteam/lib/architecture/ButtonPlugin.java b/ButtonCore/src/main/java/buttondevteam/lib/architecture/ButtonPlugin.java index 2eee783..bc0e46d 100644 --- a/ButtonCore/src/main/java/buttondevteam/lib/architecture/ButtonPlugin.java +++ b/ButtonCore/src/main/java/buttondevteam/lib/architecture/ButtonPlugin.java @@ -1,5 +1,6 @@ package buttondevteam.lib.architecture; +import buttondevteam.core.ComponentManager; import buttondevteam.lib.TBMCCoreAPI; import buttondevteam.lib.chat.TBMCChatAPI; import lombok.AccessLevel; @@ -7,9 +8,16 @@ import lombok.Getter; import lombok.experimental.var; import org.bukkit.plugin.java.JavaPlugin; +import java.util.Stack; + public abstract class ButtonPlugin extends JavaPlugin { @Getter(AccessLevel.PROTECTED) private IHaveConfig iConfig; + /** + * Used to unregister components in the right order + */ + @Getter + private Stack componentStack = new Stack<>(); protected abstract void pluginEnable(); @@ -30,6 +38,7 @@ public abstract class ButtonPlugin extends JavaPlugin { @Override public final void onDisable() { try { + ComponentManager.unregComponents(this); pluginDisable(); saveConfig(); iConfig = null; //Clearing the hashmap is not enough, we need to update the section as well diff --git a/ButtonCore/src/main/java/buttondevteam/lib/architecture/Component.java b/ButtonCore/src/main/java/buttondevteam/lib/architecture/Component.java index 634f23a..e052555 100644 --- a/ButtonCore/src/main/java/buttondevteam/lib/architecture/Component.java +++ b/ButtonCore/src/main/java/buttondevteam/lib/architecture/Component.java @@ -3,7 +3,7 @@ package buttondevteam.lib.architecture; import buttondevteam.core.ComponentManager; import buttondevteam.lib.TBMCCoreAPI; import buttondevteam.lib.architecture.exceptions.UnregisteredComponentException; -import buttondevteam.lib.chat.Command2; +import buttondevteam.lib.chat.Command2MC; import buttondevteam.lib.chat.TBMCChatAPI; import buttondevteam.lib.chat.TBMCCommandBase; import lombok.Getter; @@ -39,7 +39,8 @@ 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. + * 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) @@ -50,15 +51,13 @@ public abstract class Component { /** * Unregisters a component by calling {@link #unregister(JavaPlugin)}.
- * Make sure to unregister the dependencies last. + * Make sure to unregister the dependencies last.
+ * Components will be unregistered in opposite order of registering by default by {@link ButtonPlugin} or {@link ComponentManager#unregComponents(ButtonPlugin)}. * - * @param componentClass The component class to unregister + * @param component The component to unregister * @return Whether the component is unregistered successfully (it also got disabled) */ - public static boolean unregisterComponent(JavaPlugin plugin, Class componentClass) { - val component = components.get(componentClass); - if (component == null) - return false; //Failed to load + public static boolean unregisterComponent(JavaPlugin plugin, Component component) { return registerUnregisterComponent(plugin, component, false); } @@ -75,10 +74,16 @@ public abstract class Component { } } if (register) { + if (components.containsKey(component.getClass())) { + TBMCCoreAPI.SendException("Failed to register component " + component.getClassName(), new IllegalArgumentException("The component is already registered!")); + return false; + } component.plugin = plugin; updateConfig(plugin, component); component.register(plugin); components.put(component.getClass(), 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); @@ -90,6 +95,8 @@ public abstract class Component { } return true; //Component shouldn't be enabled } else { + if (!components.containsKey(component.getClass())) + return true; //Already unregistered if (component.enabled) { try { setComponentEnabled(component, false); @@ -190,8 +197,8 @@ public abstract class Component { * * @param commandBase Custom coded command class */ - protected final void registerCommand(Command2 commandBase) { - Command2.registerCommand(commandBase); + protected final void registerCommand(Command2MC commandBase) { + Command2MC.registerCommand(commandBase); } /** diff --git a/ButtonCore/src/main/java/buttondevteam/lib/chat/Command2.java b/ButtonCore/src/main/java/buttondevteam/lib/chat/Command2.java index 87a1a18..cb77556 100644 --- a/ButtonCore/src/main/java/buttondevteam/lib/chat/Command2.java +++ b/ButtonCore/src/main/java/buttondevteam/lib/chat/Command2.java @@ -70,15 +70,12 @@ public abstract class Command2 { } @RequiredArgsConstructor - private static class SubcommandData { + protected static class SubcommandData { public final Method method; - public final Command2 command; + public final T command; public final String[] helpText; } - private static HashMap subcommands = new HashMap<>(); - private static HashMap, Function> paramConverters = new HashMap<>(); - public Command2() { path = getcmdpath(); } @@ -91,18 +88,16 @@ public abstract class Command2 { * @param converter The converter to use * @param The type of the result */ - public static void addParamConverter(Class cl, Function converter) { - paramConverters.put(cl, converter); + protected static void addParamConverter(Class cl, Function converter, HashMap, Function> map) { + map.put(cl, converter); } - public static boolean handleCommand(CommandSender sender, String commandline) throws Exception { + protected static boolean handleCommand(CommandSender sender, String commandline, + HashMap> subcommands, HashMap, Function> paramConverters) throws Exception { for (int i = commandline.length(); i != -1; i = commandline.lastIndexOf(' ', i - 1)) { String subcommand = commandline.substring(0, i).toLowerCase(); - //System.out.println("Subcommand: "+subcommand); - //System.out.println("Subcmds: "+subcommands.toString()); SubcommandData sd = subcommands.get(subcommand); //O(1) if (sd == null) continue; //TODO: This will run each time someone runs any command - //System.out.println("sd.method: "+sd.method); //TODO: Rename in Maven val params = new ArrayList(sd.method.getParameterCount()); int j = subcommand.length(), pj; Class[] parameterTypes = sd.method.getParameterTypes(); @@ -152,27 +147,28 @@ public abstract class Command2 { return false; //Didn't handle } //TODO: Add to the help - public static void registerCommand(Command2 command) { + protected static void registerCommand(T command, HashMap> subcommands, char commandChar) { + val path = command.getCommandPath(); try { //Register the default handler first so it can be reliably overwritten val method = command.getClass().getMethod("def", CommandSender.class, String.class); val cc = command.getClass().getAnnotation(CommandClass.class); var ht = cc == null ? new String[0] : cc.helpText(); String[] both = Arrays.copyOf(ht, ht.length + 1); - both[ht.length] = "Usage: /" + command.path; //TODO: Print subcommands + both[ht.length] = "Usage: " + commandChar + path; //TODO: Print subcommands ht = both; - subcommands.put("/" + command.path, new SubcommandData(method, command, ht)); //TODO: Disable components when the plugin is disabled + subcommands.put(commandChar + path, new SubcommandData<>(method, command, ht)); //TODO: Disable components when the plugin is disabled } catch (Exception e) { - TBMCCoreAPI.SendException("Could not register default handler for command /" + command.path, e); + TBMCCoreAPI.SendException("Could not register default handler for command /" + path, e); } //Continue on for (val method : command.getClass().getMethods()) { val ann = method.getAnnotation(Subcommand.class); if (ann != null) { val cc = command.getClass().getAnnotation(CommandClass.class); var ht = ann.helpText().length != 0 || cc == null ? ann.helpText() : cc.helpText(); //If cc is null then it's empty array - val subcommand = "/" + command.path + //Add command path (class name by default) + val subcommand = commandChar + path + //Add command path (class name by default) (method.getName().equals("def") ? "" : " " + method.getName().replace('_', ' ').toLowerCase()); //Add method name, unless it's 'def' ht = getHelpText(method, ht, subcommand); - subcommands.put(subcommand, new SubcommandData(method, command, ht)); //Result of the above (def) is that it will show the help text + subcommands.put(subcommand, new SubcommandData<>(method, command, ht)); //Result of the above (def) is that it will show the help text } } } @@ -211,11 +207,10 @@ public abstract class Command2 { * For example:
* "u admin updateplugin" or "u" for the top level one
* The path must be lowercase!
- * Abstract classes with no {@link CommandClass} annotations will be ignored. * * @return The command path, which is the command class name by default (removing any "command" from it) - Change via the {@link CommandClass} annotation */ - public final String GetCommandPath() { + public final String getCommandPath() { return path; } diff --git a/ButtonCore/src/main/java/buttondevteam/lib/chat/Command2MC.java b/ButtonCore/src/main/java/buttondevteam/lib/chat/Command2MC.java new file mode 100644 index 0000000..715d078 --- /dev/null +++ b/ButtonCore/src/main/java/buttondevteam/lib/chat/Command2MC.java @@ -0,0 +1,24 @@ +package buttondevteam.lib.chat; + +import org.bukkit.command.CommandSender; + +import java.util.HashMap; +import java.util.function.Function; + +public class Command2MC extends Command2 { + + private static HashMap> subcommands = new HashMap<>(); + private static HashMap, Function> paramConverters = new HashMap<>(); + + public static boolean handleCommand(CommandSender sender, String commandLine) throws Exception { + return handleCommand(sender, commandLine, subcommands, paramConverters); + } + + public static void registerCommand(Command2MC command) { + registerCommand(command, subcommands, '/'); + } + + public static void addParamConverter(Class cl, Function converter) { + addParamConverter(cl, converter, paramConverters); + } +}