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

171 lines
6.2 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.Iterator;
import java.util.List;
import java.util.logging.Level;
import fr.neatmonster.nocheatplus.components.registry.order.IGetRegistrationOrder;
import fr.neatmonster.nocheatplus.components.registry.order.RegistrationOrder;
import fr.neatmonster.nocheatplus.components.registry.order.RegistrationOrder.AbstractRegistrationOrderSort;
import fr.neatmonster.nocheatplus.logging.StaticLog;
import fr.neatmonster.nocheatplus.logging.Streams;
import fr.neatmonster.nocheatplus.utilities.StringUtil;
/**
* Hold MiniListener instances - sort by RegistrationOrder - allow thread-safe
* reading for actual event handling.
*
* @author asofold
*
* @param <E>
* @param <P>
*/
public class MiniListenerNode<E, P> {
// TODO: Pass lock and SortedItemStore(Node), lazily (?) get.
/**
* This is intended to be "complete" in terms of containing all information
* for order, extra properties like ignoreCancelled.
*
* @author asofold
*
* @param <E>
*/
protected static class ListenerEntry<E> implements IGetRegistrationOrder {
public final MiniListener<E> listener;
public final boolean ignoreCancelled;
private final RegistrationOrder order;
public ListenerEntry(MiniListener<E> listener, boolean ignoreCancelled, RegistrationOrder order) {
this.listener = listener;
this.ignoreCancelled = ignoreCancelled;
this.order = order == null ? new RegistrationOrder() : new RegistrationOrder(order);
}
@Override
public RegistrationOrder getRegistrationOrder() {
return order;
}
}
protected static final class SortListenerEntry<E> extends AbstractRegistrationOrderSort<ListenerEntry<E>> {
@Override
protected RegistrationOrder fetchRegistrationOrder(ListenerEntry<E> item) {
return item.getRegistrationOrder();
}
};
protected final SortListenerEntry<E> typedSort = new SortListenerEntry<E>();
protected final List<ListenerEntry<E>> registeredListeners = new ArrayList<ListenerEntry<E>>();
@SuppressWarnings("unchecked")
protected ListenerEntry<E>[] sortedListeners = new ListenerEntry[0];
/**
* Stored for the case of exceptions.
*/
protected final P basePriority;
public MiniListenerNode(P basePriority) {
this.basePriority = basePriority;
}
/**
* Remove all listeners.
*/
@SuppressWarnings("unchecked")
public void clear() {
registeredListeners.clear();
sortedListeners = new ListenerEntry[0];
}
public void removeMiniListener(MiniListener<E> listener) {
boolean changed = false;
final Iterator<ListenerEntry<E>> it = registeredListeners.iterator();
while (it.hasNext()) {
if (it.next().listener == listener) {
it.remove();
changed = true;
break; // TODO: Ensure register once or remove all or what not.
}
}
if (changed) {
generateSortedListeners();
}
}
public void addMiniListener(MiniListener<E> listener, boolean ignoreCancelled, RegistrationOrder order) {
registeredListeners.add(new ListenerEntry<E>(listener, ignoreCancelled, order));
generateSortedListeners();
}
protected void generateSortedListeners() {
// TODO: Allow postponing sorting.
// TODO: Store an optimally ordered thing? (Now entries in registeredListeners are within order of registration.)
if (registeredListeners.isEmpty()) {
clear();
}
else {
final Object[] sortedOdd = typedSort.getSortedArray(registeredListeners);
@SuppressWarnings("unchecked")
final ListenerEntry<E>[] sortedListeners = new ListenerEntry[sortedOdd.length];
System.arraycopy(sortedOdd, 0, sortedListeners, 0, sortedOdd.length);
this.sortedListeners = sortedListeners;
}
}
/**
* Override to implement events that can be cancelled.
*
* @param event
*/
protected boolean isCancelled(E event) {
return false;
}
public void onEvent(final E event) {
// Go through mini listeners....
// Note that cancelled events get in here too.
final ListenerEntry<E>[] listeners = this.sortedListeners; // Allow concurrent update.
for (int i = 0; i < listeners.length; i++) {
final ListenerEntry<E> entry = listeners[i];
if (!entry.ignoreCancelled || !isCancelled(event)) {
try {
entry.listener.onEvent(event);
}
catch (Throwable t) {
// TODO: More fine grained catch.
logListenerException(entry, i, listeners.length, event, t);
}
}
}
}
private void logListenerException(final ListenerEntry<E> entry, final int index, final int length, final E event, final Throwable t) {
// Log long part once, to keep spam slightly down.
StaticLog.logOnce(Streams.STATUS, Level.SEVERE,
"Listener exception: event=" + event.getClass().getName() + " priority=" + this.basePriority + " index=" + index + " n=" + length
// TODO: Add id information to compare to registry log (later).
, StringUtil.throwableToString(t));
}
}