NoCheatPlus/NCPCore/src/main/java/fr/neatmonster/nocheatplus/event/GenericListener.java

224 lines
6.9 KiB
Java

/*
* 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;
}
}