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