Added dynamic loading functionality, Updated API to v3.1.0
This commit is contained in:
parent
0db0c9b5f6
commit
dbca8263a0
@ -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<Module, Boolean> modules = new HashMap<Module, Boolean>();
|
||||
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.</br>
|
||||
* 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<? extends Module> 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.</br>
|
||||
* 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<? extends Module> 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;
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user