/* * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package fr.neatmonster.nocheatplus.event.mini; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import fr.neatmonster.nocheatplus.components.registry.order.IGetRegistrationOrder; import fr.neatmonster.nocheatplus.components.registry.order.RegistrationOrder; import fr.neatmonster.nocheatplus.components.registry.order.RegistrationOrder.RegisterEventsWithOrder; import fr.neatmonster.nocheatplus.components.registry.order.RegistrationOrder.RegisterWithOrder; /** * * One registry for MiniListener instances.
* *
*
* Supports registration of MiniListener instances with order (FCFS): * * (Method annotations are not supported for the MiniListener class.)
*
* * @param * Event base type, e.g. Event (Bukkit). * @param

* Priority type of the underlying event system, e.g. EventPriority * (Bukkit). * * @author asofold * */ public abstract class MiniListenerRegistry { public static interface NodeFactory { public MiniListenerNode newNode(Class eventClass, P basePriority); } /////////////// // Instance. /////////////// /** * Override for efficient stuff. */ protected NodeFactory nodeFactory = new NodeFactory() { @Override public MiniListenerNode newNode(Class eventClass, P basePriority) { return new MiniListenerNode(basePriority); } }; /** * Map event class -> base priority -> node. Note that this does no merging * based on super-classes like the Bukkit implementation of the Listener * registry would do. */ protected final Map, Map>> classMap = new HashMap, Map>>(); /** * Store attached MiniListener instances by anchor objects. */ protected final Map>> attachments = new HashMap>>(); public void attach(MiniListener[] listeners, Object anchor) { attach(Arrays.asList(listeners), anchor); } public void attach(Collection> listeners, Object anchor) { for (MiniListener listener : listeners) { attach(listener, anchor); } } /** * "Attach" a listener to an object, such that the listener is removed if * removeAttachment is called.
* Note that removing a MiniListener will also remove the attachment. * * @param listener * @param anchor */ public void attach(MiniListener listener, Object anchor) { if (listener == null) { throw new NullPointerException("Must not be null: listener"); } else if (anchor == null) { throw new NullPointerException("Must not be null: anchor"); } else if (anchor.equals(listener)) { throw new IllegalArgumentException("Must not be equal: listener and anchor"); } Set> attached = attachments.get(anchor); if (attached == null) { attached = new HashSet>(); attachments.put(anchor, attached); } attached.add(listener); } /** * Convenience method, e.g. for use with Listener registration and plugins * to remove all attachments on plugin-disable. * * @param registeredAnchor * @param otherAnchor */ public void inheritAttached(Object registeredAnchor, Object otherAnchor) { // TODO: More signatures (Collection/Array). if (registeredAnchor == null) { throw new NullPointerException("Must not be null: registeredAnchor"); } else if (otherAnchor == null) { throw new NullPointerException("Must not be null: newAnchor"); } if (registeredAnchor.equals(otherAnchor)) { throw new IllegalArgumentException("Must not be equal: registeredAnchor and newAnchor"); } Set> attached = attachments.get(registeredAnchor); if (attached == null) { // TODO: throw something or return value or ignore? } else { Set> attached2 = attachments.get(otherAnchor); if (attached2 == null) { attached2 = new HashSet>(); attachments.put(otherAnchor, attached2); } attached2.addAll(attached); } } /** * Unregister all attached MiniListener instances for a given anchor. * * @param anchor */ public void unregisterAttached(Object anchor) { // TODO: Consider more signatures for Collection + Array. Set> attached = attachments.get(anchor); if (attached != null) { for (MiniListener listener : new ArrayList>(attached)) { unregister(listener); } } } @SuppressWarnings("unchecked") public void unregister(MiniListener listener) { // TODO: Consider allowing to pinpoint by priority? /* * Somewhat inefficient, as all attachments and all priority levels are checked, * this might/should be improved by adding extra mappings (consider check class by reflection). */ // Remove listener registrations. for (Map> prioMap : classMap.values()) { for (MiniListenerNode node : prioMap.values()) { try { ((MiniListenerNode) node).removeMiniListener(listener); } catch (ClassCastException e) { // TODO: Log ? } } } // Remove attachment references. Iterator>>> it = attachments.entrySet().iterator(); while (it.hasNext()) { Entry>> entry = it.next(); Set> attached = entry.getValue(); attached.remove(listener); // TODO: can throw? if (attached.isEmpty()) { it.remove(); } } } /** * Clear all listeners and attachments. The events stay registered in the * underlying event system (TBD if not: unregister). */ public void clear() { attachments.clear(); for (Map> prioMap : classMap.values()) { for (MiniListenerNode node : prioMap.values()) { node.clear(); } } } /** * Register a MiniListener instance for the given event class and base * priority. Further ignoreCancelled controls if cancelled events are * ignored and it's possible to influence the order of processing for * registered listeners.
*
* Supports registration of MiniListener instances with order (FCFS): *

* (Method annotations are not supported for the MiniListener class.)
*
* * @param eventClass * @param listener * @param basePriority * Priority for the underlying event system. * @param defaultOrder * Default registration order (secondary priority). This comes * last comparing to supported types of order (annotations, * interfaces), assuming the default order to be more general, * e.g. originating from a MultiListenerRegistry. * @param ignoreCancelled */ public void register(Class eventClass, MiniListener listener, P basePriority, RegistrationOrder defaultOrder, boolean ignoreCancelled) { // TODO: Can/should the eventClass be read from listener parameters [means constraints on MiniListener?] ? RegistrationOrder order = null; if (listener instanceof IGetRegistrationOrder) { order = ((IGetRegistrationOrder) listener).getRegistrationOrder(); } if (order == null) { Class listenerClass = listener.getClass(); if (listenerClass.isAnnotationPresent(RegisterEventsWithOrder.class)) { order = new RegistrationOrder(listenerClass.getAnnotation(RegisterEventsWithOrder.class)); } else if (listenerClass.isAnnotationPresent(RegisterWithOrder.class)) { order = new RegistrationOrder(listenerClass.getAnnotation(RegisterWithOrder.class)); } else { order = defaultOrder; } } // TODO: Accept RegisterEventsWithOrder (and RegisterWithOrder) with listener. // TODO: Accept IRegisterWithOrder with listener. Map> prioMap = classMap.get(eventClass); if (prioMap == null) { prioMap = new HashMap>(); classMap.put(eventClass, prioMap); } // TODO: Concept for when to cast. @SuppressWarnings("unchecked") MiniListenerNode node = (MiniListenerNode) prioMap.get(basePriority); if (node == null) { node = nodeFactory.newNode(eventClass, basePriority); // TODO: Consider try-catch. registerNode(eventClass, node, basePriority); prioMap.put(basePriority, node); } node.addMiniListener(listener, ignoreCancelled, order); } /** * Register a MiniListenerNode instance with the underlying event-system * (unique nodes are ensured in register(...)).
* Note that the node is put to the internals map after this has been * called, to be able to recover from errors. * * @param eventClass * @param node * @param basePriority */ protected abstract void registerNode(Class eventClass, MiniListenerNode node, P basePriority); }