From cda5ef98daa057997d5ad0c10677b6fd5bc6d094 Mon Sep 17 00:00:00 2001 From: Travis CI Date: Sat, 22 Jun 2019 22:28:59 +0200 Subject: [PATCH] Adding softreload --- src/de/steamwar/bungeecore/Persistent.java | 114 +++++++++++++++++- .../steamwar/bungeecore/ReflectionUtils.java | 70 +++++++++++ 2 files changed, 182 insertions(+), 2 deletions(-) create mode 100644 src/de/steamwar/bungeecore/ReflectionUtils.java diff --git a/src/de/steamwar/bungeecore/Persistent.java b/src/de/steamwar/bungeecore/Persistent.java index 2379e4f..2e41752 100644 --- a/src/de/steamwar/bungeecore/Persistent.java +++ b/src/de/steamwar/bungeecore/Persistent.java @@ -1,9 +1,22 @@ package de.steamwar.bungeecore; +import com.google.common.collect.Multimap; import net.md_5.bungee.api.event.ProxyReloadEvent; -import net.md_5.bungee.api.plugin.Listener; -import net.md_5.bungee.api.plugin.Plugin; +import net.md_5.bungee.api.plugin.*; import net.md_5.bungee.event.EventHandler; +import org.yaml.snakeyaml.Yaml; + +import java.io.File; +import java.io.InputStream; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.logging.Handler; +import java.util.logging.Level; public class Persistent extends Plugin implements Listener { @@ -23,7 +36,104 @@ public class Persistent extends Plugin implements Listener { @EventHandler public void onGReload(ProxyReloadEvent e){ + PluginManager pluginManager = getProxy().getPluginManager(); + Plugin bungeecore = pluginManager.getPlugin("BungeeCore"); + ClassLoader pluginClassLoader = bungeecore.getClass().getClassLoader(); + bungeecore.onDisable(); + for(Handler h : bungeecore.getLogger().getHandlers()){ + h.close(); + } + + pluginManager.unregisterCommands(bungeecore); + pluginManager.unregisterListeners(bungeecore); + getProxy().getScheduler().cancel(bungeecore); + bungeecore.getExecutorService().shutdownNow(); + Thread.getAllStackTraces().keySet().stream() + .filter(thread -> (thread.getClass().getClassLoader() == pluginClassLoader)) + .forEach(thread -> { + try { + thread.interrupt(); + thread.join(2000); + if (thread.isAlive()) { + throw new IllegalStateException("Thread " + thread.getName() + " still running"); + } + } catch (Exception t) { + getProxy().getLogger().log(Level.SEVERE, "Failed to stop thread that belong to plugin", t); + } + }); + //remove commands that were registered by plugin not through normal means + try { + Map commandMap = ReflectionUtils.getFieldValue(pluginManager, "commandMap"); + if (commandMap != null) { + commandMap.entrySet().removeIf(entry -> entry.getValue().getClass().getClassLoader() == pluginClassLoader); + } + } catch (Exception t) { + getLogger().log(Level.SEVERE, "Failed to cleanup commandMap", t); + } + try { + Map pluginsMap = ReflectionUtils.getFieldValue(pluginManager, "plugins"); + Multimap commands = ReflectionUtils.getFieldValue(pluginManager, "commandsByPlugin"); + Multimap listeners = ReflectionUtils.getFieldValue(pluginManager, "listenersByPlugin"); + + if (pluginsMap != null && commands != null && listeners != null) { + pluginsMap.values().remove(bungeecore); + commands.removeAll(bungeecore); + listeners.removeAll(bungeecore); + } + } catch (Exception t) { + getLogger().log(Level.SEVERE, "Failed to cleanup bungee internal maps from plugin refs", t); + } + if (pluginClassLoader instanceof URLClassLoader) { + try { + ((URLClassLoader) pluginClassLoader).close(); + } catch (Exception t) { + getLogger().log(Level.SEVERE, "Failed to close classloader", t); + } + } + Set allLoaders = ReflectionUtils.getStaticFieldValue(PluginClassloader.class, "allLoaders"); + if (allLoaders != null) { + allLoaders.remove(pluginClassLoader); + } + + File pluginFile = new File(getProxy().getPluginsFolder(), "BungeeCore.jar"); + try (JarFile jar = new JarFile(pluginFile)) { + JarEntry pdf = jar.getJarEntry("plugin.yml"); + + try (InputStream in = jar.getInputStream(pdf)) { + //load description + PluginDescription desc = new Yaml().loadAs(in, PluginDescription.class); + desc.setFile(pluginFile); + //check depends + HashSet plugins = new HashSet<>(); + getProxy().getPluginManager().getPlugins().forEach(plugin -> plugins.add(plugin.getDescription().getName())); + for (String dependency : desc.getDepends()) { + if (!plugins.contains(dependency)) { + getLogger().log(Level.WARNING, "{0} (required by {1}) is unavailable", new Object[]{dependency, desc.getName()}); + return; + } + } + + // do actual loading + Class main; + try (URLClassLoader loader = new PluginClassloader(new URL[]{ + pluginFile.toURI().toURL() + })) { + main = loader.loadClass(desc.getMain()); + } + Plugin clazz = (Plugin) main.getDeclaredConstructor().newInstance(); + + // reflection + Map pluginsMap = ReflectionUtils.getFieldValue(getProxy().getPluginManager(), "plugins"); + ReflectionUtils.invokeMethod(clazz, "init", getProxy(), desc); + + pluginsMap.put(desc.getName(), clazz); + clazz.onLoad(); + clazz.onEnable(); + } + } catch (Exception t) { + getLogger().log(Level.SEVERE, "Failed to load plugin", t); + } } public static void setLobbyServer(String lobbyServer) { diff --git a/src/de/steamwar/bungeecore/ReflectionUtils.java b/src/de/steamwar/bungeecore/ReflectionUtils.java new file mode 100644 index 0000000..c451db5 --- /dev/null +++ b/src/de/steamwar/bungeecore/ReflectionUtils.java @@ -0,0 +1,70 @@ +package de.steamwar.bungeecore; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +final class ReflectionUtils { + private ReflectionUtils() { + throw new IllegalStateException("Utility class"); + } + + @SuppressWarnings("unchecked") + static T getFieldValue(Object obj, String fieldName) { + Class clazz = obj.getClass(); + do { + try { + Field field = clazz.getDeclaredField(fieldName); + field.setAccessible(true); + return (T) field.get(obj); + } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException error) { + // Ignored + } + } while ((clazz = clazz.getSuperclass()) != null); + return null; + } + + static void setFieldValue(Object obj, String fieldName, Object value) { + Class clazz = obj.getClass(); + do { + try { + Field field = clazz.getDeclaredField(fieldName); + field.setAccessible(true); + field.set(obj, value); + } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException error) { + // Ignored + } + } while ((clazz = clazz.getSuperclass()) != null); + } + + @SuppressWarnings("unchecked") + static T getStaticFieldValue(Class clazz, String fieldName) { + do { + try { + Field field = clazz.getDeclaredField(fieldName); + field.setAccessible(true); + return (T) field.get(null); + } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException error) { + // Ignored + } + } while ((clazz = clazz.getSuperclass()) != null); + return null; + } + + static void invokeMethod(Object obj, String methodName, Object... args) { + Class clazz = obj.getClass(); + do { + try { + for (Method method : clazz.getDeclaredMethods()) { + if (method.getName().equals(methodName) && method.getParameterTypes().length == args.length) { + method.setAccessible(true); + method.invoke(obj, args); + } + } + } catch (SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException error) { + // Ignore + } + } while ((clazz = clazz.getSuperclass()) != null); + } + +} \ No newline at end of file