implement FunctionalEventHandler

This commit is contained in:
dfsek
2021-07-21 22:04:40 -07:00
parent 6f1938ef82
commit 953318d9c0
21 changed files with 234 additions and 120 deletions

View File

@@ -0,0 +1,76 @@
package com.dfsek.terra.event;
import com.dfsek.terra.api.addon.TerraAddon;
import com.dfsek.terra.api.event.events.Event;
import com.dfsek.terra.api.event.events.PackEvent;
import com.dfsek.terra.api.event.functional.EventContext;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
public class EventContextImpl<T extends Event> implements EventContext<T>, Comparable<EventContextImpl<T>> {
private final List<Consumer<T>> actions = new ArrayList<>();
private int priority;
private boolean failThrough = false;
private boolean global = false;
private final TerraAddon addon;
public EventContextImpl(TerraAddon addon) {
this.addon = addon;
}
public void handle(T event) {
if(event instanceof PackEvent) {
}
actions.forEach(action -> action.accept(event));
}
@Override
public EventContext<T> then(Consumer<T> action) {
actions.add(action);
return this;
}
@Override
public EventContext<T> priority(int priority) {
this.priority = priority;
return this;
}
@Override
public EventContext<T> failThrough() {
this.failThrough = true;
return this;
}
@Override
public EventContext<T> global() {
this.global = true;
return this;
}
@Override
public int compareTo(@NotNull EventContextImpl<T> o) {
return this.priority - o.priority;
}
public boolean isGlobal() {
return global;
}
public int getPriority() {
return priority;
}
public TerraAddon getAddon() {
return addon;
}
public boolean isFailThrough() {
return failThrough;
}
}

View File

