Skip to content

Instantly share code, notes, and snippets.

@aadnk
Last active January 17, 2026 15:37
Show Gist options
  • Select an option

  • Save aadnk/5563794 to your computer and use it in GitHub Desktop.

Select an option

Save aadnk/5563794 to your computer and use it in GitHub Desktop.

Revisions

  1. aadnk revised this gist May 12, 2013. 1 changed file with 5 additions and 0 deletions.
    5 changes: 5 additions & 0 deletions CancellationDetector.java
    Original file line number Diff line number Diff line change
    @@ -145,6 +145,11 @@ public void close() {
    try {
    HandlerList list = getHandlerList(eventClazz);
    getSlotsField(list).set(list, backup);

    Field handlers = list.getClass().getDeclaredField("handlers");
    handlers.setAccessible(true);
    handlers.set(list, null);

    } catch (Exception e) {
    throw new RuntimeException("Unable to clean up handler list.", e);
    }
  2. aadnk revised this gist May 12, 2013. 1 changed file with 191 additions and 140 deletions.
    331 changes: 191 additions & 140 deletions CancellationDetector.java
    Original file line number Diff line number Diff line change
    @@ -4,13 +4,15 @@
    import java.lang.reflect.Method;
    import java.util.ArrayList;
    import java.util.EnumMap;
    import java.util.Iterator;
    import java.util.List;

    import org.bukkit.event.Cancellable;
    import org.bukkit.event.Event;
    import org.bukkit.event.EventException;
    import org.bukkit.event.EventPriority;
    import org.bukkit.event.HandlerList;
    import org.bukkit.event.Listener;
    import org.bukkit.plugin.IllegalPluginAccessException;
    import org.bukkit.plugin.Plugin;
    import org.bukkit.plugin.RegisteredListener;
    @@ -19,144 +21,193 @@

    public class CancellationDetector<TEvent extends Event> {
    interface CancelListener<TEvent extends Event> {
    public void onCancelled(Plugin plugin, TEvent event);
    }

    private final Class<TEvent> eventClazz;
    private final List<CancelListener<TEvent>> listeners = Lists.newArrayList();

    // For reverting the detector
    private EnumMap<EventPriority, ArrayList<RegisteredListener>> backup;

    public CancellationDetector(Class<TEvent> eventClazz) {
    this.eventClazz = eventClazz;
    injectProxy();
    }

    public void addListener(CancelListener<TEvent> listener) {
    listeners.add(listener);
    }

    public void removeListener(CancelListener<Event> listener) {
    listeners.remove(listener);
    }

    @SuppressWarnings("unchecked")
    private EnumMap<EventPriority, ArrayList<RegisteredListener>> getSlots(HandlerList list) {
    try {
    return (EnumMap<EventPriority, ArrayList<RegisteredListener>>) getSlotsField(list).get(list);
    } catch (Exception e) {
    throw new RuntimeException("Unable to retrieve slots.", e);
    }
    }

    private Field getSlotsField(HandlerList list) {
    if (list == null)
    throw new IllegalStateException("Detected a NULL handler list.");

    try {
    Field slotField = list.getClass().getDeclaredField("handlerslots");

    // Get our slot map
    slotField.setAccessible(true);
    return slotField;
    } catch (Exception e) {
    throw new IllegalStateException("Unable to intercept 'handlerslot' in " + list.getClass(), e);
    }
    }

    private void injectProxy() {
    HandlerList list = getHandlerList(eventClazz);
    EnumMap<EventPriority, ArrayList<RegisteredListener>> slots = getSlots(list);

    // Keep a copy of this map
    backup = slots.clone();

    synchronized (list) {
    for (EventPriority priority : slots.keySet().toArray(new EventPriority[0])) {
    slots.put(priority, new ArrayList<RegisteredListener>() {
    private static final long serialVersionUID = 7869505892922082581L;

    @Override
    public boolean add(RegisteredListener e) {
    return super.add(injectRegisteredListener(e));
    }

    @Override
    public void add(int index, RegisteredListener element) {
    super.add(index, injectRegisteredListener(element));
    }
    });
    }
    }
    }

    // The core of our magic
    private RegisteredListener injectRegisteredListener(final RegisteredListener listener) {
    return new RegisteredListener(listener.getListener(), null, listener.getPriority(), listener.getPlugin(), false) {
    @SuppressWarnings("unchecked")
    @Override
    public void callEvent(Event event) throws EventException {
    if (event instanceof Cancellable) {
    boolean prior = getCancelState(event);

    listener.callEvent(event);

    // See if this plugin cancelled the event
    if (!prior && getCancelState(event)) {
    invokeCancelled(getPlugin(), (TEvent) event);
    }
    } else {
    listener.callEvent(event);
    }
    }
    };
    }

    private void invokeCancelled(Plugin plugin, TEvent event) {
    for (CancelListener<TEvent> listener : listeners) {
    listener.onCancelled(plugin, event);
    }
    }

    private boolean getCancelState(Event event) {
    return ((Cancellable) event).isCancelled();
    }

    public void close() {
    if (backup != null) {
    try {
    HandlerList list = getHandlerList(eventClazz);
    getSlotsField(list).set(list, backup);
    } catch (Exception e) {
    throw new RuntimeException("Unable to clean up handler list.", e);
    }

    backup = null;
    }
    }

    /**
    * Retrieve the handler list associated with the given class.
    *
    * @param clazz - given event class.
    * @return Associated handler list.
    */
    private static HandlerList getHandlerList(Class<? extends Event> clazz) {
    // Class must have Event as its superclass
    while (clazz.getSuperclass() != null && Event.class.isAssignableFrom(clazz.getSuperclass())) {
    try {
    Method method = clazz.getDeclaredMethod("getHandlerList");
    method.setAccessible(true);
    return (HandlerList) method.invoke(null);
    } catch (NoSuchMethodException e) {
    // Keep on searching
    clazz = clazz.getSuperclass().asSubclass(Event.class);
    } catch (Exception e) {
    throw new IllegalPluginAccessException(e.getMessage());
    }
    }
    throw new IllegalPluginAccessException("Unable to find handler list for event "
    + clazz.getName());
    }
    public void onCancelled(Plugin plugin, TEvent event);
    }

    private final Class<TEvent> eventClazz;
    private final List<CancelListener<TEvent>> listeners = Lists.newArrayList();

    // For reverting the detector
    private EnumMap<EventPriority, ArrayList<RegisteredListener>> backup;

    public CancellationDetector(Class<TEvent> eventClazz) {
    this.eventClazz = eventClazz;
    injectProxy();
    }

    public void addListener(CancelListener<TEvent> listener) {
    listeners.add(listener);
    }

    public void removeListener(CancelListener<Event> listener) {
    listeners.remove(listener);
    }

    @SuppressWarnings("unchecked")
    private EnumMap<EventPriority, ArrayList<RegisteredListener>> getSlots(HandlerList list) {
    try {
    return (EnumMap<EventPriority, ArrayList<RegisteredListener>>) getSlotsField(list).get(list);
    } catch (Exception e) {
    throw new RuntimeException("Unable to retrieve slots.", e);
    }
    }

    private Field getSlotsField(HandlerList list) {
    if (list == null)
    throw new IllegalStateException("Detected a NULL handler list.");

    try {
    Field slotField = list.getClass().getDeclaredField("handlerslots");

    // Get our slot map
    slotField.setAccessible(true);
    return slotField;
    } catch (Exception e) {
    throw new IllegalStateException("Unable to intercept 'handlerslot' in " + list.getClass(), e);
    }
    }

    private void injectProxy() {
    HandlerList list = getHandlerList(eventClazz);
    EnumMap<EventPriority, ArrayList<RegisteredListener>> slots = getSlots(list);

    // Keep a copy of this map
    backup = slots.clone();

    synchronized (list) {
    for (EventPriority p : slots.keySet().toArray(new EventPriority[0])) {
    final EventPriority priority = p;
    final ArrayList<RegisteredListener> proxyList = new ArrayList<RegisteredListener>() {
    private static final long serialVersionUID = 7869505892922082581L;

    @Override
    public boolean add(RegisteredListener e) {
    super.add(injectRegisteredListener(e));
    return backup.get(priority).add(e);
    }

    @Override
    public boolean remove(Object listener) {
    // Remove this listener
    for (Iterator<RegisteredListener> it = iterator(); it.hasNext(); ) {
    DelegatedRegisteredListener delegated = (DelegatedRegisteredListener) it.next();
    if (delegated.delegate == listener) {
    it.remove();
    break;
    }
    }
    return backup.get(priority).remove(listener);
    }
    };
    slots.put(priority, proxyList);

    for (RegisteredListener listener : backup.get(priority)) {
    proxyList.add(listener);
    }
    }
    }
    }

    // The core of our magic
    private RegisteredListener injectRegisteredListener(final RegisteredListener listener) {
    return new DelegatedRegisteredListener(listener) {
    @SuppressWarnings("unchecked")
    @Override
    public void callEvent(Event event) throws EventException {
    if (event instanceof Cancellable) {
    boolean prior = getCancelState(event);

    listener.callEvent(event);

    // See if this plugin cancelled the event
    if (!prior && getCancelState(event)) {
    invokeCancelled(getPlugin(), (TEvent) event);
    }
    } else {
    listener.callEvent(event);
    }
    }
    };
    }

    private void invokeCancelled(Plugin plugin, TEvent event) {
    for (CancelListener<TEvent> listener : listeners) {
    listener.onCancelled(plugin, event);
    }
    }

    private boolean getCancelState(Event event) {
    return ((Cancellable) event).isCancelled();
    }

    public void close() {
    if (backup != null) {
    try {
    HandlerList list = getHandlerList(eventClazz);
    getSlotsField(list).set(list, backup);
    } catch (Exception e) {
    throw new RuntimeException("Unable to clean up handler list.", e);
    }

    backup = null;
    }
    }

    /**
    * Retrieve the handler list associated with the given class.
    *
    * @param clazz - given event class.
    * @return Associated handler list.
    */
    private static HandlerList getHandlerList(Class<? extends Event> clazz) {
    // Class must have Event as its superclass
    while (clazz.getSuperclass() != null && Event.class.isAssignableFrom(clazz.getSuperclass())) {
    try {
    Method method = clazz.getDeclaredMethod("getHandlerList");
    method.setAccessible(true);
    return (HandlerList) method.invoke(null);
    } catch (NoSuchMethodException e) {
    // Keep on searching
    clazz = clazz.getSuperclass().asSubclass(Event.class);
    } catch (Exception e) {
    throw new IllegalPluginAccessException(e.getMessage());
    }
    }
    throw new IllegalPluginAccessException("Unable to find handler list for event "
    + clazz.getName());
    }

    /**
    * Represents a registered listener that delegates to a given listener.
    * @author Kristian
    */
    private static class DelegatedRegisteredListener extends RegisteredListener {
    private final RegisteredListener delegate;

    public DelegatedRegisteredListener(RegisteredListener delegate) {
    // These values will be ignored however'
    super(delegate.getListener(), null, delegate.getPriority(), delegate.getPlugin(), false);
    this.delegate = delegate;
    }

    public void callEvent(Event event) throws EventException {
    delegate.callEvent(event);
    }

    public Listener getListener() {
    return delegate.getListener();
    }

    public Plugin getPlugin() {
    return delegate.getPlugin();
    }

    public EventPriority getPriority() {
    return delegate.getPriority();
    }

    public boolean isIgnoringCancelled() {
    return delegate.isIgnoringCancelled();
    }
    }
    }
  3. aadnk created this gist May 12, 2013.
    162 changes: 162 additions & 0 deletions CancellationDetector.java
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,162 @@
    package com.comphenix.example;

    import java.lang.reflect.Field;
    import java.lang.reflect.Method;
    import java.util.ArrayList;
    import java.util.EnumMap;
    import java.util.List;

    import org.bukkit.event.Cancellable;
    import org.bukkit.event.Event;
    import org.bukkit.event.EventException;
    import org.bukkit.event.EventPriority;
    import org.bukkit.event.HandlerList;
    import org.bukkit.plugin.IllegalPluginAccessException;
    import org.bukkit.plugin.Plugin;
    import org.bukkit.plugin.RegisteredListener;

    import com.google.common.collect.Lists;

    public class CancellationDetector<TEvent extends Event> {
    interface CancelListener<TEvent extends Event> {
    public void onCancelled(Plugin plugin, TEvent event);
    }

    private final Class<TEvent> eventClazz;
    private final List<CancelListener<TEvent>> listeners = Lists.newArrayList();

    // For reverting the detector
    private EnumMap<EventPriority, ArrayList<RegisteredListener>> backup;

    public CancellationDetector(Class<TEvent> eventClazz) {
    this.eventClazz = eventClazz;
    injectProxy();
    }

    public void addListener(CancelListener<TEvent> listener) {
    listeners.add(listener);
    }

    public void removeListener(CancelListener<Event> listener) {
    listeners.remove(listener);
    }

    @SuppressWarnings("unchecked")
    private EnumMap<EventPriority, ArrayList<RegisteredListener>> getSlots(HandlerList list) {
    try {
    return (EnumMap<EventPriority, ArrayList<RegisteredListener>>) getSlotsField(list).get(list);
    } catch (Exception e) {
    throw new RuntimeException("Unable to retrieve slots.", e);
    }
    }

    private Field getSlotsField(HandlerList list) {
    if (list == null)
    throw new IllegalStateException("Detected a NULL handler list.");

    try {
    Field slotField = list.getClass().getDeclaredField("handlerslots");

    // Get our slot map
    slotField.setAccessible(true);
    return slotField;
    } catch (Exception e) {
    throw new IllegalStateException("Unable to intercept 'handlerslot' in " + list.getClass(), e);
    }
    }

    private void injectProxy() {
    HandlerList list = getHandlerList(eventClazz);
    EnumMap<EventPriority, ArrayList<RegisteredListener>> slots = getSlots(list);

    // Keep a copy of this map
    backup = slots.clone();

    synchronized (list) {
    for (EventPriority priority : slots.keySet().toArray(new EventPriority[0])) {
    slots.put(priority, new ArrayList<RegisteredListener>() {
    private static final long serialVersionUID = 7869505892922082581L;

    @Override
    public boolean add(RegisteredListener e) {
    return super.add(injectRegisteredListener(e));
    }

    @Override
    public void add(int index, RegisteredListener element) {
    super.add(index, injectRegisteredListener(element));
    }
    });
    }
    }
    }

    // The core of our magic
    private RegisteredListener injectRegisteredListener(final RegisteredListener listener) {
    return new RegisteredListener(listener.getListener(), null, listener.getPriority(), listener.getPlugin(), false) {
    @SuppressWarnings("unchecked")
    @Override
    public void callEvent(Event event) throws EventException {
    if (event instanceof Cancellable) {
    boolean prior = getCancelState(event);

    listener.callEvent(event);

    // See if this plugin cancelled the event
    if (!prior && getCancelState(event)) {
    invokeCancelled(getPlugin(), (TEvent) event);
    }
    } else {
    listener.callEvent(event);
    }
    }
    };
    }

    private void invokeCancelled(Plugin plugin, TEvent event) {
    for (CancelListener<TEvent> listener : listeners) {
    listener.onCancelled(plugin, event);
    }
    }

    private boolean getCancelState(Event event) {
    return ((Cancellable) event).isCancelled();
    }

    public void close() {
    if (backup != null) {
    try {
    HandlerList list = getHandlerList(eventClazz);
    getSlotsField(list).set(list, backup);
    } catch (Exception e) {
    throw new RuntimeException("Unable to clean up handler list.", e);
    }

    backup = null;
    }
    }

    /**
    * Retrieve the handler list associated with the given class.
    *
    * @param clazz - given event class.
    * @return Associated handler list.
    */
    private static HandlerList getHandlerList(Class<? extends Event> clazz) {
    // Class must have Event as its superclass
    while (clazz.getSuperclass() != null && Event.class.isAssignableFrom(clazz.getSuperclass())) {
    try {
    Method method = clazz.getDeclaredMethod("getHandlerList");
    method.setAccessible(true);
    return (HandlerList) method.invoke(null);
    } catch (NoSuchMethodException e) {
    // Keep on searching
    clazz = clazz.getSuperclass().asSubclass(Event.class);
    } catch (Exception e) {
    throw new IllegalPluginAccessException(e.getMessage());
    }
    }
    throw new IllegalPluginAccessException("Unable to find handler list for event "
    + clazz.getName());
    }
    }
    37 changes: 37 additions & 0 deletions ExampleMod.java
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,37 @@
    package com.comphenix.example;

    import org.bukkit.event.EventHandler;
    import org.bukkit.event.Listener;
    import org.bukkit.event.block.BlockPlaceEvent;
    import org.bukkit.plugin.Plugin;
    import org.bukkit.plugin.java.JavaPlugin;

    import com.comphenix.example.CancellationDetector.CancelListener;

    public class ExampleMod extends JavaPlugin implements Listener {
    private CancellationDetector<BlockPlaceEvent> detector = new CancellationDetector<BlockPlaceEvent>(BlockPlaceEvent.class);

    @Override
    public void onEnable() {
    getServer().getPluginManager().registerEvents(this, this);

    detector.addListener(new CancelListener<BlockPlaceEvent>() {
    @Override
    public void onCancelled(Plugin plugin, BlockPlaceEvent event) {
    System.out.println(event + " cancelled by " + plugin);
    }
    });
    }

    @Override
    public void onDisable() {
    // Incredibly important!
    detector.close();
    }

    // For testing
    @EventHandler
    public void onBlockPlaceEvent(BlockPlaceEvent e) {
    e.setCancelled(true);
    }
    }