diff --git a/src/com/redstoner/coremods/moduleLoader/ModuleLoader.java b/src/com/redstoner/coremods/moduleLoader/ModuleLoader.java index ab0e3ec..cf1dd98 100644 --- a/src/com/redstoner/coremods/moduleLoader/ModuleLoader.java +++ b/src/com/redstoner/coremods/moduleLoader/ModuleLoader.java @@ -1,9 +1,14 @@ package com.redstoner.coremods.moduleLoader; import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; import java.util.HashMap; import org.bukkit.Bukkit; +import org.bukkit.World; import org.bukkit.command.CommandSender; import org.bukkit.event.HandlerList; import org.bukkit.event.Listener; @@ -22,6 +27,8 @@ import com.redstoner.misc.VersionHelper; import com.redstoner.modules.CoreModule; import com.redstoner.modules.Module; +import net.minecraft.server.v1_11_R1.MinecraftServer; + /** The module loader, mother of all modules. Responsible for loading and taking care of all modules. * * @author Pepich */ @@ -30,9 +37,21 @@ public final class ModuleLoader implements CoreModule { private static ModuleLoader instance; private static final HashMap modules = new HashMap(); + private static URL[] urls; + private static URLClassLoader mainLoader; private ModuleLoader() - {} + { + try + { + urls = new URL[] {(new File(Main.plugin.getDataFolder(), "classes")).toURI().toURL()}; + mainLoader = new URLClassLoader(urls, this.getClass().getClassLoader()); + } + catch (MalformedURLException e) + { + System.out.println("Sumtin is wong with ya filesüstem m8. Fix eeeet or I won't werk!"); + } + } public static void init() { @@ -42,10 +61,12 @@ public final class ModuleLoader implements CoreModule Main.plugin); } - /** This method will add a module to the module list, without enabling it + /** This method will add a module to the module list, without enabling it.
+ * This method is deprecated, use addDynamicModule(String name) instead. When using this method, dynamic reloading of the module will not be supported. * * @param clazz The class of the module to be added. */ @Debugable + @Deprecated public static final void addModule(Class clazz) { Debugger.notifyMethod(clazz); @@ -60,6 +81,20 @@ public final class ModuleLoader implements CoreModule } } + @Debugable + private static final void addLoadedModule(Module m) + { + Debugger.notifyMethod(m); + if (modules.containsKey(m)) + if (modules.get(m)) + { + Utils.error( + "Module m was already loaded and enabled. Disable the module before attempting to reload it."); + return; + } + modules.put(m, false); + } + /** Call this to enable all not-yet enabled modules that are known to the loader. */ @Debugable public static final void enableModules() @@ -69,23 +104,7 @@ public final class ModuleLoader implements CoreModule { if (modules.get(module)) continue; - try - { - if (module.onEnable()) - { - if (VersionHelper.isCompatible(VersionHelper.create(2, 0, 0, -1), module.getClass())) - CommandManager.registerCommand(module.getCommandString(), module, Main.plugin); - modules.put(module, true); - Utils.info("Loaded module " + module.getClass().getName()); - } - else - Utils.error("Failed to load module " + module.getClass().getName()); - } - catch (Exception e) - { - Utils.error("Failed to load module " + module.getClass().getName()); - e.printStackTrace(); - } + enableLoadedModule(module); } Utils.info("Modules enabled, running post initialization."); for (Module module : modules.keySet()) @@ -111,11 +130,13 @@ public final class ModuleLoader implements CoreModule } } - /** This method enables a specific module. If no module with that name is known to the loader yet it will be added to the list. + /** This method enables a specific module. If no module with that name is known to the loader yet it will be added to the list.
+ * This method is deprecated, use enableDynamicModule instead. When using this method, dynamic reloading of the module will not be supported. * * @param clazz The class of the module to be enabled. * @return true, when the module was successfully enabled. */ @Debugable + @Deprecated public static final boolean enableModule(Class clazz) { Debugger.notifyMethod(clazz); @@ -123,6 +144,11 @@ public final class ModuleLoader implements CoreModule { if (module.getClass().equals(clazz)) { + if (modules.get(module)) + { + Utils.info("Module was already enabled! Ignoring module.!"); + return true; + } if (module.onEnable()) { if (module.getClass().isAnnotationPresent(AutoRegisterListener.class) @@ -130,13 +156,13 @@ public final class ModuleLoader implements CoreModule { Bukkit.getPluginManager().registerEvents((Listener) module, Main.plugin); } - Utils.info("Loaded module " + module.getClass().getName()); + Utils.info("Enabled module " + module.getClass().getName()); modules.put(module, true); return true; } else { - Utils.error("Failed to load module " + module.getClass().getName()); + Utils.error("Failed to enable module " + module.getClass().getName()); return false; } } @@ -151,12 +177,12 @@ public final class ModuleLoader implements CoreModule { Bukkit.getPluginManager().registerEvents((Listener) m, Main.plugin); } - Utils.info("Loaded module " + m.getClass().getName()); + Utils.info("Loaded and enabled module " + m.getClass().getName()); return true; } else { - Utils.error("Failed to load module " + m.getClass().getName()); + Utils.error("Failed to enable module " + m.getClass().getName()); return false; } } @@ -167,6 +193,32 @@ public final class ModuleLoader implements CoreModule } } + @SuppressWarnings("deprecation") + private static final void enableLoadedModule(Module module) + { + try + { + if (module.onEnable()) + { + if (VersionHelper.isCompatible(VersionHelper.create(2, 0, 0, -1), module.getClass())) + CommandManager.registerCommand(module.getCommandString(), module, Main.plugin); + modules.put(module, true); + if (VersionHelper.isCompatible(VersionHelper.create(3, 0, 0, 3), module.getClass())) + { + module.postEnable(); + } + Utils.info("Loaded module " + module.getClass().getName()); + } + else + Utils.error("Failed to load module " + module.getClass().getName()); + } + catch (Exception e) + { + Utils.error("Failed to load module " + module.getClass().getName()); + e.printStackTrace(); + } + } + /** This method lists all modules to the specified CommandSender. The modules will be color coded correspondingly to their enabled status. * * @param sender The person to send the info to, usually the issuer of the command or the console sender. @@ -194,19 +246,141 @@ public final class ModuleLoader implements CoreModule { for (Module module : modules.keySet()) { - if (modules.get(module)) + disableModule(module); + } + } + + public static void disableModule(Module module) + { + if (modules.get(module)) + { + module.onDisable(); + if (module.getClass().isAnnotationPresent(AutoRegisterListener.class) && (module instanceof Listener)) { - module.onDisable(); - if (module.getClass().isAnnotationPresent(AutoRegisterListener.class) && (module instanceof Listener)) - { - HandlerList.unregisterAll((Listener) module); - } + HandlerList.unregisterAll((Listener) module); } } } - public static boolean loadModule(File f) + @Command(hook = "load") + public boolean loadModule(CommandSender sender, String name) { - return false; + addDynamicModule(name); + return true; + } + + public static final void addDynamicModule(String name) + { + Object[] status = getServerStatus(); + for (Module m : modules.keySet()) + { + if (m.getClass().getName().equals(name)) + { + Utils.info( + "Found existing module, attempting override. WARNING! This operation will halt the main thread until it is completed."); + Utils.info("Current server status:"); + Utils.info("Current system time: " + status[0]); + Utils.info("Current tick: " + status[1]); + Utils.info("Last TPS: " + status[2]); + Utils.info("Entity count: " + status[3]); + Utils.info("Player count: " + status[4]); + Utils.info("Attempting to load new class definition before disabling and removing the old module:"); + boolean differs = false; + Utils.info("Old class definition: Class@" + m.getClass().hashCode()); + ClassLoader delegateParent = Module.class.getClassLoader(); + Class newClass = null; + try (URLClassLoader cl = new URLClassLoader(urls, delegateParent)) + { + newClass = cl.loadClass(m.getClass().getName()); + Utils.info("Found new class definition: Class@" + newClass.hashCode()); + differs = m.getClass() != newClass; + } + catch (IOException | ClassNotFoundException e) + { + Utils.error("Could not find a class definition, aborting now!"); + e.printStackTrace(); + return; + } + if (!differs) + { + Utils.warn("New class definition equals old definition, are you sure you did everything right?"); + Utils.info("Aborting now..."); + return; + } + Utils.info("Found new class definition, attempting to instantiate:"); + Module module = null; + try + { + module = (Module) newClass.newInstance(); + } + catch (InstantiationException | IllegalAccessException e) + { + Utils.error("Could not instantiate the module, aborting!"); + e.printStackTrace(); + return; + } + Utils.info("Instantiated new class definition, checking versions:"); + Version oldVersion = m.getClass().getAnnotation(Version.class); + Utils.info("Current version: " + VersionHelper.getString(oldVersion)); + Version newVersion = module.getClass().getAnnotation(Version.class); + Utils.info("Version of remote class: " + VersionHelper.getString(newVersion)); + if (oldVersion.equals(newVersion)) + { + Utils.error("Detected equal module versions, aborting now..."); + return; + } + Utils.info("Versions differ, disabling old module:"); + disableModule(m); + Utils.info("Disabled module, overriding the implementation:"); + modules.remove(m); + modules.put(module, false); + Utils.info("Successfully updated class definition. Enabling new implementation:"); + enableLoadedModule(module); + Object[] newStatus = getServerStatus(); + Utils.info("Task complete! Took " + ((long) newStatus[0] - (long) status[0]) + "ms to finish!"); + Utils.info("Current server status:"); + Utils.info("Current system time: " + newStatus[0]); + Utils.info("Current tick: " + newStatus[1]); + Utils.info("Last TPS: " + newStatus[2]); + Utils.info("Entity count: " + newStatus[3]); + Utils.info("Player count: " + newStatus[4]); + return; + } + } + try + { + Class clazz = mainLoader.loadClass(name); + Module module = (Module) clazz.newInstance(); + addLoadedModule(module); + enableLoadedModule(module); + } + catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) + { + if (!name.startsWith("com.redstoner.modules.")) + { + Utils.warn( + "Couldn't find class definition, suspecting missing path. Autocompleting path, trying again."); + addDynamicModule("com.redstoner.modules." + name); + } + else + e.printStackTrace(); + } + } + + @SuppressWarnings("deprecation") + private static final Object[] getServerStatus() + { + final Object[] status = new Object[5]; + status[0] = System.currentTimeMillis(); + status[1] = MinecraftServer.currentTick; + status[2] = MinecraftServer.getServer().recentTps[0]; + int i = 0; + for (World w : Bukkit.getWorlds()) + { + i += w.getEntities().size(); + } + status[3] = i; + status[4] = Bukkit.getOnlinePlayers().size(); + return status; } } diff --git a/src/com/redstoner/misc/VersionHelper.java b/src/com/redstoner/misc/VersionHelper.java index 9e7bf35..2c4a266 100644 --- a/src/com/redstoner/misc/VersionHelper.java +++ b/src/com/redstoner/misc/VersionHelper.java @@ -8,7 +8,7 @@ import com.redstoner.exceptions.MissingVersionException; /** This class can be used to compare modules against the loader version or against each other to prevent dependency issues. * * @author Pepich */ -@Version(major = 2, minor = 1, revision = 1, compatible = 0) +@Version(major = 2, minor = 1, revision = 2, compatible = 0) public final class VersionHelper { private VersionHelper() @@ -86,6 +86,15 @@ public final class VersionHelper if (!clazz.isAnnotationPresent(Version.class)) throw new MissingVersionException("The given class is not associated with a version."); Version ver = clazz.getAnnotation(Version.class); + return getString(ver); + } + + /** Returns the String representation of a version. + * + * @param ver The version to be represented. + * @return The String representation. */ + public static String getString(Version ver) + { return ver.major() + "." + ver.minor() + "." + ver.revision() + "." + ver.compatible(); }