-
-
Save chaseking/5563922 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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