Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save chaseking/5563923 to your computer and use it in GitHub Desktop.
Save chaseking/5563923 to your computer and use it in GitHub Desktop.
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());
}
}
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);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment