/* * This file is part of LanternServer, licensed under the MIT License (MIT). * * Copyright (c) LanternPowered <https://www.lanternpowered.org> * Copyright (c) SpongePowered <https://www.spongepowered.org> * Copyright (c) contributors * * 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. */ package org.lanternpowered.server.event; import static com.google.common.base.Preconditions.checkNotNull; import static org.lanternpowered.server.util.Conditions.checkPlugin; import com.github.benmanes.caffeine.cache.Caffeine; import com.github.benmanes.caffeine.cache.LoadingCache; import com.google.common.collect.HashMultimap; import com.google.common.collect.Lists; import com.google.common.collect.Multimap; import com.google.common.collect.Sets; import com.google.common.reflect.TypeToken; import org.lanternpowered.server.event.filter.FilterFactory; import org.lanternpowered.server.event.gen.DefineableClassLoader; import org.lanternpowered.server.game.Lantern; import org.spongepowered.api.event.Cancellable; import org.spongepowered.api.event.Event; import org.spongepowered.api.event.EventListener; import org.spongepowered.api.event.EventManager; import org.spongepowered.api.event.Listener; import org.spongepowered.api.event.Order; import org.spongepowered.api.plugin.PluginContainer; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.function.Predicate; public class LanternEventManager implements EventManager { private final Object lock = new Object(); private final DefineableClassLoader classLoader = new DefineableClassLoader(this.getClass().getClassLoader()); private final AnnotatedEventListener.Factory listenerFactory = new ClassEventListenerFactory("org.lanternpowered.server.event.listener", new FilterFactory("org.lanternpowered.server.event.filters", this.classLoader), this.classLoader); private final Multimap<Class<?>, RegisteredListener<?>> listenersByEvent = HashMultimap.create(); /** * A cache of all the handlers for an event type for quick event posting. */ private final LoadingCache<Class<? extends Event>, List<RegisteredListener<?>>> listenersCache = Caffeine.newBuilder().build(this::bakeHandlers); @SuppressWarnings({ "unchecked", "rawtypes" }) private List<RegisteredListener<?>> bakeHandlers(Class<?> rootEvent) { final List<RegisteredListener<?>> handlers = Lists.newArrayList(); final Set<Class<?>> types = (Set) TypeToken.of(rootEvent).getTypes().rawTypes(); synchronized (this.lock) { types.stream().filter(Event.class::isAssignableFrom).forEach(type -> handlers.addAll(this.listenersByEvent.get(type))); } Collections.sort(handlers); return handlers; } private static boolean isValidHandler(Method method) { final int modifiers = method.getModifiers(); if (Modifier.isStatic(modifiers) || !Modifier.isPublic(modifiers) || Modifier.isAbstract(modifiers) || method.getDeclaringClass().isInterface() || method.getReturnType() != void.class) { return false; } final Class<?>[] parameters = method.getParameterTypes(); return parameters.length >= 1 && Event.class.isAssignableFrom(parameters[0]); } private void register(RegisteredListener<?> listener) { this.register(Collections.singletonList(listener)); } @SuppressWarnings({ "rawtypes", "unchecked" }) private void register(List<RegisteredListener<?>> listeners) { synchronized (this.lock) { final Set<Class<?>> types = Sets.newHashSet(); for (RegisteredListener listener : listeners) { if (this.listenersByEvent.put(listener.getEventClass(), listener)) { types.addAll(TypeToken.of(listener.getEventClass()).getTypes().rawTypes()); } } if (!types.isEmpty()) { this.listenersCache.invalidateAll(types); } } } @SuppressWarnings("unchecked") public void register(PluginContainer plugin, Object listener) { checkNotNull(plugin, "plugin"); checkNotNull(listener, "listener"); final List<RegisteredListener<?>> handlers = new ArrayList<>(); final Class<?> handle = listener.getClass(); for (Method method : handle.getMethods()) { final Listener subscribe = method.getAnnotation(Listener.class); if (subscribe != null) { if (isValidHandler(method)) { final Class<? extends Event> eventClass = (Class<? extends Event>) method.getParameterTypes()[0]; final AnnotatedEventListener handler; try { handler = this.listenerFactory.create(listener, method); } catch (Exception e) { Lantern.getLogger().error("Failed to create listener for {} on {}", method, handle, e); continue; } handlers.add(createRegistration(plugin, eventClass, subscribe, handler)); } else { Lantern.getLogger().warn("The method {} on {} has @{} but has the wrong signature", method, handle.getName(), Listener.class.getName()); } } } this.register(handlers); } private static <T extends Event> RegisteredListener<T> createRegistration(PluginContainer plugin, Class<T> eventClass, Listener subscribe, EventListener<? super T> listener) { return createRegistration(plugin, eventClass, subscribe.order(), listener); } private static <T extends Event> RegisteredListener<T> createRegistration(PluginContainer plugin, Class<T> eventClass, Order order, EventListener<? super T> listener) { return new RegisteredListener<>(plugin, eventClass, order, listener); } @Override public void registerListeners(Object plugin, Object listener) { register(checkPlugin(plugin, "plugin"), checkNotNull(listener, "listener")); } @Override public <T extends Event> void registerListener(Object plugin, Class<T> eventClass, Order order, boolean beforeModifications, EventListener<? super T> listener) { // Ignore the "beforeModifications" property, this is only used in combination with mods this.registerListener(plugin, eventClass, order, listener); } @Override public <T extends Event> void registerListener(Object plugin, Class<T> eventClass, EventListener<? super T> listener) { this.registerListener(plugin, eventClass, Order.DEFAULT, listener); } @Override public <T extends Event> void registerListener(Object plugin, Class<T> eventClass, Order order, EventListener<? super T> listener) { final PluginContainer container = checkPlugin(plugin, "plugin"); checkNotNull(eventClass, "eventClass"); checkNotNull(order, "order"); checkNotNull(listener, "listener"); this.register(createRegistration(container, eventClass, order, listener)); } private void unregister(Predicate<RegisteredListener<?>> unregister) { synchronized (this.lock) { final Set<Class<?>> types = new HashSet<>(); final Iterator<RegisteredListener<?>> it = this.listenersByEvent.values().iterator(); while (it.hasNext()) { final RegisteredListener<?> listener = it.next(); if (unregister.test(listener)) { types.addAll(TypeToken.of(listener.getEventClass()).getTypes().rawTypes()); it.remove(); } } if (!types.isEmpty()) { this.listenersCache.invalidateAll(types); } } } @Override public void unregisterListeners(final Object listener) { checkNotNull(listener, "listener"); this.unregister(handler -> listener.equals(handler.getHandle())); } @Override public void unregisterPluginListeners(Object pluginObj) { final PluginContainer plugin = checkPlugin(pluginObj, "plugin"); this.unregister(handler -> plugin.equals(handler.getPlugin())); } @Override public boolean post(Event event) { checkNotNull(event, "event"); for (RegisteredListener listener : this.listenersCache.get(event.getClass())) { try { //noinspection unchecked listener.handle(event); } catch (Throwable e) { Lantern.getLogger().error("Could not pass {} to {}", event.getClass().getSimpleName(), listener.getPlugin(), e); } } return event instanceof Cancellable && ((Cancellable) event).isCancelled(); } }