0

Added dynamic loading functionality, Updated API to v3.1.0

This commit is contained in:
Pepich 2017-03-29 09:30:15 +02:00
parent 0db0c9b5f6
commit dbca8263a0
2 changed files with 216 additions and 33 deletions

View File

@ -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;
}
}

View File

@ -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();
}