[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.
This commit is contained in:
asofold 2018-01-16 22:19:18 +01:00
parent f7571dcf2f
commit 37127c1f2b
15 changed files with 936 additions and 591 deletions

View File

@ -22,6 +22,7 @@ import fr.neatmonster.nocheatplus.compat.blocks.changetracker.BlockChangeTracker
import fr.neatmonster.nocheatplus.components.registry.ComponentRegistry;
import fr.neatmonster.nocheatplus.components.registry.ComponentRegistryProvider;
import fr.neatmonster.nocheatplus.components.registry.GenericInstanceRegistry;
import fr.neatmonster.nocheatplus.event.mini.EventRegistryBukkit;
import fr.neatmonster.nocheatplus.logging.LogManager;
@ -41,13 +42,20 @@ import fr.neatmonster.nocheatplus.logging.LogManager;
* the plugin is enabled, all components will be unregistered in onDisable.</li>
* <li>References to all components will be held until onDisable is
* finished.</li>
* <li>Interfaces checked for managed listeners: IHaveMethodOrder (method),
* ComponentWithName (tag)</li>
* <li>Event registration and unregistering via passing a
* {@link org.bukkit.event.Listener} is possible, see
* {@link fr.neatmonster.nocheatplus.event.mini.EventRegistryBukkit} and
* {@link fr.neatmonster.nocheatplus.event.mini.MiniListenerRegistry} for
* details on how to apply
* {@link fr.neatmonster.nocheatplus.components.registry.order.RegistrationOrder}
* for controlling the order of event processing and further options. Fetch the
* event registry via {@link #getEventRegistry()}</li>
* <hr>
* Not sure about all the login-denial API, some of those might get removed.
* <hr>
* NOTE: Class names for implementations of the NoCheatPlusAPI which aim at unit
* tests, where server access might not work, should start with "UnitTest".
* <hr>
*
* @author asofold
*
@ -166,4 +174,19 @@ public interface NoCheatPlusAPI extends ComponentRegistry<Object>, ComponentRegi
*/
public BlockChangeTracker getBlockChangeTracker();
/**
* Get the registry to register events with the
* {@link org.bukkit.plugin.PluginManager}, e.g. for the case that
* {@link #addComponent(Object)} and similar is not sufficient/appropriate.
* <br>
* <br>
* For details see
* {@link fr.neatmonster.nocheatplus.event.mini.EventRegistryBukkit} and
* {@link fr.neatmonster.nocheatplus.event.mini.MiniListenerRegistry}.
* <hr>
*
* @return
*/
public EventRegistryBukkit getEventRegistry();
}

View File

@ -743,7 +743,6 @@ public abstract class ConfPaths {
// Compatibility section (possibly temporary).
@GlobalConfig
public static final String COMPATIBILITY = "compatibility.";
public static final String COMPATIBILITY_MANAGELISTENERS = COMPATIBILITY + "managelisteners";
private static final String COMPATIBILITY_EXEMPTIONS = COMPATIBILITY + "exemptions.";
private static final String COMPATIBILITY_EXEMPTIONS_REMOVE = COMPATIBILITY_EXEMPTIONS + "remove.";
@ -797,8 +796,6 @@ public abstract class ConfPaths {
public static final String PROTECT_PLUGINS_HIDE_MSG_NOPERMISSION = "protection.plugins.hide.messages.nopermission";
@Moved(newPath = PROTECT_COMMANDS_CONSOLEONLY_ACTIVE)
public static final String MISCELLANEOUS_OPINCONSOLEONLY = "miscellaneous.opinconsoleonly";
@Moved(newPath = COMPATIBILITY_MANAGELISTENERS)
public static final String MISCELLANEOUS_MANAGELISTENERS = "miscellaneous.managelisteners";
@Moved(newPath = INVENTORY_OPEN_CHECK)
public static final String INVENTORY_ENSURECLOSE = "checks.inventory.ensureclose";
@Moved(newPath = LOGGING_EXTENDED_STATUS)
@ -862,6 +859,10 @@ public abstract class ConfPaths {
public static final String MOVING_PASSABLE_RAYTRACING_CHECK = "checks.moving.passable.raytracing.active";
@Deprecated
public static final String MOVING_PASSABLE_RAYTRACING_BLOCKCHANGEONLY = "checks.moving.passable.raytracing.blockchangeonly";
@Deprecated
public static final String MISCELLANEOUS_MANAGELISTENERS = "miscellaneous.managelisteners";
@Deprecated
public static final String COMPATIBILITY_MANAGELISTENERS = "compatibility.managelisteners";
/**
* Get moved paths for which an annotation doesn't work.

View File

@ -606,7 +606,6 @@ public class DefaultConfig extends ConfigFile {
set(ConfPaths.STRINGS + ".vehicleenvelope", start + "moved a vehicle too fast ([tags])" + end, 785);
// Compatibility settings.
set(ConfPaths.COMPATIBILITY_MANAGELISTENERS, false, 785);
set(ConfPaths.COMPATIBILITY_EXEMPTIONS_WILDCARD_DEFAULT_METADATA_ACTIVE, true, 785);
set(ConfPaths.COMPATIBILITY_EXEMPTIONS_WILDCARD_DEFAULT_METADATA_KEYS, Arrays.asList("nocheat.exempt"), 785);
set(ConfPaths.COMPATIBILITY_EXEMPTIONS_WILDCARD_NPC_ACTIVE, true, 785);

View File

@ -1,223 +0,0 @@
/*
* 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/>.
*/
package fr.neatmonster.nocheatplus.event;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.bukkit.Bukkit;
import org.bukkit.event.Cancellable;
import org.bukkit.event.Event;
import org.bukkit.event.EventException;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.plugin.EventExecutor;
import org.bukkit.plugin.Plugin;
import fr.neatmonster.nocheatplus.NCPAPIProvider;
import fr.neatmonster.nocheatplus.logging.Streams;
/**
* listener registered for one event only. Allows to delegate to other registered listeners.
* @author mc_dev
*
* @param <E>
*/
public class GenericListener<E extends Event> implements Listener, EventExecutor {
public static class MethodEntry{
/**
* beforeTag overrides afterTag
* @author mc_dev
*
*/
public static class MethodOrder{
public static final MethodOrder getMethodOrder(fr.neatmonster.nocheatplus.event.MethodOrder anno){
return anno.beforeTag().isEmpty() ? null : new MethodOrder(anno.beforeTag());
}
public final String beforeTag;
/**
* You can use regular expressions for beforeTag, so it will be checked from position 0 on.<br>
* You can specify "*" for "all other tags" to add it in front.
* @param beforeTag
*/
public MethodOrder(String beforeTag){
this.beforeTag = beforeTag;
}
}
// One method
// TODO
public final Object listener;
public final Method method;
public final boolean ignoreCancelled;
/**
* This allows setting some information about where this listener comes from,
* also allowing to register before listeners with other tags. The default plugin should be null.
*/
public final String tag;
public final MethodOrder order;
public MethodEntry(Object listener, Method method, boolean ignoreCancelled, String tag, MethodOrder order){
this.listener = listener;
this.method = method;
this.ignoreCancelled = ignoreCancelled;
this.tag = tag;
this.order = order;
}
}
////////////////
// Instance
////////////////
protected final Class<E> clazz;
protected MethodEntry[] entries = new MethodEntry[0];
/** If the event type implements Cancellable, is set once only, null means unknsown (TODO) */
protected final boolean isCancellable;
/** Event priority, for debugging purposes. Note that the factories seem to have to use another variable name. */
protected final EventPriority priority;
private boolean registered = false;
public GenericListener(final Class<E> clazz, final EventPriority priority) {
this.clazz = clazz;
this.priority = priority;
isCancellable = clazz.isInstance(Cancellable.class);
}
@Override
public void execute(final Listener listener, final Event event){
if (!clazz.isAssignableFrom(event.getClass())){
// Strange but true.
return;
}
// TODO: profiling option !
final Cancellable cancellable = isCancellable ? (Cancellable) event : null;
final MethodEntry[] entries = this.entries;
for (int i = 0; i < entries.length ; i++){
final MethodEntry entry = entries[i];
try {
if (!isCancellable || !entry.ignoreCancelled || !cancellable.isCancelled()) entry.method.invoke(entry.listener, event);
} catch (Throwable t) {
// IllegalArgumentException IllegalAccessException InvocationTargetException
onError(entry, event, t);
}
}
}
private void onError(final MethodEntry entry, final Event event, final Throwable t) {
final String descr = "GenericListener<" + clazz.getName() +"> @" + priority +" encountered an exception for " + entry.listener.getClass().getName() + " with method " + entry.method.toGenericString();
NCPAPIProvider.getNoCheatPlusAPI().getLogManager().severe(Streams.SERVER_LOGGER, new EventException(t, descr));
}
public void register(Plugin plugin) {
if (registered) return;
Bukkit.getPluginManager().registerEvent(clazz, this, priority, this, plugin, false);
registered = true;
}
public boolean isRegistered() {
return registered;
}
public void addMethodEntry(final MethodEntry entry){
// TODO: maybe optimize later.
// MethodOrder: the new enties order will be compared versus the old entries tags, not the other way round !).
final MethodEntry[] entries = this.entries;
int insertion = -1;
if (entry.order != null){
if (entry.order.beforeTag != null){
if ("*".equals(entry.order.beforeTag)){
insertion = 0;
}
else{
for (int i = 0; i < entries.length; i++){
MethodEntry other = entries[i];
if (other.order != null){
if (other.tag.matches(entry.order.beforeTag)){
insertion = i;
break;
}
}
}
}
}
}
final MethodEntry[] newEntries;
if (insertion == entries.length || insertion == -1){
newEntries = Arrays.copyOf(entries, entries.length + 1);
newEntries[newEntries.length - 1] = entry;
}
else{
newEntries = new MethodEntry[entries.length + 1];
for (int i = 0; i < newEntries.length; i ++ ){
if (i < insertion) newEntries[i] = entries[i];
else if (i == insertion) newEntries[i] = entry;
else{
// i > insertion
newEntries[i] = entries[i - 1];
}
}
}
Arrays.fill(entries, null);
this.entries = newEntries;
}
/**
* TODO: more methods for tags ? (....return type ?)
* @param listener
*/
public void remove(Listener listener) {
final MethodEntry[] entries = this.entries;
final List<MethodEntry> keep = new ArrayList<MethodEntry>(entries.length);
for (MethodEntry entry : entries){
if (entry.listener != listener) keep.add(entry);
}
if (keep.size() != entries.length){
final MethodEntry[] newEntries = new MethodEntry[keep.size()];
keep.toArray(newEntries);
Arrays.fill(entries, null);
this.entries = newEntries;
}
}
/**
* Remove all MethodEntry references.
*/
public void clear() {
MethodEntry[] oldEntries = this.entries;
this.entries = new MethodEntry[0];
for (int i = 0; i < oldEntries.length; i++){
oldEntries[i] = null;
}
}
/**
* If any methods are actually registered.
* @return
*/
public boolean hasListenerMethods() {
return entries.length > 0;
}
}

View File

@ -1,30 +0,0 @@
/*
* 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/>.
*/
package fr.neatmonster.nocheatplus.event;
import fr.neatmonster.nocheatplus.event.GenericListener.MethodEntry.MethodOrder;
/**
* Implement to register Listeners via delegation that does not allow for passinf MethodOrder directly.
* @author mc_dev
*
*/
public interface IHaveMethodOrder {
/**
*
* @return
*/
public MethodOrder getMethodOrder();
}

View File

@ -1,246 +0,0 @@
/*
* 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/>.
*/
package fr.neatmonster.nocheatplus.event;
import java.lang.reflect.Method;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.bukkit.event.Event;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.plugin.Plugin;
import fr.neatmonster.nocheatplus.components.registry.feature.ComponentWithName;
import fr.neatmonster.nocheatplus.event.GenericListener.MethodEntry;
import fr.neatmonster.nocheatplus.event.GenericListener.MethodEntry.MethodOrder;
import fr.neatmonster.nocheatplus.logging.StaticLog;
/**
* This class allows to register event-listeners which will all be called form within one event handler per event+priority combination.<br>
* In future this will also allow to control registration order, to ensure receiving events before or after some other listeners or plugins.<br>
* @author mc_dev
*
*/
public class ListenerManager {
protected Map<Class<? extends Event>, EnumMap<EventPriority, GenericListener<?>>> map = new HashMap<Class<? extends Event>, EnumMap<EventPriority,GenericListener<?>>>();
private final Plugin plugin;
private boolean registerDirectly;
public ListenerManager(Plugin plugin){
this(plugin, false);
}
public ListenerManager(Plugin plugin, boolean registerDirectly){
this.plugin = plugin;
this.registerDirectly = true;
}
/**
* Probably put to protected later.<br>
* NOTE: Not thread-safe.
* @param clazz
* @param priority
* @return
*/
public <E extends Event> GenericListener<E> getListener(Class<E> clazz, EventPriority priority){
EnumMap<EventPriority, GenericListener<?>> prioMap = map.get(clazz);
if (prioMap == null){
prioMap = new EnumMap<EventPriority, GenericListener<?>>(EventPriority.class);
map.put(clazz, prioMap);
}
@SuppressWarnings("unchecked")
GenericListener<E> listener = (GenericListener<E>) prioMap.get(priority);
if (listener == null){
listener = new GenericListener<E>(clazz, priority);
prioMap.put(priority, listener);
}
if (registerDirectly && !listener.isRegistered()) listener.register(plugin);
return listener;
}
public Plugin getPlugin() {
return plugin;
}
public boolean isRegisterDirectly() {
return registerDirectly;
}
public void setRegisterDirectly(boolean registerDirectly) {
this.registerDirectly = registerDirectly;
}
/**
* Register all yet unregistered generic listeners with the PluginManager.<br>
* NOTE: This does not set registerDirectly.
*/
public void registerAllWithBukkit(){
for (final EnumMap<EventPriority, GenericListener<?>> prioMap : map.values()){
for (final GenericListener<?> listener : prioMap.values()){
if (!listener.isRegistered()) listener.register(plugin);
}
}
}
/**
* Clear internal mappings.
*/
public void clear(){
for (final Map<EventPriority, GenericListener<?>> prioMap : map.values()){
for (final GenericListener<?> listener : prioMap.values()){
listener.clear();
}
}
map.clear();
}
/**
* This registers all declared methods that have the @EventHandler annotation.<br>
* Interfaces checked if arguments are not given: IHaveMethodOrder (order), ComponentWithName (tag)<br>
* NOTE: Does not do any super class checking.
* @param listener
* @param tag Identifier for the registering plugin / agent, null is not discouraged, but null entries are ignored concerning sortin order.
*/
public void registerAllEventHandlers(Listener listener, String tag){
registerAllEventHandlers(listener, tag, null);
}
/**
* This registers all methods that have the @EventHandler annotation.<br>
* Interfaces checked if arguments are not given: IHaveMethodOrder (order), ComponentWithName (tag)<br>
* NOTES: <br>
* - Does not do any super class checking.<br>
* - Given MethodOrder overridden by implementing IHaveMethodOrder overridden by per method @MethodOrder annotations.<br>
* - Given tag overridden by Listener implementing ComponentWithName overridden by per method @MEthodOrder annotations.<br>
* @param listener
* @param tag Identifier for the registering plugin / agent, null is not discouraged, but null entries are ignored concerning sorting order.
* @param order Allows to register before other tags or just first. Expect MethodOrder to change in near future. The method order of already registered methods will not be compared to.
*/
public void registerAllEventHandlers(Listener listener, String tag, MethodOrder order){
if (listener instanceof IHaveMethodOrder){
order = ((IHaveMethodOrder) listener).getMethodOrder();
}
if (listener instanceof ComponentWithName){
// TODO: maybe change to an interface only defined here.
tag = ((ComponentWithName) listener).getComponentName();
}
Class<?> clazz = listener.getClass();
Set<Method> allMethods = new HashSet<Method>();
for (Method method : clazz.getMethods()){
allMethods.add(method);
}
for (Method method : clazz.getDeclaredMethods()){
allMethods.add(method);
}
for (Method method : allMethods){
EventHandler anno = method.getAnnotation(EventHandler.class);
if (anno == null) continue;
if (!method.isAccessible()){
// Try to make it accessible.
try{
method.setAccessible(true);
} catch (SecurityException e){
StaticLog.logWarning("[ListenerManager] Can not set method accessible: " + method.toGenericString() +" registered in " + clazz.getName()+ ", ignoring it!");
}
}
Class<?>[] argTypes = method.getParameterTypes();
if (argTypes.length != 1){
StaticLog.logWarning("[ListenerManager] Bad method signature (number of arguments not 1): " + method.toGenericString() +" registered in " + clazz.getName()+ ", ignoring it!");
continue;
}
Class<?> eventType = argTypes[0];
if (!Event.class.isAssignableFrom(eventType)){
StaticLog.logWarning("[ListenerManager] Bad method signature (argument does not extend Event): " + method.toGenericString() +" registered in " + clazz.getName()+ ", ignoring it!");
continue;
}
Class<? extends Event> checkedEventType = eventType.asSubclass(Event.class);
MethodOrder tempOrder = order;
String tempTag = tag;
fr.neatmonster.nocheatplus.event.MethodOrder orderAnno = method.getAnnotation(fr.neatmonster.nocheatplus.event.MethodOrder.class);
if (orderAnno != null){
MethodOrder veryTempOrder = tempOrder = MethodOrder.getMethodOrder(orderAnno);
if (veryTempOrder != null) tempOrder = veryTempOrder;
if (!orderAnno.tag().isEmpty()) tempTag = orderAnno.tag();
}
getListener(checkedEventType, anno.priority()).addMethodEntry(new MethodEntry(listener, method, anno.ignoreCancelled(), tempTag, tempOrder));
}
}
/**
* TODO: more methods for tags ? (+ return something?).
* @param listener
*/
public void remove(Listener listener) {
for (Map<EventPriority, GenericListener<?>> prioMap : map.values()){
for (GenericListener<?> gl : prioMap.values()){
gl.remove(listener);
}
}
}
/**
* Check if any GenericListeners are registered with Bukkit. <br>(To check if actually any listener methods are registered use: hasListenerMethods)
* @return
*/
public boolean hasListeners(){
return !map.isEmpty();
}
/**
* Check if any GenericListeners are present that are registered.
* @return
*/
public boolean hasRegisteredListeners(){
for (Map<EventPriority, GenericListener<?>> prioMap : map.values()){
for (GenericListener<?> gl : prioMap.values()){
if (gl.isRegistered()) return true;
}
}
return false;
}
/**
* Check if any GenericListeners are present that are not yet registered.
* @return
*/
public boolean hasPendingListeners(){
for (Map<EventPriority, GenericListener<?>> prioMap : map.values()){
for (GenericListener<?> gl : prioMap.values()){
if (!gl.isRegistered()) return true;
}
}
return false;
}
/**
* Check if any methods are registered for listening.
* @return
*/
public boolean hasListenerMethods(){
for (Map<EventPriority, GenericListener<?>> prioMap : map.values()){
for (GenericListener<?> gl : prioMap.values()){
if (gl.hasListenerMethods()) return true;
}
}
return false;
}
}

View File

@ -1,34 +0,0 @@
/*
* 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/>.
*/
package fr.neatmonster.nocheatplus.event;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Annotation to allow per-method method-order. Empty strings are regarded as "not set".
* @author mc_dev
*
*/
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MethodOrder {
public String tag() default "";
public String beforeTag() default "";
}

View File

@ -0,0 +1,225 @@
package fr.neatmonster.nocheatplus.event.mini;
import java.lang.reflect.Method;
import java.util.Collection;
import org.bukkit.Bukkit;
import org.bukkit.event.Cancellable;
import org.bukkit.event.Event;
import org.bukkit.event.EventException;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.server.PluginDisableEvent;
import org.bukkit.plugin.EventExecutor;
import org.bukkit.plugin.Plugin;
import fr.neatmonster.nocheatplus.components.registry.feature.ComponentWithName;
import fr.neatmonster.nocheatplus.components.registry.order.RegistrationOrder;
/**
* A MultiListenerRegistry that registers Bukkit types with a Spigot/CraftBukkit
* server.
*
* <br>
* Listener registration for {@link org.bukkit.event.Listener}, checking methods
* for {@link org.bukkit.event.EventHandler}. <br>
* <br>
* Supports passing a defaultOrder, as well as the per-class annotation
* {@link fr.neatmonster.nocheatplus.components.registry.order.RegistrationOrder.RegisterEventsWithOrder},
* and the per-method annotation
* {@link fr.neatmonster.nocheatplus.components.registry.order.RegistrationOrder.RegisterMethodWithOrder}.
* <br>
* Priority (FCFS): RegisterMethodWithOrder, RegisterEventsWithOrder,
* defaultOrder
*
* <br>
* <br>
* For alternatives and more details and conventions see:
* {@link fr.neatmonster.nocheatplus.event.mini.MiniListenerRegistry}<br>
*
*
* @author asofold
*
*/
public class EventRegistryBukkit extends MultiListenerRegistry<Event, EventPriority> {
/**
* Node for events that implement the Cancellable interface (Bukkit).
*
* @author asofold
*
* @param <E>
*/
protected static class CancellableNodeBukkit<E> extends MiniListenerNode<E, EventPriority> {
public CancellableNodeBukkit(EventPriority basePriority) {
super(basePriority);
}
// TODO: Future java: E extends Cancellable ?
@Override
protected boolean isCancelled(E event) {
return ((Cancellable) event).isCancelled();
}
}
private final Plugin plugin;
/**
* Pass this listener with each event registration to the
* {@link org.bukkit.plugin.PluginManager}.
*/
private final Listener dummyListener = new Listener() {}; // TODO: Get from NCP ?
public EventRegistryBukkit(Plugin plugin) {
this.plugin = plugin;
nodeFactory = new NodeFactory<Event, EventPriority>() {
@Override
public <E extends Event> MiniListenerNode<E, EventPriority> newNode(Class<E> eventClass, EventPriority basePriority) {
if (Cancellable.class.isAssignableFrom(eventClass)) {
// TODO: Check if order is right (eventClass extends Cancellable).
// TODO: Future java (see above) ?
return new CancellableNodeBukkit<E>(basePriority);
} else {
return new MiniListenerNode<E, EventPriority>(basePriority);
}
}
};
// Auto register for plugin disable.
// TODO: Ensure the ignoreCancelled setting is correct (do listeners really not unregister if the event is cancelled).
register(new MiniListener<PluginDisableEvent>() {
@Override
public void onEvent(PluginDisableEvent event) {
unregisterAttached(event.getPlugin());
}
}, EventPriority.MONITOR, new RegistrationOrder("nocheatplus.system.registry", null, ".*"), true);
}
@Override
protected <E extends Event> void registerNode(final Class<E> eventClass,
final MiniListenerNode<E, EventPriority> node, final EventPriority basePriority) {
Bukkit.getPluginManager().registerEvent(eventClass,
dummyListener,
basePriority, new EventExecutor() {
@SuppressWarnings("unchecked")
@Override
public void execute(Listener dummy, Event event) throws EventException {
if (eventClass.isAssignableFrom(event.getClass())) {
node.onEvent((E) event);
}
}
}, plugin, false);
}
/**
* Convenience method to have a listener unregister with disabling a plugin.
*
* @param listener
* Do not call with a plugin class being the listener, use the
* other register method instead!
* @param plugin
*/
public void register(Listener listener, Plugin plugin) {
register(listener, null, plugin);
}
/**
* Convenience method to have a listener unregister with disabling a certain
* other plugin.
*
* @param listener
* Do not call with a plugin class being the listener, use the
* other register method instead!
* @param defaultOrder
* @param plugin
* @see {@link #register(Listener, RegistrationOrder)}
*/
public void register(Listener listener, RegistrationOrder defaultOrder, Plugin plugin) {
attach(internalRegister(listener, defaultOrder), plugin);
}
/**
* Register the given listener similar to
* {@link org.bukkit.plugin.PluginManager#registerEvents(Listener, Plugin)}.
* <br>
* All events are registered for the plugin that was passed upon creation of
* this registry (supposedly NoCheatPlus).
*
* @param listener
* @see {@link #register(Listener, RegistrationOrder)}
*/
public void register(Listener listener) {
register(listener, (RegistrationOrder) null);
}
/**
* Register the given listener similar to
* {@link org.bukkit.plugin.PluginManager#registerEvents(Listener, Plugin)}.
* <br>
* All events are registered for the plugin that was passed upon creation of
* this registry (supposedly NoCheatPlus). Methods are selected by presence
* of the annotation {@link org.bukkit.event.EventHandler}.
* <hr>
* Supports passing a defaultOrder, as well as the per-class annotation
* {@link fr.neatmonster.nocheatplus.components.registry.order.RegistrationOrder.RegisterEventsWithOrder},
* and the per-method annotation
* {@link fr.neatmonster.nocheatplus.components.registry.order.RegistrationOrder.RegisterMethodWithOrder}.
* <br>
* Priority (FCFS): RegisterMethodWithOrder, RegisterEventsWithOrder,
* defaultOrder
*
* <br>
* <br>
* If no order is given, but the listener implements ComponentWithName, a
* RegistrationOrder with the component name as tag is created. <br>
* <br>
* <br>
* For alternatives and more details and conventions see:
* {@link fr.neatmonster.nocheatplus.event.mini.MiniListenerRegistry}<br>
*
* @param listener
* @param defaultOrder
*/
public void register(Listener listener, RegistrationOrder defaultOrder) {
internalRegister(listener, defaultOrder);
}
private Collection<MiniListener<? extends Event>> internalRegister(Listener listener,
RegistrationOrder defaultOrder) {
// Note: default ignoreCancelled and priority should have no effect, as EventHandler sets the defaults anyway.
// NCP for convenience: tag by component name, if no order is given.
if (defaultOrder == null && listener instanceof ComponentWithName) {
defaultOrder = new RegistrationOrder(((ComponentWithName) listener).getComponentName());
}
return super.register((Object) listener, EventPriority.NORMAL, defaultOrder, false);
}
@Override
protected boolean shouldBeEventHandler(Method method) {
return method.getAnnotation(EventHandler.class) != null;
}
@Override
protected boolean getIgnoreCancelled(Method method, boolean defaultIgnoreCancelled) {
EventHandler info = method.getAnnotation(EventHandler.class);
if (info == null) {
return defaultIgnoreCancelled;
}
else {
return info.ignoreCancelled();
}
}
@Override
protected EventPriority getPriority(Method method, EventPriority defaultPriority) {
EventHandler info = method.getAnnotation(EventHandler.class);
if (info == null) {
return defaultPriority;
}
else {
return info.priority();
}
}
}

View File

@ -0,0 +1,11 @@
package fr.neatmonster.nocheatplus.event.mini;
/**
* Minimal listener for one event.
* @author asofold
*
* @param <E>
*/
public interface MiniListener<E> {
public void onEvent(E event);
}

View File

@ -0,0 +1,156 @@
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));
}
}

