diff --git a/ContentGrabbingJi-core/src/main/java/net/lamgc/cgj/bot/event/DefaultEventHandlerRegistry.java b/ContentGrabbingJi-core/src/main/java/net/lamgc/cgj/bot/event/DefaultEventHandlerRegistry.java new file mode 100644 index 0000000..018c6c9 --- /dev/null +++ b/ContentGrabbingJi-core/src/main/java/net/lamgc/cgj/bot/event/DefaultEventHandlerRegistry.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2020 LamGC + * + * ContentGrabbingJi is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * ContentGrabbingJi 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package net.lamgc.cgj.bot.event; + +import java.lang.reflect.Method; +import java.util.*; +import java.util.concurrent.CopyOnWriteArraySet; + +/** + * 默认的事件处理注册器. + * @author LamGC + */ +public class DefaultEventHandlerRegistry implements EventHandlerRegistry { + + private final Map instanceMap = new Hashtable<>(); + private final Map, Set> eventHandlerMap = new Hashtable<>(); + + @Override + public int registerHandler(Object handlerObject) { + int count = 0; + Class handlerClass = handlerObject.getClass(); + Method[] methods = handlerClass.getDeclaredMethods(); + for (Method method : methods) { + if (!EventUtils.checkEventHandlerMethod(method)) { + continue; + } + addHandlerMethod(method, handlerObject); + count++; + } + + return count; + } + + private void addHandlerMethod(Method method, Object instance) { + instanceMap.put(method, instance); + Class eventObjectClass = getParameterTypeClass(method); + if (!eventHandlerMap.containsKey(eventObjectClass)) { + eventHandlerMap.put(eventObjectClass, new CopyOnWriteArraySet<>()); + } + eventHandlerMap.get(eventObjectClass).add(method); + } + + private Class getParameterTypeClass(Method method) { + Class parameterType = method.getParameterTypes()[0]; + if (EventObject.class.isAssignableFrom(parameterType)) { + throw new IllegalArgumentException("Wrong parameter type: " + parameterType.getName()); + } + return parameterType.asSubclass(EventObject.class); + } + + @Override + public Map getMatchedHandlerMethod(EventObject event) { + Map result = new HashMap<>(0); + Set methods = findHandleMethod(event.getClass()); + for (Method method : methods) { + result.put(method, instanceMap.get(method)); + } + return result; + } + + /** + * 查找可处理的方法. + * @param eventClass 事件 Class 对象. + * @return 返回存储了可处理方法的集合. + */ + private Set findHandleMethod(Class eventClass) { + Set methods = new HashSet<>(); + for (Class clazz : eventHandlerMap.keySet()) { + if (!clazz.isAssignableFrom(eventClass)) { + continue; + } + + Set eventHandlers = eventHandlerMap.get(clazz); + for (Method handlerMethod : eventHandlers) { + EventHandler handlerInfo = handlerMethod.getAnnotation(EventHandler.class); + if (handlerInfo.inheritable()) { + methods.add(handlerMethod); + } + } + } + return methods; + } + +} diff --git a/ContentGrabbingJi-core/src/main/java/net/lamgc/cgj/bot/event/MessageEventHandler.java b/ContentGrabbingJi-core/src/main/java/net/lamgc/cgj/bot/event/MessageEventHandler.java new file mode 100644 index 0000000..c5ad818 --- /dev/null +++ b/ContentGrabbingJi-core/src/main/java/net/lamgc/cgj/bot/event/MessageEventHandler.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2020 LamGC + * + * ContentGrabbingJi is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * ContentGrabbingJi 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package net.lamgc.cgj.bot.event; + +import net.lamgc.cgj.bot.framework.message.AbstractMessageEvent; + +/** + * 消息事件处理类. + *

该类将接受原始消息事件, 经消息处理器处理后返回. + * @author LamGC + */ +public class MessageEventHandler { + + @EventHandler(inheritable = true) + public void onHandle(AbstractMessageEvent event) { + + } + +} diff --git a/ContentGrabbingJi-core/src/main/java/net/lamgc/cgj/bot/event/ThreadPoolEventExecutor.java b/ContentGrabbingJi-core/src/main/java/net/lamgc/cgj/bot/event/ThreadPoolEventExecutor.java new file mode 100644 index 0000000..d653c29 --- /dev/null +++ b/ContentGrabbingJi-core/src/main/java/net/lamgc/cgj/bot/event/ThreadPoolEventExecutor.java @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2020 LamGC + * + * ContentGrabbingJi is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * ContentGrabbingJi 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package net.lamgc.cgj.bot.event; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ThreadPoolExecutor; + +/** + * 基于线程池, 以事件为单位的异步事件执行器. + *

以事件为单位对于 ContentGrabbingJi 来说是实现难度低, 还很合适的, + * ContentGrabbingJi 只会注册一个 Handler, 所以依然类似于一个 Handler 一个线程. + * @author LamGC + */ +public class ThreadPoolEventExecutor implements EventExecutor { + + private final ThreadPoolExecutor threadExecutor; + private final EventHandlerRegistry registry; + + /** + * 构造线程池事件执行器. + * @param threadExecutor 执行器所使用的线程池. + * @param registry 事件处理注册器. + */ + public ThreadPoolEventExecutor(ThreadPoolExecutor threadExecutor, EventHandlerRegistry registry) { + this.threadExecutor = threadExecutor; + this.registry = registry; + } + + @Override + public void execute(EventObject event) { + threadExecutor.execute(new ExecuteRunnable(event, registry.getMatchedHandlerMethod(event))); + } + + @Override + public boolean isAsync() { + return true; + } + + /** + * 按事件为单位的事件处理执行类. + */ + private final static class ExecuteRunnable implements Runnable { + + private final static Logger log = LoggerFactory.getLogger(ExecuteRunnable.class); + + private final EventObject event; + private final Map handlerMethods; + + private ExecuteRunnable(EventObject event, Map handlerMethods) { + this.event = Objects.requireNonNull(event); + this.handlerMethods = Objects.requireNonNull(handlerMethods); + } + + @Override + public void run() { + if (event instanceof Cancelable) { + runCancelableEvent(); + } else { + runOrdinaryEvent(); + } + } + + /** + * 运行可取消事件. + */ + private void runCancelableEvent() { + Cancelable cancelable = (Cancelable) event; + for (Method handlerMethod : handlerMethods.keySet()) { + if (cancelable.canceled()) { + log.warn("事件 {} 已取消, 终止处理.", event); + break; + } + + try { + Object instance = handlerMethods.get(handlerMethod); + checkInstance(handlerMethod, instance); + handlerMethod.invoke(instance, event); + } catch (InvocationTargetException e) { + log.error("Handler '" + handlerMethod.getDeclaringClass() + "." + + handlerMethod.getName() + "()'" +" throws an uncaught exception when handling an event", + e); + } catch (Exception e) { + log.error("Exception in handler '" + handlerMethod.getDeclaringClass() + "." + + handlerMethod.getName() + "()' call", e); + } + } + } + + /** + * 运行普通事件. + */ + private void runOrdinaryEvent() { + handlerMethods.forEach((method, instance) -> { + try { + checkInstance(method, instance); + method.invoke(instance, event); + } catch (InvocationTargetException e) { + log.error("Handler '" + method.getDeclaringClass() + "." + method.getName() + "()'" + + " throws an uncaught exception when handling an event", e); + } catch (Exception e) { + log.error("Exception in handler '" + method.getDeclaringClass() + "." + + method.getName() + "()' call", e); + } + }); + } + + private void checkInstance(Method method, Object object) { + if (!method.getDeclaringClass().isAssignableFrom(object.getClass())) { + throw new ClassCastException("Method declaration class does not match call instance (Method: '" + + method.getDeclaringClass().getName() + "', Instance: '" + object.getClass().getName() + "')"); + } + } + + } + +}