From db13460ae45be85d9047574af6deecd18ab44239 Mon Sep 17 00:00:00 2001 From: wzp Date: Fri, 16 Aug 2024 01:06:45 +0800 Subject: [PATCH] feat: adding event handler system --- .idea/compiler.xml | 2 +- .idea/fileTemplates/includes/Version.txt | 2 +- .idea/modules.xml | 1 - build.gradle.kts | 2 +- src/main/java/cn/wzpmc/Main.java | 86 +++++++++++++++-- .../java/cn/wzpmc/api/plugins/BasePlugin.java | 5 + .../wzpmc/api/plugins/IPluginClassLoader.java | 16 ++++ .../cn/wzpmc/api/plugins/IPluginManager.java | 25 +++++ .../java/cn/wzpmc/api/plugins/JavaPlugin.java | 38 ++++++++ .../wzpmc/api/plugins/event/EventHandler.java | 17 ++++ src/main/java/cn/wzpmc/api/user/IBot.java | 19 ++++ .../cn/wzpmc/api/utils/IncreasbleHashMap.java | 65 +++++++++++++ .../cn/wzpmc/api/utils/IncreasbleMap.java | 58 ++++++++++++ .../console/logger/PluginMessageFactory.java | 49 ++++++++++ .../entities/event/EventHandlerMethod.java | 16 ++++ .../cn/wzpmc/entities/user/bot/MyBot.java | 27 ++++++ .../java/cn/wzpmc/network/PacketHandler.java | 25 ++++- .../network/WebSocketConnectionHandler.java | 6 +- .../java/cn/wzpmc/plugins/CommandManager.java | 8 +- .../java/cn/wzpmc/plugins/JavaPlugin.java | 23 ----- .../cn/wzpmc/plugins/PluginClassLoader.java | 13 ++- .../java/cn/wzpmc/plugins/PluginManager.java | 37 ++++++++ .../java/cn/wzpmc/utils/ReflectionUtils.java | 92 +++++++++++++++++++ .../cn/wzpmc/utils/TemplateFileUtils.java | 18 ++++ src/test/java/DemoEventHandler.java | 42 +++++++++ src/test/java/TestEventHandle.java | 32 +++++++ src/test/java/TestEventHandler.java | 18 ++++ 27 files changed, 695 insertions(+), 47 deletions(-) create mode 100644 src/main/java/cn/wzpmc/api/plugins/IPluginManager.java create mode 100644 src/main/java/cn/wzpmc/api/plugins/JavaPlugin.java create mode 100644 src/main/java/cn/wzpmc/api/plugins/event/EventHandler.java create mode 100644 src/main/java/cn/wzpmc/api/utils/IncreasbleHashMap.java create mode 100644 src/main/java/cn/wzpmc/api/utils/IncreasbleMap.java create mode 100644 src/main/java/cn/wzpmc/console/logger/PluginMessageFactory.java create mode 100644 src/main/java/cn/wzpmc/entities/event/EventHandlerMethod.java delete mode 100644 src/main/java/cn/wzpmc/plugins/JavaPlugin.java create mode 100644 src/main/java/cn/wzpmc/plugins/PluginManager.java create mode 100644 src/main/java/cn/wzpmc/utils/ReflectionUtils.java create mode 100644 src/test/java/DemoEventHandler.java create mode 100644 src/test/java/TestEventHandle.java create mode 100644 src/test/java/TestEventHandler.java diff --git a/.idea/compiler.xml b/.idea/compiler.xml index 53eb9c3..ef4060e 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -10,6 +10,6 @@ - + \ No newline at end of file diff --git a/.idea/fileTemplates/includes/Version.txt b/.idea/fileTemplates/includes/Version.txt index 37e50c3..3d287b9 100644 --- a/.idea/fileTemplates/includes/Version.txt +++ b/.idea/fileTemplates/includes/Version.txt @@ -1 +1 @@ -0.0.3-dev \ No newline at end of file +0.0.4-dev \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml index 6f5f4dd..f55fa3e 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -2,7 +2,6 @@ - diff --git a/build.gradle.kts b/build.gradle.kts index d993c9f..13019be 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -4,7 +4,7 @@ import com.github.jengelman.gradle.plugins.shadow.transformers.Log4j2PluginsCach val projectName = rootProject.name val groupName by extra("cn.wzpmc") val projectArtifactId by extra("my-bot") -val projectVersion by extra("0.0.3-dev") +val projectVersion by extra("0.0.4-dev-SNAPSHOT") plugins { id("java") diff --git a/src/main/java/cn/wzpmc/Main.java b/src/main/java/cn/wzpmc/Main.java index 2146953..651e645 100644 --- a/src/main/java/cn/wzpmc/Main.java +++ b/src/main/java/cn/wzpmc/Main.java @@ -1,12 +1,16 @@ package cn.wzpmc; +import cn.wzpmc.api.plugins.BasePlugin; import cn.wzpmc.commands.StopCommand; import cn.wzpmc.configuration.Configuration; import cn.wzpmc.console.MyBotConsole; import cn.wzpmc.entities.user.bot.MyBot; import cn.wzpmc.network.WebSocketConnectionHandler; import cn.wzpmc.plugins.CommandManager; +import cn.wzpmc.plugins.PluginClassLoader; +import cn.wzpmc.plugins.PluginManager; import cn.wzpmc.utils.JsonUtils; +import cn.wzpmc.utils.ReflectionUtils; import cn.wzpmc.utils.TemplateFileUtils; import cn.wzpmc.utils.YamlUtils; import io.netty.channel.ChannelFuture; @@ -14,40 +18,102 @@ import lombok.SneakyThrows; import lombok.extern.log4j.Log4j2; import java.io.File; +import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; +import java.net.URL; @Log4j2 public class Main { private static final String DEFAULT_CONFIGURATION_FILE_PATH = "templates/config.yaml"; - @SneakyThrows - public static void main(String[] args) { + private static File pluginsDir; + public static void initializeJVM(){ System.setProperty("java.util.logging.manager", "org.apache.logging.log4j.jul.LogManager"); System.setProperty("terminal.jline", "true"); + } + public static void initializeJsonUtils(){ JsonUtils.initReader(); JsonUtils.initWriter(); - log.info("启动MyBot..."); + } + public static Configuration getConfiguration() { File configurationFile = new File("config.yaml"); if (TemplateFileUtils.saveDefaultConfig(Main.class.getClassLoader(), DEFAULT_CONFIGURATION_FILE_PATH, configurationFile)) { log.debug("创建日志文件成功!"); log.info("首次启动,默认配置文件已创建,请填写后再次启动MyBot!"); - return; + return null; + } + pluginsDir = new File("plugins"); + if (TemplateFileUtils.createDefaultDirectory(pluginsDir)) { + log.debug("plugin文件夹创建"); } log.debug("读取配置文件 {}", configurationFile.getAbsolutePath()); - Configuration configuration = YamlUtils.readYamlFile(configurationFile, Configuration.class); + return YamlUtils.readYamlFile(configurationFile, Configuration.class); + } + public static MyBot createBot(Configuration configuration){ + return new MyBot(configuration); + } + public static URI getUriFromConfiguration(Configuration configuration){ URI uri; try { - uri = new URI(configuration.getWebsocket()); + uri = new URI(configuration.getWebsocket()); } catch (URISyntaxException e) { - log.error("无法解析websocket地址"); + return null; + } + return uri; + } + public static void loadPlugins(MyBot myBot) throws MalformedURLException { + File[] files = pluginsDir.listFiles(); + if (files == null) { + log.error("没有权限读取插件目录!"); return; } - WebSocketConnectionHandler webSocketConnectionHandler = new WebSocketConnectionHandler(); - ChannelFuture future = webSocketConnectionHandler.connect(uri); - MyBot myBot = new MyBot(configuration); + PluginManager pluginManager = myBot.getPluginManager(); + for (File file : files) { + if (file.isDirectory()) { + continue; + } + String name = file.getName(); + if (!name.endsWith(".jar")) { + continue; + } + URI fileURI = file.toURI(); + PluginClassLoader pluginClassLoader = new PluginClassLoader(new URL[]{fileURI.toURL()}, myBot); + BasePlugin load = ReflectionUtils.load(pluginClassLoader, file, pluginManager); + if (load == null){ + log.info("插件{}加载失败!", name); + continue; + } + load.onLoad(); + } CommandManager commandManager = myBot.getCommandManager(); commandManager.registerCommand(new StopCommand(myBot)); + } + public static WebSocketConnectionHandler createConnection(MyBot myBot, URI uri){ + WebSocketConnectionHandler webSocketConnectionHandler = new WebSocketConnectionHandler(myBot); + ChannelFuture future = webSocketConnectionHandler.connect(uri); + return webSocketConnectionHandler; + } + public static void startConsole(MyBot myBot, WebSocketConnectionHandler webSocketConnectionHandler){ MyBotConsole myBotConsole = new MyBotConsole(myBot, webSocketConnectionHandler); myBotConsole.start(); } + @SneakyThrows + public static void main(String[] args) { + initializeJVM(); + initializeJsonUtils(); + log.info("启动MyBot..."); + Configuration configuration = getConfiguration(); + if (configuration == null){ + return; + } + MyBot myBot = createBot(configuration); + URI uri = getUriFromConfiguration(configuration); + if (uri == null){ + log.error("无法解析websocket地址"); + return; + } + loadPlugins(myBot); + WebSocketConnectionHandler webSocketConnectionHandler = createConnection(myBot, uri); + startConsole(myBot, webSocketConnectionHandler); + } } \ No newline at end of file diff --git a/src/main/java/cn/wzpmc/api/plugins/BasePlugin.java b/src/main/java/cn/wzpmc/api/plugins/BasePlugin.java index 7d572ce..6ab5f64 100644 --- a/src/main/java/cn/wzpmc/api/plugins/BasePlugin.java +++ b/src/main/java/cn/wzpmc/api/plugins/BasePlugin.java @@ -1,6 +1,7 @@ package cn.wzpmc.api.plugins; import cn.wzpmc.api.user.IBot; +import org.apache.logging.log4j.Logger; /** * 插件基类 @@ -45,4 +46,8 @@ public interface BasePlugin { * @return 类加载器 */ IPluginClassLoader getClassLoader(); + + void onLoad(); + void onUnload(); + Logger getLogger(); } diff --git a/src/main/java/cn/wzpmc/api/plugins/IPluginClassLoader.java b/src/main/java/cn/wzpmc/api/plugins/IPluginClassLoader.java index 1a72633..8c78879 100644 --- a/src/main/java/cn/wzpmc/api/plugins/IPluginClassLoader.java +++ b/src/main/java/cn/wzpmc/api/plugins/IPluginClassLoader.java @@ -32,4 +32,20 @@ public abstract class IPluginClassLoader extends URLClassLoader { * @return Bot对象 */ abstract public IBot getBot(); + + /** + * 获取插件名称 + * @author wzp + * @since 2024/8/8 23:16 v0.0.4-dev + * @return 插件名称 + */ + abstract public String getName(); + + /** + * 获取插件版本 + * @author wzp + * @since 2024/8/8 23:16 v0.0.4-dev + * @return 版本 + */ + abstract public String getVersion(); } diff --git a/src/main/java/cn/wzpmc/api/plugins/IPluginManager.java b/src/main/java/cn/wzpmc/api/plugins/IPluginManager.java new file mode 100644 index 0000000..655a491 --- /dev/null +++ b/src/main/java/cn/wzpmc/api/plugins/IPluginManager.java @@ -0,0 +1,25 @@ +package cn.wzpmc.api.plugins; + +import java.lang.reflect.InvocationTargetException; +import java.util.List; + +/** + * @author wzp + * @version 0.0.4-dev + * @since 2024/8/6 下午3:19 + */ +public interface IPluginManager { + /** + * 初始化插件主类 + * + * @param 插件主类类型 + * @param baseClass 插件主类 + * @param name 插件名称 + * @param version 插件版本 + * @return 这个插件的实例 + * @author wzp + * @since 2024/8/5 上午12:58 v0.0.4-dev + */ + T initPlugin(Class baseClass, String name, String version) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException; + List getPlugins(); +} diff --git a/src/main/java/cn/wzpmc/api/plugins/JavaPlugin.java b/src/main/java/cn/wzpmc/api/plugins/JavaPlugin.java new file mode 100644 index 0000000..92d0580 --- /dev/null +++ b/src/main/java/cn/wzpmc/api/plugins/JavaPlugin.java @@ -0,0 +1,38 @@ +package cn.wzpmc.api.plugins; + +import lombok.Getter; +import lombok.Setter; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +/** + * Java插件基类 + * @author wzp + * @version 0.0.2-dev + * @since 2024/7/31 下午7:01 + */ +@Setter +@Getter +public abstract class JavaPlugin implements BasePlugin { + private Logger logger; + @Override + public IPluginClassLoader getClassLoader() { + Class aClass = this.getClass(); + ClassLoader loader = aClass.getClassLoader(); + if (loader instanceof IPluginClassLoader){ + return (IPluginClassLoader) loader; + } + throw new RuntimeException(new IllegalAccessException("You shouldn't load plugin class without PluginClassLoader!!!")); + } + + @Override + public Logger getLogger() { + IPluginClassLoader classLoader = this.getClassLoader(); + String name = classLoader.getName(); + String version = classLoader.getVersion(); + if (this.logger == null){ + this.logger = LogManager.getLogger(name + "-" + version); + } + return this.logger; + } +} diff --git a/src/main/java/cn/wzpmc/api/plugins/event/EventHandler.java b/src/main/java/cn/wzpmc/api/plugins/event/EventHandler.java new file mode 100644 index 0000000..f4ecd40 --- /dev/null +++ b/src/main/java/cn/wzpmc/api/plugins/event/EventHandler.java @@ -0,0 +1,17 @@ +package cn.wzpmc.api.plugins.event; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 使用此注解以指定一个方法为事件执行器 + * @author wzp + * @version 0.0.4-dev + * @since 2024/8/15 23:47 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface EventHandler { +} diff --git a/src/main/java/cn/wzpmc/api/user/IBot.java b/src/main/java/cn/wzpmc/api/user/IBot.java index a94ab31..ff1f7f2 100644 --- a/src/main/java/cn/wzpmc/api/user/IBot.java +++ b/src/main/java/cn/wzpmc/api/user/IBot.java @@ -1,8 +1,11 @@ package cn.wzpmc.api.user; +import cn.wzpmc.api.events.Event; import cn.wzpmc.api.plugins.ICommandManager; import cn.wzpmc.api.plugins.configuration.IConfiguration; +import java.lang.reflect.InvocationTargetException; + /** * 机器人接口 * @author wzp @@ -32,4 +35,20 @@ public abstract class IBot extends CommandSender { * @since 2024/8/1 下午4:57 v0.0.2-dev */ public abstract void stop(); + + /** + * 注册事件执行器 + * @author wzp + * @since 2024/8/15 23:46 v0.0.4-dev + * @param handler 事件执行器 + */ + public abstract void registerEventHandler(Object handler); + + /** + * 触发一个事件 + * @author wzp + * @since 2024/8/16 00:49 v0.0.4-dev + * @param event 事件 + */ + public abstract void triggerEvent(Event event) throws InvocationTargetException, IllegalAccessException; } diff --git a/src/main/java/cn/wzpmc/api/utils/IncreasbleHashMap.java b/src/main/java/cn/wzpmc/api/utils/IncreasbleHashMap.java new file mode 100644 index 0000000..3bc30bc --- /dev/null +++ b/src/main/java/cn/wzpmc/api/utils/IncreasbleHashMap.java @@ -0,0 +1,65 @@ +package cn.wzpmc.api.utils; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; + +/** + * 一个单Key对应多Value的HashMap + * @author wzp + * @version 0.0.4-dev + * @since 2024/8/16 00:02 + */ +public class IncreasbleHashMap extends HashMap> implements IncreasbleMap{ + @Override + public void add(K key, V value) { + List newArrayList = super.getOrDefault(key, new ArrayList<>()); + newArrayList.add(value); + super.put(key, newArrayList); + } + + @Override + public boolean delete(K key, V value) { + if (!super.containsKey(key)) { + return false; + } + List vs = super.get(key); + return vs.remove(value); + } + + @Override + public boolean delete(V value) { + boolean has = false; + for (List vs : super.values()) { + if (vs.contains(value)){ + has = true; + vs.remove(value); + } + } + return has; + } + + @Override + public void addAll(IncreasbleMap increasbleMap) { + for (Entry> entry : increasbleMap.entrySet()) { + this.addAll(entry.getKey(), entry.getValue()); + } + } + @Override + public void addAll(K key, Collection values) { + List list = this.getOrDefault(key, new ArrayList<>()); + list.addAll(values); + this.put(key, list); + } + + @Override + public boolean containsValue(Object value) { + for (List vs : super.values()) { + if (vs.contains(value)){ + return true; + } + } + return false; + } +} diff --git a/src/main/java/cn/wzpmc/api/utils/IncreasbleMap.java b/src/main/java/cn/wzpmc/api/utils/IncreasbleMap.java new file mode 100644 index 0000000..ef13bf2 --- /dev/null +++ b/src/main/java/cn/wzpmc/api/utils/IncreasbleMap.java @@ -0,0 +1,58 @@ +package cn.wzpmc.api.utils; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +/** + * 一个单Key对应多Value的Map + * @author wzp + * @version 0.0.4-dev + * @since 2024/8/15 23:57 + */ +public interface IncreasbleMap extends Map> { + /** + * 向一个Key中添加元素 + * @author wzp + * @since 2024/8/16 00:04 v0.0.4-dev + * @param key 键 + * @param value 值 + */ + void add(K key, V value); + + /** + * 删除某个key中的对应元素 + * @author wzp + * @since 2024/8/16 00:05 v0.0.4-dev + * @param key 键 + * @param value 值 + * @return 是否删除成功 + */ + boolean delete(K key, V value); + + /** + * 删除所有的对应value的值 + * @author wzp + * @since 2024/8/16 00:05 v0.0.4-dev + * @param value 值 + * @return 是否删除成功 + */ + boolean delete(V value); + + /** + * 将两个表融合 + * @author wzp + * @since 2024/8/16 00:35 v0.0.4-dev + * @param increasbleMap 另一个表 + */ + void addAll(IncreasbleMap increasbleMap); + + /** + * 将所有value添加到此key中 + * @author wzp + * @since 2024/8/16 00:43 v0.0.4-dev + * @param key 键 + * @param values 所有要添加的值的集合 + */ + void addAll(K key, Collection values); +} diff --git a/src/main/java/cn/wzpmc/console/logger/PluginMessageFactory.java b/src/main/java/cn/wzpmc/console/logger/PluginMessageFactory.java new file mode 100644 index 0000000..88f1ead --- /dev/null +++ b/src/main/java/cn/wzpmc/console/logger/PluginMessageFactory.java @@ -0,0 +1,49 @@ +package cn.wzpmc.console.logger; + +import cn.wzpmc.api.plugins.BasePlugin; +import cn.wzpmc.api.plugins.IPluginClassLoader; +import org.apache.logging.log4j.message.*; + +import java.lang.reflect.InvocationTargetException; + +import static org.apache.logging.log4j.spi.AbstractLogger.DEFAULT_FLOW_MESSAGE_FACTORY_CLASS; + +/** + * @author wzp + * @version 0.0.4-dev + * @since 2024/8/9 00:35 + */ +public class PluginMessageFactory implements MessageFactory { + private final String tag; + private static final MessageFactory baseFactory; + + static { + try { + baseFactory = (MessageFactory) DEFAULT_FLOW_MESSAGE_FACTORY_CLASS.getConstructor().newInstance(); + } catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) { + throw new RuntimeException(e); + } + } + + public PluginMessageFactory(BasePlugin plugin) { + IPluginClassLoader classLoader = plugin.getClassLoader(); + String name = classLoader.getName(); + String version = classLoader.getVersion(); + this.tag = '[' + name + '-' + version + "] "; + } + + @Override + public Message newMessage(Object message) { + return baseFactory.newMessage(tag + "{}", message); + } + + @Override + public Message newMessage(String message) { + return baseFactory.newMessage(this.tag + message); + } + + @Override + public Message newMessage(String message, Object... params) { + return baseFactory.newMessage(this.tag + message, params); + } +} diff --git a/src/main/java/cn/wzpmc/entities/event/EventHandlerMethod.java b/src/main/java/cn/wzpmc/entities/event/EventHandlerMethod.java new file mode 100644 index 0000000..db59cee --- /dev/null +++ b/src/main/java/cn/wzpmc/entities/event/EventHandlerMethod.java @@ -0,0 +1,16 @@ +package cn.wzpmc.entities.event; + +import lombok.Data; + +import java.lang.reflect.Method; + +/** + * @author wzp + * @version 0.0.4-dev + * @since 2024/8/15 23:53 + */ +@Data +public class EventHandlerMethod { + private final Object object; + private final Method method; +} diff --git a/src/main/java/cn/wzpmc/entities/user/bot/MyBot.java b/src/main/java/cn/wzpmc/entities/user/bot/MyBot.java index 93a8681..0449394 100644 --- a/src/main/java/cn/wzpmc/entities/user/bot/MyBot.java +++ b/src/main/java/cn/wzpmc/entities/user/bot/MyBot.java @@ -1,17 +1,26 @@ package cn.wzpmc.entities.user.bot; +import cn.wzpmc.api.events.Event; import cn.wzpmc.api.message.MessageComponent; import cn.wzpmc.api.message.StringMessage; import cn.wzpmc.api.message.json.JsonMessage; +import cn.wzpmc.api.plugins.BasePlugin; import cn.wzpmc.api.user.IBot; import cn.wzpmc.api.user.permission.Permissions; +import cn.wzpmc.api.utils.IncreasbleHashMap; import cn.wzpmc.configuration.Configuration; import cn.wzpmc.console.MyBotConsole; +import cn.wzpmc.entities.event.EventHandlerMethod; import cn.wzpmc.plugins.CommandManager; +import cn.wzpmc.plugins.PluginManager; +import cn.wzpmc.utils.ReflectionUtils; import lombok.Getter; import lombok.Setter; import lombok.extern.log4j.Log4j2; +import java.lang.reflect.InvocationTargetException; +import java.util.List; + /** * 机器人实现类 * @author wzp @@ -27,6 +36,8 @@ public class MyBot extends IBot { @Setter private String name; private final CommandManager commandManager = new CommandManager(this); + private final PluginManager pluginManager = new PluginManager(); + private final IncreasbleHashMap, EventHandlerMethod> events = new IncreasbleHashMap<>(); @Setter private MyBotConsole console = null; public MyBot(Configuration configuration){ @@ -46,8 +57,24 @@ public class MyBot extends IBot { @Override public void stop() { + for (BasePlugin plugin : this.pluginManager.getPlugins()) { + plugin.onUnload(); + } if (this.console != null) { this.console.shutdown(); } } + + @Override + public void registerEventHandler(Object handler) { + this.events.addAll(ReflectionUtils.loadEvents(handler)); + } + + @Override + public void triggerEvent(Event event) throws InvocationTargetException, IllegalAccessException { + List eventHandlerMethods = this.events.get(event.getClass()); + for (EventHandlerMethod eventHandlerMethod : eventHandlerMethods) { + eventHandlerMethod.getMethod().invoke(eventHandlerMethod.getObject(), event); + } + } } diff --git a/src/main/java/cn/wzpmc/network/PacketHandler.java b/src/main/java/cn/wzpmc/network/PacketHandler.java index cd4b525..e6c49a4 100644 --- a/src/main/java/cn/wzpmc/network/PacketHandler.java +++ b/src/main/java/cn/wzpmc/network/PacketHandler.java @@ -1,12 +1,17 @@ package cn.wzpmc.network; import cn.wzpmc.api.events.Event; +import cn.wzpmc.api.user.IBot; import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; +import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; +import java.lang.reflect.InvocationTargetException; + /** * websocket包处理器 * @author wzp @@ -14,7 +19,9 @@ import lombok.extern.log4j.Log4j2; * @since 2024/7/31 上午12:14 */ @Log4j2 +@RequiredArgsConstructor public class PacketHandler extends SimpleChannelInboundHandler { + private final IBot bot; @Override protected void channelRead0(ChannelHandlerContext channelHandlerContext, TextWebSocketFrame webSocketFrame) { @@ -23,8 +30,22 @@ public class PacketHandler extends SimpleChannelInboundHandler aClass = this.getClass(); - ClassLoader loader = aClass.getClassLoader(); - if (loader instanceof IPluginClassLoader){ - return (IPluginClassLoader) loader; - } - throw new RuntimeException(new IllegalAccessException("You shouldn't load plugin class without PluginClassLoader!!!")); - } -} diff --git a/src/main/java/cn/wzpmc/plugins/PluginClassLoader.java b/src/main/java/cn/wzpmc/plugins/PluginClassLoader.java index 528faec..e7f0544 100644 --- a/src/main/java/cn/wzpmc/plugins/PluginClassLoader.java +++ b/src/main/java/cn/wzpmc/plugins/PluginClassLoader.java @@ -9,6 +9,7 @@ import lombok.Setter; import lombok.ToString; import java.net.URL; +import java.util.Objects; /** * 插件类加载器实现 @@ -21,10 +22,20 @@ import java.net.URL; @ToString public class PluginClassLoader extends IPluginClassLoader { private final IBot bot; - @Setter private BasePlugin plugin; + private String name; + private String version; public PluginClassLoader(URL[] urls, IBot bot) { super(urls); this.bot = bot; } + public void setPlugin(BasePlugin plugin, String name, String version){ + if (Objects.isNull(this.plugin)){ + this.plugin = plugin; + this.name = name; + this.version = version; + return; + } + throw new IllegalStateException("Cannot set plugin with a initialized class loader!!!"); + } } diff --git a/src/main/java/cn/wzpmc/plugins/PluginManager.java b/src/main/java/cn/wzpmc/plugins/PluginManager.java new file mode 100644 index 0000000..cbac7ac --- /dev/null +++ b/src/main/java/cn/wzpmc/plugins/PluginManager.java @@ -0,0 +1,37 @@ +package cn.wzpmc.plugins; + +import cn.wzpmc.api.plugins.BasePlugin; +import cn.wzpmc.api.plugins.IPluginManager; +import lombok.Getter; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * 插件管理器 + * @author wzp + * @since 2024/8/4 下午2:38 + * @version 0.0.4-dev + */ +@Getter +public class PluginManager implements IPluginManager { + private final List plugins = new ArrayList<>(); + public T initPlugin(Class baseClass, String name, String version) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { + ClassLoader loader = baseClass.getClassLoader(); + if (!(loader instanceof PluginClassLoader)){ + throw new IllegalArgumentException("baseClass " + baseClass.getName() + " must be loaded with plugin class loader!!!"); + } + PluginClassLoader pluginClassLoader = (PluginClassLoader) loader; + if (Objects.nonNull(pluginClassLoader.getPlugin())){ + throw new IllegalStateException("baseClass " + baseClass.getName() + " has already been initialization!!!"); + } + Constructor constructor = baseClass.getConstructor(); + T t = constructor.newInstance(); + pluginClassLoader.setPlugin(t, name, version); + plugins.add(t); + return t; + } +} diff --git a/src/main/java/cn/wzpmc/utils/ReflectionUtils.java b/src/main/java/cn/wzpmc/utils/ReflectionUtils.java new file mode 100644 index 0000000..0f382a5 --- /dev/null +++ b/src/main/java/cn/wzpmc/utils/ReflectionUtils.java @@ -0,0 +1,92 @@ +package cn.wzpmc.utils; + +import cn.wzpmc.api.events.Event; +import cn.wzpmc.api.plugins.BasePlugin; +import cn.wzpmc.api.plugins.IPluginManager; +import cn.wzpmc.api.plugins.event.EventHandler; +import cn.wzpmc.api.utils.IncreasbleHashMap; +import cn.wzpmc.api.utils.IncreasbleMap; +import cn.wzpmc.entities.event.EventHandlerMethod; +import com.alibaba.fastjson2.JSONObject; +import lombok.extern.log4j.Log4j2; +import org.yaml.snakeyaml.Yaml; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.URLClassLoader; +import java.util.Optional; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +/** + * 反射工具类 + * @author wzp + * @since 2024/8/5 上午1:42 + * @version 0.0.4-dev + */ +@Log4j2 +public class ReflectionUtils { + public static BasePlugin load(URLClassLoader loader, File file, IPluginManager pluginManager){ + String absolutePath = file.getAbsolutePath(); + try(JarFile jarFile = new JarFile(file)){ + Optional first = jarFile.stream().filter((e) -> "plugin.yml".equals(e.getName())).findFirst(); + if (first.isEmpty()){ + log.error("cannot find plugin.yml in plugin {}", absolutePath); + return null; + } + InputStream stream = loader.getResourceAsStream("plugin.yml"); + Yaml yaml = new Yaml(); + JSONObject jsonObject = yaml.loadAs(stream, JSONObject.class); + String main = jsonObject.getString("main"); + String version = jsonObject.getString("version"); + String name = jsonObject.getString("name"); + try{ + Class aClass = loader.loadClass(main); + if (!BasePlugin.class.isAssignableFrom(aClass)) { + log.error("插件{}-{}的主类{}未继承cn.wzpmc.api.plugins.JavaPlugin", name, version, main); + return null; + } + try { + //noinspection unchecked + BasePlugin basePlugin = pluginManager.initPlugin((Class) aClass, name, version); + log.info("插件{}-{}已成功加载", name, version); + return basePlugin; + } catch (NoSuchMethodException | InvocationTargetException | InstantiationException | + IllegalAccessException e) { + log.error(e); + return null; + } + }catch (ClassNotFoundException e) { + log.error("无法为插件{}-{}加载主类{}!", name, version, main); + return null; + } + + } catch (IOException e) { + log.error(e); + return null; + } + } + public static IncreasbleMap, EventHandlerMethod> loadEvents(Object eventHandlerObject){ + Class eventHandlerClass = eventHandlerObject.getClass(); + IncreasbleMap, EventHandlerMethod> result = new IncreasbleHashMap<>(); + for (Method declaredMethod : eventHandlerClass.getDeclaredMethods()) { + declaredMethod.setAccessible(true); + if (!declaredMethod.isAnnotationPresent(EventHandler.class)){ + continue; + } + if (declaredMethod.getParameterCount() == 1) { + Class eventType = declaredMethod.getParameterTypes()[0]; + if (Event.class.isAssignableFrom(eventType)){ + //noinspection unchecked + result.add((Class) eventType, new EventHandlerMethod(eventHandlerObject, declaredMethod)); + continue; + } + } + log.warn("Not event method {}::{} shouldn't been annotations with @EventHandler", eventHandlerClass, declaredMethod.getName()); + } + return result; + } +} diff --git a/src/main/java/cn/wzpmc/utils/TemplateFileUtils.java b/src/main/java/cn/wzpmc/utils/TemplateFileUtils.java index 75cd5dd..c7ce6a3 100644 --- a/src/main/java/cn/wzpmc/utils/TemplateFileUtils.java +++ b/src/main/java/cn/wzpmc/utils/TemplateFileUtils.java @@ -40,4 +40,22 @@ public class TemplateFileUtils { throw new RuntimeException(e); } } + + /** + * 创建默认文件夹,当其不存在时会被创建 + * @author wzp + * @since 2024/8/4 下午2:36 v0.0.4-dev + * @param path 文件夹路径 + * @return 是否创建 + */ + public static boolean createDefaultDirectory(File path){ + log.debug("创建文件夹:{}", path.getAbsolutePath()); + if (path.isDirectory()){ + return false; + } + if (path.mkdir()) { + return true; + } + throw new RuntimeException(new IOException("Cannot create directory " + path.getAbsolutePath())); + } } diff --git a/src/test/java/DemoEventHandler.java b/src/test/java/DemoEventHandler.java new file mode 100644 index 0000000..e664b84 --- /dev/null +++ b/src/test/java/DemoEventHandler.java @@ -0,0 +1,42 @@ +import cn.wzpmc.api.events.Event; +import cn.wzpmc.api.events.message.priv.PrivateMessageEvent; +import cn.wzpmc.api.events.notice.notify.PokeNotifyEvent; +import cn.wzpmc.api.plugins.event.EventHandler; + +/** + * @author wzp + * @version 0.0.4-dev + * @since 2024/8/16 01:02 + */ +public final class DemoEventHandler { + @EventHandler + public void onMessage(PrivateMessageEvent event) { + System.out.println(event); + System.out.println("Called 1"); + } + @EventHandler + public void onMessage2(PrivateMessageEvent event) { + System.out.println(event); + System.out.println("Called 2"); + } + @EventHandler + public void onPoke(PokeNotifyEvent event) { + System.out.println(event); + System.out.println("Called poke"); + } + public void otherMethod(Event event){ + System.err.println(event); + System.err.println("otherMethod shouldn't called!"); + } + @EventHandler + public void wrongMethod(Event event, String wrongArgs){ + System.err.println(event); + System.err.println(wrongArgs); + System.err.println("wrongMethod shouldn't called!"); + } + @EventHandler + public void wrongMethod(String wrongArgs){ + System.err.println(wrongArgs); + System.err.println("wrongMethod2 shouldn't called!"); + } +} diff --git a/src/test/java/TestEventHandle.java b/src/test/java/TestEventHandle.java new file mode 100644 index 0000000..678dac2 --- /dev/null +++ b/src/test/java/TestEventHandle.java @@ -0,0 +1,32 @@ +import cn.wzpmc.Main; +import cn.wzpmc.api.events.message.priv.PrivateMessageEvent; +import cn.wzpmc.api.events.notice.notify.PokeNotifyEvent; +import cn.wzpmc.api.user.Friend; +import cn.wzpmc.configuration.Configuration; +import cn.wzpmc.entities.user.bot.MyBot; +import org.junit.jupiter.api.Test; + +import java.lang.reflect.InvocationTargetException; + +/** + * @author wzp + * @version 0.0.4-dev + * @since 2024/8/16 01:01 + */ +public class TestEventHandle { + @Test + public void testEventHandle() throws InvocationTargetException, IllegalAccessException { + Configuration configuration = Main.getConfiguration(); + Friend targetUser = new Friend(); + targetUser.setNickname("test"); + targetUser.setId(Long.valueOf(0)); + MyBot bot = Main.createBot(configuration); + bot.registerEventHandler(new DemoEventHandler()); + PokeNotifyEvent pokeNotifyEvent = new PokeNotifyEvent(); + pokeNotifyEvent.setTargetId(Long.valueOf(0)); + bot.triggerEvent(pokeNotifyEvent); + PrivateMessageEvent privateMessageEvent = new PrivateMessageEvent(); + privateMessageEvent.setSender(targetUser); + bot.triggerEvent(privateMessageEvent); + } +} diff --git a/src/test/java/TestEventHandler.java b/src/test/java/TestEventHandler.java new file mode 100644 index 0000000..6967b58 --- /dev/null +++ b/src/test/java/TestEventHandler.java @@ -0,0 +1,18 @@ +import cn.wzpmc.api.events.Event; +import cn.wzpmc.api.events.message.priv.PrivateMessageEvent; +import cn.wzpmc.api.events.notice.notify.PokeNotifyEvent; +import cn.wzpmc.api.plugins.event.EventHandler; +import cn.wzpmc.utils.ReflectionUtils; +import org.junit.jupiter.api.Test; + +/** + * @author wzp + * @version 0.0.4-dev + * @since 2024/8/16 00:12 + */ +public class TestEventHandler { + @Test + public void testEventHandler() { + System.out.println(ReflectionUtils.loadEvents(new DemoEventHandler())); + } +}