View File

@ -0,0 +1,280 @@
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);
}

View File

@ -0,0 +1,14 @@
package fr.neatmonster.nocheatplus.event.mini;
import fr.neatmonster.nocheatplus.components.registry.order.IGetRegistrationOrder;
/**
* Convenience interface to have RegistrationOrder bundled this way.
*
* @author asofold
*
* @param <E>
*/
public interface MiniListenerWithOrder<E> extends MiniListener<E>, IGetRegistrationOrder {
}

View File

@ -0,0 +1,201 @@
package fr.neatmonster.nocheatplus.event.mini;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import fr.neatmonster.nocheatplus.NCPAPIProvider;
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.RegisterMethodWithOrder;
import fr.neatmonster.nocheatplus.logging.Streams;
/**
* Support for registering multiple event-handler methods at once.<br>
* <br>
*
* The MultiListenerRegistry allows passing a defaultOrder, as well as the
* per-class annotation
* {@link fr.neatmonster.nocheatplus.components.registry.order.RegistrationOrder.RegisterEventsWithOrder},
* and the per-method annotation
* {@link fr.neatmonster.nocheatplus.components.registry.order.RegistrationOrder.RegisterMethodWithOrder}.
* <br>
* Priority (FCFS): RegisterMethodWithOrder, RegisterEventsWithOrder,
* defaultOrder
*
* <br>
* <br>
* For alternatives and more details and conventions see:
* {@link fr.neatmonster.nocheatplus.event.mini.MiniListenerRegistry}<br>
*
*
* @author asofold
*
* @param <EB>
* Event base class, e.g. Event for Bukkit.
* @param <P>
* Priority class, e.g. EventPriority for Bukkit.
*/
public abstract class MultiListenerRegistry<EB, P> extends MiniListenerRegistry<EB, P> {
/**
*
* @param method
* @param basePriority
* @param defaultOrder
* Default order, taken from the Listener, iff the annotation
* RegisterEventsWithOrder was present. The
* RegisterMethodWithOrder annotation is processed here.
* @param ignoreCancelled
* @return
*/
@SuppressWarnings("unchecked")
protected <E extends EB> MiniListener<E> register(Object listener, Method method, P basePriority,
RegistrationOrder defaultOrder, boolean ignoreCancelled) {
RegistrationOrder order = null;
if (method.getClass().isAnnotationPresent(RegisterMethodWithOrder.class)) {
order = new RegistrationOrder(method.getClass().getAnnotation(RegisterMethodWithOrder.class));
}
if (order == null) {
order = defaultOrder;
}
MiniListener<E> miniListener = getMiniListener(listener, method, order);
if (listener == null) {
// TODO: Throw rather.
return null;
}
register((Class<E>) method.getParameterTypes()[0], miniListener,
basePriority, defaultOrder, ignoreCancelled);
return miniListener;
}
/**
* Auxiliary method to get a MiniListener instance for a given method.
*
* @param method
* @return
*/
protected <E extends EB> MiniListener<E> getMiniListener(final Object listener, final Method method, final RegistrationOrder order) {
try {
if (!method.getReturnType().equals(void.class)) {
return null;
}
Class<?>[] parameters = method.getParameterTypes();
if (parameters.length != 1) {
return null;
}
@SuppressWarnings({ "unchecked", "unused" })
Class<E> eventClass = (Class<E>) parameters[0];
if (!method.isAccessible()) {
method.setAccessible(true);
}
} catch (Throwable t) {
return null;
}
MiniListener<E> miniListener = new MiniListenerWithOrder<E>() {
@Override
public void onEvent(E event) {
try {
method.invoke(listener, event);
} catch (IllegalArgumentException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
}
}
@Override
public RegistrationOrder getRegistrationOrder() {
/*
* Return the given instance of RegistrationOrder, assuming
* it'll be copied upon registration. Typically the registry
* can't and shouldn't distinguish if this comes from an
* external source anyway.
*/
return order;
}
};
return miniListener;
}
/**
* Listener registration, checking methods for
* {@link org.bukkit.event.EventHandler}. Supports passing a defaultOrder,
* as well as the per-class annotation
* {@link fr.neatmonster.nocheatplus.components.registry.order.RegistrationOrder.RegisterEventsWithOrder},
* and the per-method annotation
* {@link fr.neatmonster.nocheatplus.components.registry.order.RegistrationOrder.RegisterMethodWithOrder}.
* <br>
* Priority (FCFS): RegisterMethodWithOrder, RegisterEventsWithOrder,
* defaultOrder
*
* @param listener
* All internally created MiniListener instances will be attached
* to the listener by default.
* @return Collection of MiniListener instances for attaching.
*/
protected Collection<MiniListener<? extends EB>> register(Object listener,
P defaultPriority, RegistrationOrder defaultOrder, boolean defaultIgnoreCancelled) {
Collection<MiniListener<? extends EB>> listeners = new ArrayList<MiniListener<? extends EB>>();
Class<?> listenerClass = listener.getClass();
RegistrationOrder order = null;
if (listenerClass.isAnnotationPresent(RegisterEventsWithOrder.class)) {
order = new RegistrationOrder(listenerClass.getAnnotation(RegisterEventsWithOrder.class));
}
if (order == null) {
order = defaultOrder;
}
int shouldBe = 0;
for (Method method : listenerClass.getMethods()) {
if (shouldBeEventHandler(method)) {
shouldBe ++;
MiniListener<? extends EB> miniListener = register(listener, method,
getPriority(method, defaultPriority), order,
getIgnoreCancelled(method, defaultIgnoreCancelled));
if (miniListener == null) {
NCPAPIProvider.getNoCheatPlusAPI().getLogManager().severe(Streams.STATUS,
"Could not register event listener: " + listener.getClass().getName()
+ "#" + method.getName());
} else {
listeners.add(miniListener);
}
}
}
if (shouldBe > listeners.size()) {
// TODO: Unregister and throw ? Should perhaps depend on configuration.
}
if (!listeners.isEmpty()) {
attach(listeners, listener);
}
return listeners;
}
/**
* Solely meant for check for annotations (not return type etc., use
* getMiniListener for that).
*
* @param method
* @return
*/
protected abstract boolean shouldBeEventHandler(Method method);
/**
* This is meant to process platform specific annotations.
*
* @param method
* @return
*/
protected abstract boolean getIgnoreCancelled(Method method, boolean defaultIgnoreCancelled);
/**
* This is meant to process platform specific annotations.
*
* @param method
* @return
*/
protected abstract P getPriority(Method method, P defaultPriority);
}

