From ef20c0fa3a544f2a4f2acd42ee3fb5559c65e0b5 Mon Sep 17 00:00:00 2001 From: bundabrg Date: Mon, 15 Jun 2020 10:08:20 +0800 Subject: [PATCH] Implement Event Management and move Plugins to utilize that Instead of plugins being the focus Geyser instead implements an Event Management system that can be used internally. Plugins are then able to utilize this through the convenience of an external jar and registering annotated classes. --- .../geysermc/connector/GeyserConnector.java | 11 +- .../connector/event/EventContext.java | 31 ++++ .../connector/event/EventHandler.java | 70 ++++++++ .../connector/event/EventManager.java | 141 +++++++++++++++ .../events}/CancellableGeyserEvent.java | 2 +- .../events/DisableEvent.java | 5 +- .../{plugin => event}/events/EnableEvent.java | 4 +- .../api => event/events}/GeyserEvent.java | 2 +- .../connector/plugin/GeyserPlugin.java | 110 ++++++++++++ .../connector/plugin/PluginClassLoader.java | 19 +- .../connector/plugin/PluginManager.java | 166 ++++-------------- .../connector/plugin/annotations/Event.java | 12 +- 12 files changed, 405 insertions(+), 168 deletions(-) create mode 100644 connector/src/main/java/org/geysermc/connector/event/EventContext.java create mode 100644 connector/src/main/java/org/geysermc/connector/event/EventHandler.java create mode 100644 connector/src/main/java/org/geysermc/connector/event/EventManager.java rename connector/src/main/java/org/geysermc/connector/{plugin/api => event/events}/CancellableGeyserEvent.java (96%) rename connector/src/main/java/org/geysermc/connector/{plugin => event}/events/DisableEvent.java (91%) rename connector/src/main/java/org/geysermc/connector/{plugin => event}/events/EnableEvent.java (93%) rename connector/src/main/java/org/geysermc/connector/{plugin/api => event/events}/GeyserEvent.java (96%) create mode 100644 connector/src/main/java/org/geysermc/connector/plugin/GeyserPlugin.java diff --git a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java index 25d92f4ad..deac2586c 100644 --- a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java +++ b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java @@ -35,6 +35,7 @@ import org.geysermc.common.AuthType; import org.geysermc.common.PlatformType; import org.geysermc.connector.bootstrap.GeyserBootstrap; import org.geysermc.connector.command.CommandManager; +import org.geysermc.connector.event.EventManager; import org.geysermc.connector.metrics.Metrics; import org.geysermc.connector.network.ConnectorServerEventHandler; import org.geysermc.connector.network.remote.RemoteServer; @@ -50,8 +51,8 @@ import org.geysermc.connector.network.translators.world.block.BlockTranslator; import org.geysermc.connector.network.translators.effect.EffectRegistry; import org.geysermc.connector.network.translators.world.block.entity.BlockEntityTranslator; import org.geysermc.connector.plugin.PluginManager; -import org.geysermc.connector.plugin.events.DisableEvent; -import org.geysermc.connector.plugin.events.EnableEvent; +import org.geysermc.connector.event.events.DisableEvent; +import org.geysermc.connector.event.events.EnableEvent; import org.geysermc.connector.utils.DimensionUtils; import org.geysermc.connector.utils.DockerCheck; import org.geysermc.connector.utils.LocaleUtils; @@ -92,6 +93,7 @@ public class GeyserConnector { private PlatformType platformType; private GeyserBootstrap bootstrap; + private final EventManager eventManager; private final PluginManager pluginManager; private Metrics metrics; @@ -102,6 +104,7 @@ public class GeyserConnector { instance = this; this.bootstrap = bootstrap; + this.eventManager = new EventManager(this); this.pluginManager = new PluginManager(this, new File(bootstrap.getDataFolder(), "plugins")); GeyserLogger logger = bootstrap.getGeyserLogger(); @@ -163,7 +166,7 @@ public class GeyserConnector { } // Trigger all plugins Enable Events - pluginManager.triggerEvent(new EnableEvent()); + eventManager.triggerEvent(new EnableEvent()); double completeTime = (System.currentTimeMillis() - startupTime) / 1000D; logger.info(String.format("Done (%ss)! Run /geyser help for help!", new DecimalFormat("#.###").format(completeTime))); @@ -174,7 +177,7 @@ public class GeyserConnector { shuttingDown = true; // Trigger all plugins Disable Events - pluginManager.triggerEvent(new DisableEvent()); + eventManager.triggerEvent(new DisableEvent()); if (players.size() >= 1) { bootstrap.getGeyserLogger().info("Kicking " + players.size() + " player(s)"); diff --git a/connector/src/main/java/org/geysermc/connector/event/EventContext.java b/connector/src/main/java/org/geysermc/connector/event/EventContext.java new file mode 100644 index 000000000..be897e313 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/event/EventContext.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + * + */ + +package org.geysermc.connector.event; + +public interface EventContext { + void unregister(); +} \ No newline at end of file diff --git a/connector/src/main/java/org/geysermc/connector/event/EventHandler.java b/connector/src/main/java/org/geysermc/connector/event/EventHandler.java new file mode 100644 index 000000000..c5bd2d4fa --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/event/EventHandler.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + * + */ + +package org.geysermc.connector.event; + +import jdk.nashorn.internal.ir.annotations.Immutable; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.geysermc.connector.event.events.GeyserEvent; + +import java.util.Comparator; + +@Getter +@AllArgsConstructor +public class EventHandler implements Comparator>, Comparable> { + public final Class eventClass; + public final Executor executor; + public final int priority; + public final boolean ignoreCancelled; + + public void execute(EventContext ctx, T event) { + executor.run(ctx, event); + } + + @Override + public int compare(EventHandler left, EventHandler right) { + return left.getPriority() - right.getPriority(); + } + + @Override + public int compareTo(EventHandler other) { + return getPriority() - other.getPriority(); + } + + public interface Executor { + void run(EventContext ctx, T event); + } + + @Immutable + public static final class PRIORITY { + public final static int LOWEST = 10; + public final static int LOW = 4; + public final static int NORMAL = 50; + public final static int HIGH = 60; + public final static int HIGHEST = 90; + } +} diff --git a/connector/src/main/java/org/geysermc/connector/event/EventManager.java b/connector/src/main/java/org/geysermc/connector/event/EventManager.java new file mode 100644 index 000000000..1d3a1ad98 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/event/EventManager.java @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + * + */ + +package org.geysermc.connector.event; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.event.events.CancellableGeyserEvent; +import org.geysermc.connector.event.events.GeyserEvent; + +import java.util.HashMap; +import java.util.Map; +import java.util.PriorityQueue; + +@Getter +@AllArgsConstructor +public class EventManager { + private final Map, PriorityQueue>> eventHandlers = new HashMap<>(); + + private final GeyserConnector connector; + + /** + * Trigger a new event + * + * This will be executed with all registered handlers in order of priority + */ + public void triggerEvent(GeyserEvent event) { + if (!eventHandlers.containsKey(event.getClass())) { + return; + } + + for (EventHandler handler : eventHandlers.get(event.getClass())) { + Context ctx = new Context(this, handler); + + //noinspection unchecked + ((EventHandler)handler).execute(ctx, event); + } + } + + /** + * Trigger a new cancellable event + * + * This will be executed with all registered handlers in order of priority ignoring those who + * do not wish to process events that are cancelled + */ + public void triggerEvent(CancellableGeyserEvent event) { + if (!eventHandlers.containsKey(event.getClass())) { + return; + } + + boolean cancelled = event.isCancelled(); + for (EventHandler handler : eventHandlers.get(event.getClass())) { + if (!cancelled || !handler.isIgnoreCancelled()) { + Context ctx = new Context(this, handler); + + //noinspection unchecked + ((EventHandler) handler).execute(ctx, event); + + cancelled = event.isCancelled(); + } + } + } + + /** + * Create a new EventHandler using an Executor + */ + public EventHandler on(Class cls, EventHandler.Executor executor, int priority, boolean ignoreCancelled) { + EventHandler handler = new EventHandler<>(cls, executor, priority, ignoreCancelled); + register(handler); + return handler; + } + + public EventHandler on(Class cls, EventHandler.Executor executor) { + return on(cls, executor, EventHandler.PRIORITY.NORMAL, true); + } + + public EventHandler on(Class cls, EventHandler.Executor executor, boolean ignoreCancelled) { + return on(cls, executor, EventHandler.PRIORITY.NORMAL, ignoreCancelled); + } + + public EventHandler on(Class cls, EventHandler.Executor executor, int priority) { + return on(cls, executor, priority, true); + } + + /** + * Register an EventHandler + */ + public void register(EventHandler handler) { + if (!eventHandlers.containsKey(handler.getEventClass())) { + eventHandlers.put(handler.getEventClass(), new PriorityQueue<>()); + } + eventHandlers.get(handler.getEventClass()).add(handler); + } + + /** + * Unregister an EventHandler + */ + public void unregister(EventHandler handler) { + if (eventHandlers.containsKey(handler.getEventClass())) { + eventHandlers.get(handler.getEventClass()).remove(handler); + } + } + + @AllArgsConstructor + public static class Context implements EventContext { + private final EventManager manager; + private final EventHandler handler; + + /** + * Unregister an EventHandler from within an Event execution + */ + @Override + public void unregister() { + manager.unregister(handler); + } + } +} diff --git a/connector/src/main/java/org/geysermc/connector/plugin/api/CancellableGeyserEvent.java b/connector/src/main/java/org/geysermc/connector/event/events/CancellableGeyserEvent.java similarity index 96% rename from connector/src/main/java/org/geysermc/connector/plugin/api/CancellableGeyserEvent.java rename to connector/src/main/java/org/geysermc/connector/event/events/CancellableGeyserEvent.java index fdc4cf0d5..582896ff8 100644 --- a/connector/src/main/java/org/geysermc/connector/plugin/api/CancellableGeyserEvent.java +++ b/connector/src/main/java/org/geysermc/connector/event/events/CancellableGeyserEvent.java @@ -24,7 +24,7 @@ * */ -package org.geysermc.connector.plugin.api; +package org.geysermc.connector.event.events; import lombok.Getter; import lombok.Setter; diff --git a/connector/src/main/java/org/geysermc/connector/plugin/events/DisableEvent.java b/connector/src/main/java/org/geysermc/connector/event/events/DisableEvent.java similarity index 91% rename from connector/src/main/java/org/geysermc/connector/plugin/events/DisableEvent.java rename to connector/src/main/java/org/geysermc/connector/event/events/DisableEvent.java index f41def665..23fe96586 100644 --- a/connector/src/main/java/org/geysermc/connector/plugin/events/DisableEvent.java +++ b/connector/src/main/java/org/geysermc/connector/event/events/DisableEvent.java @@ -24,10 +24,7 @@ * */ -package org.geysermc.connector.plugin.events; - -import lombok.ToString; -import org.geysermc.connector.plugin.api.GeyserEvent; +package org.geysermc.connector.event.events; /** * DisableEvent is triggered for each plugin when disabling it diff --git a/connector/src/main/java/org/geysermc/connector/plugin/events/EnableEvent.java b/connector/src/main/java/org/geysermc/connector/event/events/EnableEvent.java similarity index 93% rename from connector/src/main/java/org/geysermc/connector/plugin/events/EnableEvent.java rename to connector/src/main/java/org/geysermc/connector/event/events/EnableEvent.java index a3242f4aa..23887048f 100644 --- a/connector/src/main/java/org/geysermc/connector/plugin/events/EnableEvent.java +++ b/connector/src/main/java/org/geysermc/connector/event/events/EnableEvent.java @@ -24,9 +24,7 @@ * */ -package org.geysermc.connector.plugin.events; - -import org.geysermc.connector.plugin.api.CancellableGeyserEvent; +package org.geysermc.connector.event.events; /** * EnableEvent is triggered for each plugin when enabling it diff --git a/connector/src/main/java/org/geysermc/connector/plugin/api/GeyserEvent.java b/connector/src/main/java/org/geysermc/connector/event/events/GeyserEvent.java similarity index 96% rename from connector/src/main/java/org/geysermc/connector/plugin/api/GeyserEvent.java rename to connector/src/main/java/org/geysermc/connector/event/events/GeyserEvent.java index 0e8e2f1f6..206150316 100644 --- a/connector/src/main/java/org/geysermc/connector/plugin/api/GeyserEvent.java +++ b/connector/src/main/java/org/geysermc/connector/event/events/GeyserEvent.java @@ -24,7 +24,7 @@ * */ -package org.geysermc.connector.plugin.api; +package org.geysermc.connector.event.events; public abstract class GeyserEvent { diff --git a/connector/src/main/java/org/geysermc/connector/plugin/GeyserPlugin.java b/connector/src/main/java/org/geysermc/connector/plugin/GeyserPlugin.java new file mode 100644 index 000000000..b706e3dbe --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/plugin/GeyserPlugin.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + * + */ + +package org.geysermc.connector.plugin; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.geysermc.connector.event.EventHandler; +import org.geysermc.connector.event.events.GeyserEvent; +import org.geysermc.connector.plugin.annotations.Event; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +/** + * All GeyserPlugins extend from this + */ +@Getter +@AllArgsConstructor +public abstract class GeyserPlugin { + private final Map>> classEventHandlers = new HashMap<>(); + + private final PluginManager pluginManager; + private final PluginClassLoader pluginClassLoader; + + /** + * Register all Events contained in a class + */ + public void registerEvents(Object obj) { + for (Method method : obj.getClass().getMethods()) { + Event eventAnnotation = method.getAnnotation(Event.class); + + // Check that the method is annotated with @Event + if (eventAnnotation == null) { + continue; + } + + // Make sure it only has a single Event parameter + if (method.getParameterCount() != 2 || !GeyserEvent.class.isAssignableFrom(method.getParameters()[1].getType())) { + continue; + } + + //noinspection unchecked + EventHandler handler = pluginManager.getConnector().getEventManager() + .on((Class)method.getParameters()[1].getType(), (ctx, event) -> { + try { + method.invoke(obj, ctx, event); + } catch (IllegalAccessException | InvocationTargetException e) { + e.printStackTrace(); + } + }); + + if (!classEventHandlers.containsKey(obj.getClass())) { + classEventHandlers.put(obj, new ArrayList<>()); + } + + classEventHandlers.get(obj).add(handler); + } + } + + /** + * Unregister events in class + */ + public void unregisterEvents(Object obj) { + if (!classEventHandlers.containsKey(obj)) { + return; + } + + for (EventHandler handler : classEventHandlers.get(obj)) { + pluginManager.getConnector().getEventManager().unregister(handler); + } + + classEventHandlers.remove(obj); + } + + /** + * Unregister all events for a plugin + */ + public void unregisterPluginEvents(Class plugin) { + for (Object obj : classEventHandlers.keySet()) { + unregisterEvents(obj); + } + } +} diff --git a/connector/src/main/java/org/geysermc/connector/plugin/PluginClassLoader.java b/connector/src/main/java/org/geysermc/connector/plugin/PluginClassLoader.java index 52b789eaa..7999d3114 100644 --- a/connector/src/main/java/org/geysermc/connector/plugin/PluginClassLoader.java +++ b/connector/src/main/java/org/geysermc/connector/plugin/PluginClassLoader.java @@ -52,16 +52,16 @@ public class PluginClassLoader extends URLClassLoader { private final Map> classes = new ConcurrentHashMap<>(); @Getter - private final Object plugin; + private final Class pluginClass; PluginClassLoader(PluginManager pluginManager, ClassLoader parent, File pluginFile) throws IOException, InvalidPluginClassLoaderException { super(new URL[] {pluginFile.toURI().toURL()}, parent); this.jar = new JarFile(pluginFile); this.pluginManager = pluginManager; - this.plugin = findPlugin(); + this.pluginClass = findPlugin(); - if (this.plugin == null) { + if (this.pluginClass == null) { throw new InvalidPluginClassLoaderException("Unable to find class annotated by @Plugin"); } } @@ -69,7 +69,7 @@ public class PluginClassLoader extends URLClassLoader { /** * Find first class annotated by @Plugin */ - private Object findPlugin() { + private Class findPlugin() { for (Enumeration entries = jar.entries(); entries.hasMoreElements();) { JarEntry entry = entries.nextElement(); if (!entry.getName().endsWith(".class")) { @@ -83,9 +83,10 @@ public class PluginClassLoader extends URLClassLoader { continue; } - if (cls.getAnnotation(Plugin.class) != null) { + if (GeyserPlugin.class.isAssignableFrom(cls) && cls.getAnnotation(Plugin.class) != null) { cacheClass(cls, true); - return cls; + //noinspection unchecked + return (Class) cls; } } return null; @@ -98,7 +99,7 @@ public class PluginClassLoader extends URLClassLoader { classes.put(cls.getName(), cls); if (global) { - pluginManager.getPluginClasses().put(cls.getName(), cls); + pluginManager.getGlobalClasses().put(cls.getName(), cls); } } @@ -157,11 +158,11 @@ public class PluginClassLoader extends URLClassLoader { return classes.get(name); } - boolean global = plugin.getClass().getAnnotation(Plugin.class).global(); + boolean global = pluginClass.getAnnotation(Plugin.class).global(); // Now try load from global if permitted if (global) { - Class cls = pluginManager.getPluginClasses().get(name); + Class cls = pluginManager.getGlobalClasses().get(name); if (cls != null) { return cls; } diff --git a/connector/src/main/java/org/geysermc/connector/plugin/PluginManager.java b/connector/src/main/java/org/geysermc/connector/plugin/PluginManager.java index 4e42880c7..6766b3c6c 100644 --- a/connector/src/main/java/org/geysermc/connector/plugin/PluginManager.java +++ b/connector/src/main/java/org/geysermc/connector/plugin/PluginManager.java @@ -29,26 +29,19 @@ package org.geysermc.connector.plugin; import lombok.Getter; import lombok.ToString; import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.plugin.annotations.Event; -import org.geysermc.connector.plugin.api.CancellableGeyserEvent; -import org.geysermc.connector.plugin.api.GeyserEvent; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.nio.file.Path; import java.util.ArrayList; -import java.util.Comparator; -import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.PriorityQueue; +import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; /** - * Handles 3rd party plugins for Geyser + * Handles 3rd party plugins for Geyser and will hook into our Event system using annotations */ @Getter @ToString @@ -57,9 +50,8 @@ public class PluginManager { private final GeyserConnector connector; private final File pluginPath; - private final Map> pluginClasses = new ConcurrentHashMap<>(); - private final List loaders = new ArrayList<>(); - private final Map, PriorityQueue> eventHandlers = new HashMap<>(); + private final Map> globalClasses = new ConcurrentHashMap<>(); + private final List plugins = new ArrayList<>(); public PluginManager(GeyserConnector connector, File pluginPath) { this.connector = connector; @@ -70,15 +62,16 @@ public class PluginManager { /** * Load all plugins in the defined pluginPath */ + @SuppressWarnings("ResultOfMethodCallIgnored") public void loadPlugins() { pluginPath.mkdirs(); - for (File entry : pluginPath.listFiles()) { + for (File entry : Objects.requireNonNull(pluginPath.listFiles())) { if (entry.isDirectory() || !entry.getName().toLowerCase().endsWith(".jar")) { continue; } try { loadPlugin(entry); - } catch (IOException | PluginClassLoader.InvalidPluginClassLoaderException e) { + } catch (IOException | PluginManagerException e) { e.printStackTrace(); } } @@ -87,139 +80,40 @@ public class PluginManager { /** * Load a specific plugin and register its events */ - private void loadPlugin(File pluginFile) throws IOException, PluginClassLoader.InvalidPluginClassLoaderException { + private void loadPlugin(File pluginFile) throws IOException, PluginManagerException { if (!pluginFile.exists()) { throw new FileNotFoundException(String.format("%s does not exist", pluginFile.getName())); } - PluginClassLoader loader = new PluginClassLoader(this, getClass().getClassLoader(), pluginFile); - registerEvents(loader.getPlugin(), loader.getPlugin()); - loaders.add(loader); + + PluginClassLoader loader = null; + try { + loader = new PluginClassLoader(this, getClass().getClassLoader(), pluginFile); + } catch (PluginClassLoader.InvalidPluginClassLoaderException e) { + throw new PluginManagerException(e.getMessage(), e); + } + + GeyserPlugin plugin; + + try { + plugin = loader.getPluginClass().getConstructor(PluginManager.class, PluginClassLoader.class).newInstance(this, loader); + } catch (InstantiationException | NoSuchMethodException | InvocationTargetException | IllegalAccessException e) { + throw new PluginManagerException(e.getMessage(), e); + } + + plugins.add(plugin); + plugin.registerEvents(plugin); } - /** - * Register all Events contained in class - */ - public void registerEvents(Object plugin, Object cls) { - for (Method method : cls.getClass().getMethods()) { - Event eventAnnotation = method.getAnnotation(Event.class); + public static class PluginManagerException extends Exception { - // Check that the method is annotated with @Event - if (eventAnnotation == null) { - continue; - } - - // Make sure it only has a single Event parameter - if (method.getParameterCount() != 1 || !GeyserEvent.class.isAssignableFrom(method.getParameters()[0].getType())) { - continue; - } - - if (!eventHandlers.containsKey(method.getParameters()[0].getType())) { - eventHandlers.put(method.getParameters()[0].getType(), new PriorityQueue<>()); - } - - eventHandlers.get(method.getParameters()[0].getType()).add( - new EventHandler( - plugin, - cls, - method, - eventAnnotation - ) - ); - } - } - - /** - * Unregister events in class - */ - public void unregisterEvents(Class cls) { - for (Map.Entry, PriorityQueue> entry : eventHandlers.entrySet()) { - List remove = new ArrayList<>(); - for (EventHandler handler : entry.getValue()) { - if (handler.cls == cls) { - remove.add(handler); - } - } - entry.getValue().removeAll(remove); - } - } - - /** - * Unregister all events for a plugin - */ - public void unregisterPluginEvents(Class plugin) { - for (Map.Entry, PriorityQueue> entry : eventHandlers.entrySet()) { - List remove = new ArrayList<>(); - for (EventHandler handler : entry.getValue()) { - if (handler.plugin == plugin) { - remove.add(handler); - } - } - entry.getValue().removeAll(remove); - } - } - - /** - * Trigger a new event - * - * This will be executed with all registered handlers in order of priority - */ - public void triggerEvent(GeyserEvent event) { - if (!eventHandlers.containsKey(event.getClass())) { - return; + public PluginManagerException(String message, Throwable ex) { + super(message, ex); } - for (EventHandler handler : eventHandlers.get(event.getClass())) { - try { - handler.method.invoke(handler.cls, event); - } catch (IllegalAccessException | InvocationTargetException e) { - e.printStackTrace(); - } - } - } + public PluginManagerException(Throwable ex) { - /** - * Trigger a new cancellable event - * - * This will be executed with all registered handlers in order of priority ignoring those who - * do not wish to process events that are cancelled - */ - public void triggerEvent(CancellableGeyserEvent event) { - if (!eventHandlers.containsKey(event.getClass())) { - return; } - boolean cancelled = event.isCancelled(); - for (EventHandler handler : eventHandlers.get(event.getClass())) { - if (cancelled && handler.annotation.ignoreCancelled()) { - continue; - } - - try { - handler.method.invoke(handler.cls, event); - } catch (IllegalAccessException | InvocationTargetException e) { - e.printStackTrace(); - } - cancelled = event.isCancelled(); - } - } - - static class EventHandler implements Comparator { - Object plugin; - Object cls; - Method method; - Event annotation; - - EventHandler(Object plugin, Object cls, Method method, Event annotation) { - this.plugin = plugin; - this.cls = cls; - this.method = method; - this.annotation = annotation; - } - - @Override - public int compare(EventHandler left, EventHandler right) { - return left.annotation.priority() - right.annotation.priority(); - } } } diff --git a/connector/src/main/java/org/geysermc/connector/plugin/annotations/Event.java b/connector/src/main/java/org/geysermc/connector/plugin/annotations/Event.java index b024fc184..442b88ad0 100644 --- a/connector/src/main/java/org/geysermc/connector/plugin/annotations/Event.java +++ b/connector/src/main/java/org/geysermc/connector/plugin/annotations/Event.java @@ -26,7 +26,7 @@ package org.geysermc.connector.plugin.annotations; -import lombok.Getter; +import org.geysermc.connector.event.EventHandler; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -43,16 +43,8 @@ import java.lang.annotation.Target; @Target({ElementType.METHOD}) public @interface Event { // Events are processed from lowest to highest priority - int priority() default 50; + int priority() default EventHandler.PRIORITY.NORMAL; // If ignoreCancelled is true then the handler will not be executed boolean ignoreCancelled() default false; - - class PRIORITY { - public static int LOWEST = 10; - public static int LOW = 40; - public static int NORMAL = 50; - public static int HIGH = 60; - public static int HIGHEST = 90; - } }