@@ -2,110 +2,45 @@ package com.dfsek.terra.event;
import com.dfsek.terra.api.TerraPlugin;
import com.dfsek.terra.api.addon.TerraAddon;
import com.dfsek.terra.api.event.EventHandler;
import com.dfsek.terra.api.event.EventListener;
import com.dfsek.terra.api.event.EventManager;
import com.dfsek.terra.api.event.annotations.Global;
import com.dfsek.terra.api.event.annotations.Priority;
import com.dfsek.terra.api.event.events.Cancellable;
import com.dfsek.terra.api.event.events.Event;
import com.dfsek.terra.api.event.events.PackEvent;
import com.dfsek.terra.api.util.reflection.ReflectionUtil;
import com.dfsek.terra.api.event.functional.EventContext;
import com.dfsek.terra.api.event.functional.FunctionalEventHandler;
import com.dfsek.terra.api.util.reflection.TypeKey;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class EventManagerImpl implements EventManager {
private final Map<Class<? extends Event>, List<ListenerHolder>> listeners = new HashMap<>();
private final Map<Class<?>, EventHandler> handlers = new HashMap<>();
private final TerraPlugin main;
public EventManagerImpl(TerraPlugin main) {
this.main = main;
registerHandler(FunctionalEventHandler.class, new FunctionalEventHandlerImpl(main)); // default handler
}
@Override
public boolean callEvent(Event event) {
listeners.getOrDefault(event.getClass(), Collections.emptyList()).forEach(listenerHolder -> {
try {
if(event instanceof PackEvent && !listenerHolder.global) {
PackEvent packEvent = (PackEvent) event;
if(packEvent
.getPack()
.addons()
.contains(listenerHolder.addon)) {
listenerHolder.method.invoke(listenerHolder.listener, event);
}
} else {
listenerHolder.method.invoke(listenerHolder.listener, event);
}
} catch(InvocationTargetException e) {
StringWriter writer = new StringWriter();
e.getTargetException().printStackTrace(new PrintWriter(writer));
main.logger().warning("Exception occurred during event handling:");
main.logger().warning(writer.toString());
main.logger().warning("Report this to the maintainers of " + listenerHolder.method.getName());
} catch(Exception e) {
StringWriter writer = new StringWriter();
e.printStackTrace(new PrintWriter(writer));
main.logger().warning("Exception occurred during event handling:");
main.logger().warning(writer.toString());
main.logger().warning("Report this to the maintainers of " + listenerHolder.method.getName());
}
}
);
if(event instanceof Cancellable) return !((Cancellable) event).isCancelled();
return true;
public void callEvent(Event event) {
handlers.values().forEach(handler -> handler.handle(event));
}
@Override
public <T extends EventHandler> void registerHandler(Class<T> clazz, T handler) {
handlers.put(clazz, handler);
}
@SuppressWarnings("unchecked")
@Override
public void registerListener(TerraAddon addon, EventListener listener) {
Class<? extends EventListener> listenerClass = listener.getClass();
Method[] methods = ReflectionUtil.getMethods(listenerClass);
for(Method method : methods) {
if(method.getParameterCount() != 1) continue; // Check that parameter count is only 1.
Class<?> eventParam = method.getParameterTypes()[0];
if(!Event.class.isAssignableFrom(eventParam)) continue; // Check that parameter is an Event.
Priority p = method.getAnnotation(Priority.class);
int priority = p == null ? 0 : p.value();
method.setAccessible(true);
List<ListenerHolder> holders = listeners.computeIfAbsent((Class<? extends Event>) eventParam, e -> new ArrayList<>());
holders.add(new ListenerHolder(method, listener, priority, addon, method.getAnnotation(Global.class) != null));
holders.sort(Comparator.comparingInt(ListenerHolder::getPriority)); // Sort priorities.
}
}
private static final class ListenerHolder {
private final Method method;
private final EventListener listener;
private final int priority;
private final TerraAddon addon;
private final boolean global;
private ListenerHolder(Method method, EventListener listener, int priority, TerraAddon addon, boolean global) {
this.method = method;
this.listener = listener;
this.priority = priority;
this.addon = addon;
this.global = global;
}
public int getPriority() {
return priority;
}
public <T extends EventHandler> T getHandler(Class<T> clazz) {
return (T) handlers.computeIfAbsent(clazz, c -> {
throw new IllegalArgumentException("No event handler registered for class " + clazz.getCanonicalName());
});
}
}

View File

@@ -0,0 +1,65 @@
package com.dfsek.terra.event;
import com.dfsek.terra.api.TerraPlugin;
import com.dfsek.terra.api.addon.TerraAddon;
import com.dfsek.terra.api.event.events.Event;
import com.dfsek.terra.api.event.events.PackEvent;
import com.dfsek.terra.api.event.functional.EventContext;
import com.dfsek.terra.api.event.functional.FunctionalEventHandler;
import com.dfsek.terra.api.util.reflection.TypeKey;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class FunctionalEventHandlerImpl implements FunctionalEventHandler {
private final Map<Type, List<EventContextImpl<?>>> contextMap = new HashMap<>();
private final TerraPlugin main;
public FunctionalEventHandlerImpl(TerraPlugin main) {
this.main = main;
}
@SuppressWarnings("unchecked")
@Override
public void handle(Event event) {
contextMap.getOrDefault(event.getClass(), Collections.emptyList()).forEach(context -> {
try {
if(event instanceof PackEvent) {
if((context.isGlobal() || ((PackEvent) event).getPack().addons().contains(context.getAddon()))) {
((EventContextImpl<Event>) context).handle(event);
}
} else {
((EventContextImpl<Event>) context).handle(event);
}
} catch(Exception e) {
if(context.isFailThrough()) throw e; // Rethrow if it's fail-through.
StringWriter writer = new StringWriter();
e.printStackTrace(new PrintWriter(writer));
main.logger().warning("Exception occurred during event handling:");
main.logger().warning(writer.toString());
main.logger().warning("Report this to the maintainers of " + context.getAddon().getName() + ", " + context.getAddon().getAuthor());
}
});
}
@Override
public <T extends Event> EventContext<T> register(TerraAddon addon, Class<T> clazz) {
EventContextImpl<T> eventContext = new EventContextImpl<>(addon);
contextMap.computeIfAbsent(clazz, c -> new ArrayList<>()).add(eventContext);
return eventContext;
}
@Override
public <T extends Event> EventContext<T> register(TerraAddon addon, TypeKey<T> clazz) {
EventContextImpl<T> eventContext = new EventContextImpl<>(addon);
contextMap.computeIfAbsent(clazz.getType(), c -> new ArrayList<>()).add(eventContext);
return eventContext;
}
}