View File

@ -84,7 +84,6 @@ import fr.neatmonster.nocheatplus.components.NoCheatPlusAPI;
import fr.neatmonster.nocheatplus.components.registry.ComponentRegistry;
import fr.neatmonster.nocheatplus.components.registry.DefaultGenericInstanceRegistry;
import fr.neatmonster.nocheatplus.components.registry.event.IGenericInstanceHandle;
import fr.neatmonster.nocheatplus.components.registry.feature.ComponentWithName;
import fr.neatmonster.nocheatplus.components.registry.feature.ConsistencyChecker;
import fr.neatmonster.nocheatplus.components.registry.feature.IDisableListener;
import fr.neatmonster.nocheatplus.components.registry.feature.IHoldSubComponents;
@ -101,8 +100,7 @@ import fr.neatmonster.nocheatplus.components.registry.order.SetupOrder;
import fr.neatmonster.nocheatplus.config.ConfPaths;
import fr.neatmonster.nocheatplus.config.ConfigFile;
import fr.neatmonster.nocheatplus.config.ConfigManager;
import fr.neatmonster.nocheatplus.event.IHaveMethodOrder;
import fr.neatmonster.nocheatplus.event.ListenerManager;
import fr.neatmonster.nocheatplus.event.mini.EventRegistryBukkit;
import fr.neatmonster.nocheatplus.hooks.ExemptionSettings;
import fr.neatmonster.nocheatplus.hooks.NCPExemptionManager;
import fr.neatmonster.nocheatplus.hooks.NCPHookManager;
@ -178,9 +176,7 @@ public class NoCheatPlus extends JavaPlugin implements NoCheatPlusAPI {
*/
final LinkedList<CommandProtectionEntry> changedCommands = new LinkedList<CommandProtectionEntry>();
private final ListenerManager listenerManager = new ListenerManager(this, false);
private boolean manageListeners = true;
private final EventRegistryBukkit eventRegistry = new EventRegistryBukkit(this);
protected boolean lateListenerRegistered = false;
@ -568,37 +564,15 @@ public class NoCheatPlus extends JavaPlugin implements NoCheatPlusAPI {
*/
private void addListener(final Listener listener) {
// private: Use addComponent.
if (manageListeners) {
String tag = "NoCheatPlus";
if (listener instanceof ComponentWithName) {
tag = ((ComponentWithName) listener).getComponentName();
}
listenerManager.registerAllEventHandlers(listener, tag);
listeners.add(listener);
}
else {
Bukkit.getPluginManager().registerEvents(listener, this);
if (listener instanceof IHaveMethodOrder) {
// TODO: Might log the order too, might prevent registration ?
// TODO: Alternative: queue listeners and register after startup (!)
logManager.warning(Streams.INIT, "Listener demands registration order, but listeners are not managed: " + listener.getClass().getName());
}
}
}
/**
* Test if NCP uses the ListenerManager at all.
* @return If so.
*/
public boolean doesManageListeners() {
return manageListeners;
eventRegistry.register(listener);
listeners.add(listener);
}
@Override
public void removeComponent(final Object obj) {
if (obj instanceof Listener) {
listeners.remove((Listener) obj);
listenerManager.remove((Listener) obj);
eventRegistry.unregisterAttached((Listener) obj);
}
if (obj instanceof PermStateReceiver) {
permStateReceivers.remove((PermStateReceiver) obj);
@ -659,15 +633,10 @@ public class NoCheatPlus extends JavaPlugin implements NoCheatPlusAPI {
// Remove listener references.
if (verbose) {
if (listenerManager.hasListenerMethods()) {
logManager.info(Streams.INIT, "Cleanup ListenerManager...");
}
else {
logManager.info(Streams.INIT, "(ListenerManager not in use, prevent registering...)");
}
logManager.info(Streams.INIT, "Cleanup event registry (Bukkit)...");
}
listenerManager.setRegisterDirectly(false);
listenerManager.clear();
// TODO: Prevent register feature ?
eventRegistry.clear();
lateListenerRegistered = false;
BukkitScheduler sched = getServer().getScheduler();
@ -932,18 +901,6 @@ public class NoCheatPlus extends JavaPlugin implements NoCheatPlusAPI {
// Set some instance members.
setInstanceMembers(config);
// Listener manager.
manageListeners = config.getBoolean(ConfPaths.COMPATIBILITY_MANAGELISTENERS);
if (manageListeners) {
listenerManager.setRegisterDirectly(true);
listenerManager.registerAllWithBukkit();
}
else {
// Just for safety.
listenerManager.setRegisterDirectly(false);
listenerManager.clear();
}
// Register some generic stuff.
// (Deny change some.)
registerGenericInstance(new Counters());
@ -955,7 +912,7 @@ public class NoCheatPlus extends JavaPlugin implements NoCheatPlusAPI {
registerGenericInstance(new PassengerUtil());
genericInstanceRegistry.denyChangeExistingRegistration(PassengerUtil.class);
// (Allow override others.)
registerGenericInstance(new Random(System.currentTimeMillis() ^ ((long) this.hashCode() * (long) listenerManager.hashCode() * (long) logManager.hashCode())));
registerGenericInstance(new Random(System.currentTimeMillis() ^ ((long) this.hashCode() * (long) eventRegistry.hashCode() * (long) logManager.hashCode())));
addComponent(new BridgeCrossPlugin());
// Initialize MCAccess.
@ -1560,4 +1517,9 @@ public class NoCheatPlus extends JavaPlugin implements NoCheatPlusAPI {
return blockChangeTracker;
}
@Override
public EventRegistryBukkit getEventRegistry() {
return eventRegistry;
}
}

View File

@ -25,6 +25,7 @@ import fr.neatmonster.nocheatplus.components.NoCheatPlusAPI;
import fr.neatmonster.nocheatplus.components.registry.ComponentRegistry;
import fr.neatmonster.nocheatplus.components.registry.DefaultGenericInstanceRegistry;
import fr.neatmonster.nocheatplus.components.registry.event.IGenericInstanceHandle;
import fr.neatmonster.nocheatplus.event.mini.EventRegistryBukkit;
import fr.neatmonster.nocheatplus.logging.LogManager;
import fr.neatmonster.nocheatplus.logging.StaticLog;
@ -163,6 +164,11 @@ public class PluginTests {
throw new UnsupportedOperationException();
}
@Override
public EventRegistryBukkit getEventRegistry() {
throw new UnsupportedOperationException();
}
}
public static void setUnitTestNoCheatPlusAPI(boolean force) {