512 lines
15 KiB
Java
512 lines
15 KiB
Java
package com.redstoner.coremods.moduleLoader;
|
|
|
|
import java.io.File;
|
|
import java.io.FileNotFoundException;
|
|
import java.io.IOException;
|
|
import java.net.MalformedURLException;
|
|
import java.net.URL;
|
|
import java.net.URLClassLoader;
|
|
import java.util.HashMap;
|
|
import java.util.List;
|
|
|
|
import org.bukkit.Bukkit;
|
|
import org.bukkit.World;
|
|
import org.bukkit.command.CommandSender;
|
|
import org.bukkit.configuration.InvalidConfigurationException;
|
|
import org.bukkit.configuration.file.FileConfiguration;
|
|
import org.bukkit.event.HandlerList;
|
|
import org.bukkit.event.Listener;
|
|
|
|
import com.nemez.cmdmgr.Command;
|
|
import com.nemez.cmdmgr.Command.AsyncType;
|
|
import com.nemez.cmdmgr.CommandManager;
|
|
import com.redstoner.annotations.AutoRegisterListener;
|
|
import com.redstoner.annotations.Debugable;
|
|
import com.redstoner.annotations.Version;
|
|
import com.redstoner.coremods.debugger.Debugger;
|
|
import com.redstoner.exceptions.MissingVersionException;
|
|
import com.redstoner.misc.Main;
|
|
import com.redstoner.misc.Utils;
|
|
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 */
|
|
@Version(major = 3, minor = 1, revision = 4, compatible = 2)
|
|
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 static HashMap<Module, URLClassLoader> loaders = new HashMap<Module, URLClassLoader>();
|
|
private static File configFile;
|
|
private static FileConfiguration config;
|
|
|
|
private ModuleLoader()
|
|
{
|
|
try
|
|
{
|
|
config = Main.plugin.getConfig();
|
|
configFile = new File(Main.plugin.getDataFolder(), "config.yml");
|
|
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()
|
|
{
|
|
if (instance == null)
|
|
instance = new ModuleLoader();
|
|
CommandManager.registerCommand(ModuleLoader.class.getResourceAsStream("ModuleLoader.cmd"), instance,
|
|
Main.plugin);
|
|
}
|
|
|
|
public static final void loadFromConfig()
|
|
{
|
|
try
|
|
{
|
|
if (!configFile.exists())
|
|
{
|
|
configFile.getParentFile().mkdirs();
|
|
configFile.createNewFile();
|
|
}
|
|
config.load(configFile);
|
|
}
|
|
catch (FileNotFoundException e)
|
|
{}
|
|
catch (IOException e)
|
|
{
|
|
e.printStackTrace();
|
|
}
|
|
catch (InvalidConfigurationException e)
|
|
{
|
|
configFile.delete();
|
|
try
|
|
{
|
|
configFile.createNewFile();
|
|
}
|
|
catch (IOException e1)
|
|
{
|
|
e1.printStackTrace();
|
|
}
|
|
Utils.error("Invalid config file! Creating new, blank file!");
|
|
}
|
|
List<String> autoload = config.getStringList("autoLoad");
|
|
if (autoload == null || autoload.isEmpty())
|
|
{
|
|
config.set("autoLoad", new String[] {"# Add the modules here!"});
|
|
Main.plugin.saveConfig();
|
|
try
|
|
{
|
|
config.save(configFile);
|
|
}
|
|
catch (IOException e)
|
|
{
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
for (String s : autoload)
|
|
if (!s.startsWith("#"))
|
|
ModuleLoader.addDynamicModule(s);
|
|
}
|
|
|
|
/** 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);
|
|
try
|
|
{
|
|
Module module = clazz.newInstance();
|
|
modules.put(module, false);
|
|
}
|
|
catch (InstantiationException | IllegalAccessException e)
|
|
{
|
|
Utils.error("Could not add " + clazz.getName() + " to the list, constructor not accessible.");
|
|
}
|
|
}
|
|
|
|
@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. */
|
|
@SuppressWarnings("deprecation")
|
|
@Debugable
|
|
public static final void enableModules()
|
|
{
|
|
Debugger.notifyMethod();
|
|
for (Module module : modules.keySet())
|
|
{
|
|
if (modules.get(module))
|
|
continue;
|
|
enableLoadedModule(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);
|
|
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();
|
|
}
|
|
}
|
|
Utils.info("Modules enabled, running post initialization.");
|
|
for (Module module : modules.keySet())
|
|
{
|
|
if (modules.get(module))
|
|
{
|
|
try
|
|
{
|
|
if (VersionHelper.isCompatible(VersionHelper.create(3, 0, 0, 3), module.getClass()))
|
|
{
|
|
module.postEnable();
|
|
}
|
|
}
|
|
catch (MissingVersionException e)
|
|
{
|
|
e.printStackTrace();
|
|
}
|
|
if (module.getClass().isAnnotationPresent(AutoRegisterListener.class) && (module instanceof Listener))
|
|
{
|
|
Bukkit.getPluginManager().registerEvents((Listener) module, Main.plugin);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/** 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);
|
|
for (Module module : modules.keySet())
|
|
{
|
|
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)
|
|
&& (module instanceof Listener))
|
|
{
|
|
Bukkit.getPluginManager().registerEvents((Listener) module, Main.plugin);
|
|
}
|
|
Utils.info("Enabled module " + module.getClass().getName());
|
|
Utils.info("Loaded module " + module.getClass().getName());
|
|
modules.put(module, true);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
Utils.error("Failed to enable module " + module.getClass().getName());
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
try
|
|
{
|
|
Module m = clazz.newInstance();
|
|
modules.put(m, false);
|
|
if (m.onEnable())
|
|
{
|
|
if (m.getClass().isAnnotationPresent(AutoRegisterListener.class) && (m instanceof Listener))
|
|
{
|
|
Bukkit.getPluginManager().registerEvents((Listener) m, Main.plugin);
|
|
}
|
|
Utils.info("Loaded and enabled module " + m.getClass().getName());
|
|
Utils.info("Loaded module " + m.getClass().getName());
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
Utils.error("Failed to enable module " + m.getClass().getName());
|
|
return false;
|
|
}
|
|
}
|
|
catch (InstantiationException | IllegalAccessException e)
|
|
{
|
|
Utils.error("Could not add " + clazz.getName() + " to the list, constructor not accessible.");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
@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());
|
|
if (module.getClass().isAnnotationPresent(AutoRegisterListener.class) && (module instanceof Listener))
|
|
{
|
|
Bukkit.getPluginManager().registerEvents((Listener) module, Main.plugin);
|
|
}
|
|
}
|
|
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.
|
|
* @return true. */
|
|
@Command(hook = "list", async = AsyncType.ALWAYS)
|
|
public boolean listModulesCommand(CommandSender sender)
|
|
{
|
|
Utils.sendModuleHeader(sender);
|
|
StringBuilder sb = new StringBuilder("Modules:\n");
|
|
for (Module module : modules.keySet())
|
|
{
|
|
String[] classPath = module.getClass().getName().split("\\.");
|
|
String classname = classPath[classPath.length - 1];
|
|
sb.append(modules.get(module) ? "&a" : "&c");
|
|
sb.append(classname);
|
|
sb.append(", ");
|
|
}
|
|
sb.delete(sb.length() - 2, sb.length());
|
|
Utils.sendMessage(sender, " §e", sb.toString(), '&');
|
|
Utils.sendMessage(sender, " §7", "For more detailed information, consult the debugger.");
|
|
return true;
|
|
}
|
|
|
|
public static void disableModules()
|
|
{
|
|
for (Module module : modules.keySet())
|
|
{
|
|
disableModule(module);
|
|
}
|
|
}
|
|
|
|
public static void disableModule(Module module)
|
|
{
|
|
if (modules.get(module))
|
|
{
|
|
module.onDisable();
|
|
if (module.getClass().isAnnotationPresent(AutoRegisterListener.class) && (module instanceof Listener))
|
|
{
|
|
HandlerList.unregisterAll((Listener) module);
|
|
}
|
|
}
|
|
}
|
|
|
|
@Command(hook = "load")
|
|
public boolean loadModule(CommandSender sender, String name)
|
|
{
|
|
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 = mainLoader.getParent();
|
|
Class<?> newClass = null;
|
|
URLClassLoader cl = new URLClassLoader(urls, delegateParent);
|
|
try
|
|
{
|
|
newClass = cl.loadClass(m.getClass().getName());
|
|
Utils.info("Found new class definition: Class@" + newClass.hashCode());
|
|
differs = m.getClass() != newClass;
|
|
}
|
|
catch (ClassNotFoundException e)
|
|
{
|
|
Utils.error("Could not find a class definition, aborting now!");
|
|
e.printStackTrace();
|
|
try
|
|
{
|
|
cl.close();
|
|
}
|
|
catch (IOException e1)
|
|
{
|
|
e1.printStackTrace();
|
|
}
|
|
return;
|
|
}
|
|
if (!differs)
|
|
{
|
|
Utils.warn("New class definition equals old definition, are you sure you did everything right?");
|
|
Utils.info("Aborting now...");
|
|
try
|
|
{
|
|
cl.close();
|
|
}
|
|
catch (IOException e)
|
|
{
|
|
e.printStackTrace();
|
|
}
|
|
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();
|
|
try
|
|
{
|
|
cl.close();
|
|
}
|
|
catch (IOException e1)
|
|
{
|
|
e1.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...");
|
|
try
|
|
{
|
|
cl.close();
|
|
}
|
|
catch (IOException e)
|
|
{
|
|
e.printStackTrace();
|
|
}
|
|
return;
|
|
}
|
|
Utils.info("Versions differ, disabling old module:");
|
|
disableModule(m);
|
|
Utils.info("Disabled module, overriding the implementation:");
|
|
modules.remove(m);
|
|
try
|
|
{
|
|
if (loaders.containsKey(m))
|
|
loaders.remove(m).close();
|
|
}
|
|
catch (IOException e)
|
|
{
|
|
e.printStackTrace();
|
|
}
|
|
modules.put(module, false);
|
|
loaders.put(module, cl);
|
|
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;
|
|
}
|
|
}
|