NoCheatPlus/NCPCore/src/main/java/fr/neatmonster/nocheatplus/event/mini/MiniListenerRegistry.java

295 lines
12 KiB
Java
Raw Normal View History

2018-01-17 23:52:50 +01:00
/*
* 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 <http://www.gnu.org/licenses/>.
*/
[BLEEDING][BREAKING] Use a new internal event registry. The old ListenerManager is removed, new system in place. Removed doesManageListeners(). (The new system isn't that new, but it's been fixed and adapted to using RegistrationOrder.) New * Register all Bukkit events via the new EventRegistryBukkit. * This way listeners can be ordered by numeric priority and tags (regular expressions for beforeTag and afterTag). * Unregistering listeners is possible (a listener node stays in the Bukkit registry, but only one per event). * It's possible to add listeners with minimal impact (MiniListener). * The registry registers by event class 'hard' i.e., no relations between already registered classes are checked. * Order isn't necessarily stable nor even reproducible for randomized start conditions with the same elements. Point * Compatibility hooks can easily place event listeners before/after/between NCP default listeners, without resorting to tricks like 'load-before'. * Future registry of NCP itself: unregistering listeners is necessary for runtime-removable checks, order is useful if not necessary, to be able to add check listeners at any point of time. Breaks: * Anything relying on the previous (optional) managelisteners feature. Missing: * Lots of testing/inspection. * Ensure all NCP listeners are coming with name/tag at least. * Provide meaningful tags/RegistrationOrder for fine grained access (e.g. after feature.moving but before feature.inventory). * Change cncp to hard depend on NCP and use listener priorities.
2018-01-16 22:19:18 +01:00
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.<br>
*
* <br>
* <br>
* Supports registration of MiniListener instances with order (FCFS):
* <ul>
* <li>Implement the interface
* {@link fr.neatmonster.nocheatplus.components.registry.order.IGetRegistrationOrder}</li>
* <li>Annotation
* {@link fr.neatmonster.nocheatplus.components.registry.order.RegistrationOrder.RegisterEventsWithOrder}</li>
* <li>Annotation
* {@link fr.neatmonster.nocheatplus.components.registry.order.RegistrationOrder.RegisterWithOrder}</li>
* <li>the given defaultOrder</li>
* </ul>
* (Method annotations are not supported for the MiniListener class.) <br>
* <hr>
*
* @param <EB>
* Event base type, e.g. Event (Bukkit).
* @param <P>
* Priority type of the underlying event system, e.g. EventPriority
* (Bukkit).
*
* @author asofold
*
*/
public abstract class MiniListenerRegistry<EB, P> {
public static interface NodeFactory<EB, P> {
public <E extends EB> MiniListenerNode<E, P> newNode(Class<E> eventClass, P basePriority);
}
///////////////
// Instance.
///////////////
/**
* Override for efficient stuff.
*/
protected NodeFactory<EB, P> nodeFactory = new NodeFactory<EB, P>() {
@Override
public <E extends EB> MiniListenerNode<E, P> newNode(Class<E> eventClass, P basePriority) {
return new MiniListenerNode<E, P>(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<Class<? extends EB>, Map<P, MiniListenerNode<? extends EB, P>>> classMap = new HashMap<Class<? extends EB>, Map<P, MiniListenerNode<? extends EB, P>>>();
/**
* Store attached MiniListener instances by anchor objects.
*/
protected final Map<Object, Set<MiniListener<? extends EB>>> attachments = new HashMap<Object, Set<MiniListener<? extends EB>>>();
public void attach(MiniListener<? extends EB>[] listeners, Object anchor) {
attach(Arrays.asList(listeners), anchor);
}
public void attach(Collection<MiniListener<? extends EB>> listeners, Object anchor) {
for (MiniListener<? extends EB> listener : listeners) {
attach(listener, anchor);
}
}
/**
* "Attach" a listener to an object, such that the listener is removed if
* removeAttachment is called.<br>
* Note that removing a MiniListener will also remove the attachment.
*
* @param listener
* @param anchor
*/
public <E extends EB> void attach(MiniListener<E> 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<MiniListener<? extends EB>> attached = attachments.get(anchor);
if (attached == null) {
attached = new HashSet<MiniListener<? extends EB>>();
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<MiniListener<? extends EB>> attached = attachments.get(registeredAnchor);
if (attached == null) {
// TODO: throw something or return value or ignore?
} else {
Set<MiniListener<? extends EB>> attached2 = attachments.get(otherAnchor);
if (attached2 == null) {
attached2 = new HashSet<MiniListener<? extends EB>>();
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<MiniListener<? extends EB>> attached = attachments.get(anchor);
if (attached != null) {
for (MiniListener<? extends EB> listener : new ArrayList<MiniListener<? extends EB>>(attached)) {
unregister(listener);
}
}
}
@SuppressWarnings("unchecked")
public <E extends EB> void unregister(MiniListener<E> 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<P, MiniListenerNode<? extends EB, P>> prioMap : classMap.values()) {
for (MiniListenerNode<? extends EB, P> node : prioMap.values()) {
try {
((MiniListenerNode<E, P>) node).removeMiniListener(listener);
} catch (ClassCastException e) {
// TODO: Log ?
}
}
}
// Remove attachment references.
Iterator<Entry<Object, Set<MiniListener<? extends EB>>>> it = attachments.entrySet().iterator();
while (it.hasNext()) {
Entry<Object, Set<MiniListener<? extends EB>>> entry = it.next();
Set<MiniListener<? extends EB>> 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<P, MiniListenerNode<? extends EB, P>> prioMap : classMap.values()) {
for (MiniListenerNode<? extends EB, P> 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.<br>
* <br>
* Supports registration of MiniListener instances with order (FCFS):
* <ul>
* <li>Implement the interface
* {@link fr.neatmonster.nocheatplus.components.registry.order.IGetRegistrationOrder}</li>
* <li>Annotation
* {@link fr.neatmonster.nocheatplus.components.registry.order.RegistrationOrder.RegisterEventsWithOrder}</li>
* <li>Annotation
* {@link fr.neatmonster.nocheatplus.components.registry.order.RegistrationOrder.RegisterWithOrder}</li>
* <li>the given defaultOrder</li>
* </ul>
* (Method annotations are not supported for the MiniListener class.) <br>
* <hr>
*
* @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 <E extends EB> void register(Class<E> eventClass, MiniListener<E> 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<P, MiniListenerNode<? extends EB, P>> prioMap = classMap.get(eventClass);
if (prioMap == null) {
prioMap = new HashMap<P, MiniListenerNode<? extends EB, P>>();
classMap.put(eventClass, prioMap);
}
// TODO: Concept for when to cast.
@SuppressWarnings("unchecked")
MiniListenerNode<E, P> node = (MiniListenerNode<E, P>) 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(...)). <br>
* 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 <E extends EB> void registerNode(Class<E> eventClass,
MiniListenerNode<E, P> node, P basePriority);
}