From 85f2bd5a100a083a5dd1b3f7603dfdcf88261b30 Mon Sep 17 00:00:00 2001 From: MiaoWoo Date: Tue, 13 Oct 2020 09:24:58 +0800 Subject: [PATCH] feat: optimize framework & support depends check Signed-off-by: MiaoWoo --- packages/api/src/{interfaces => }/amqp.ts | 0 packages/api/src/command.ts | 2 +- packages/api/src/constants.ts | 10 ++ packages/api/src/{interfaces => }/database.ts | 0 packages/api/src/index.ts | 6 +- packages/api/src/interfaces/index.ts | 5 - .../src/interfaces/server/native_plugin.ts | 6 - packages/api/src/{interfaces => }/plugin.ts | 10 +- .../{interfaces/server/index.ts => server.ts} | 61 +++++++--- packages/api/src/{interfaces => }/web.ts | 0 packages/common/src/reflect.ts | 106 ++++++++---------- packages/core/src/index.ts | 1 + packages/nashorn/src/index.ts | 14 ++- packages/ployfill/src/proxy.ts | 12 +- packages/plugin/src/manager.ts | 29 +++-- packages/spring/src/server.ts | 39 +++---- 16 files changed, 173 insertions(+), 128 deletions(-) rename packages/api/src/{interfaces => }/amqp.ts (100%) rename packages/api/src/{interfaces => }/database.ts (100%) delete mode 100644 packages/api/src/interfaces/index.ts delete mode 100644 packages/api/src/interfaces/server/native_plugin.ts rename packages/api/src/{interfaces => }/plugin.ts (96%) rename packages/api/src/{interfaces/server/index.ts => server.ts} (74%) rename packages/api/src/{interfaces => }/web.ts (100%) diff --git a/packages/api/src/interfaces/amqp.ts b/packages/api/src/amqp.ts similarity index 100% rename from packages/api/src/interfaces/amqp.ts rename to packages/api/src/amqp.ts diff --git a/packages/api/src/command.ts b/packages/api/src/command.ts index 5e652d6b..cb4ef843 100644 --- a/packages/api/src/command.ts +++ b/packages/api/src/command.ts @@ -1,6 +1,6 @@ import i18n from '@ccms/i18n' import { injectable } from "@ccms/container" -import { plugin } from './interfaces' +import { plugin } from './plugin' export namespace command { @injectable() diff --git a/packages/api/src/constants.ts b/packages/api/src/constants.ts index a210564d..172eaac0 100644 --- a/packages/api/src/constants.ts +++ b/packages/api/src/constants.ts @@ -1,4 +1,14 @@ export namespace constants { + export namespace ServiceIdentifier { + /** + * Runtime Server NettyPipeline + */ + export const NettyPipeline = Symbol("NettyPipeline") + /** + * Runtime Server RootLogger + */ + export const RootLogger = Symbol("RootLogger") + } export namespace Reflect { export const Method = { getServerConnection: [/*spigot 1.8.8*/'aq',/*spigot 1.12.2*/ 'an', /*spigot 1.14.4+*/'getServerConnection', /*catserver 1.12.2*/'func_147137_ag'] diff --git a/packages/api/src/interfaces/database.ts b/packages/api/src/database.ts similarity index 100% rename from packages/api/src/interfaces/database.ts rename to packages/api/src/database.ts diff --git a/packages/api/src/index.ts b/packages/api/src/index.ts index 01b84754..4a0b3035 100644 --- a/packages/api/src/index.ts +++ b/packages/api/src/index.ts @@ -1,11 +1,15 @@ import "@ccms/nashorn" +export * from './web' +export * from './amqp' export * from './chat' export * from './task' export * from './event' export * from './proxy' +export * from './plugin' +export * from './server' export * from './console' export * from './channel' export * from './command' +export * from './database' export * from './constants' -export * from './interfaces' diff --git a/packages/api/src/interfaces/index.ts b/packages/api/src/interfaces/index.ts deleted file mode 100644 index 24fb9409..00000000 --- a/packages/api/src/interfaces/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from './web' -export * from './amqp' -export * from './plugin' -export * from './server' -export * from './database' diff --git a/packages/api/src/interfaces/server/native_plugin.ts b/packages/api/src/interfaces/server/native_plugin.ts deleted file mode 100644 index 3edb6498..00000000 --- a/packages/api/src/interfaces/server/native_plugin.ts +++ /dev/null @@ -1,6 +0,0 @@ -export interface NativePluginManager { - load(name: string): boolean; - unload(name: string): boolean; - reload(name: string): boolean; - delete(name: string): boolean; -} \ No newline at end of file diff --git a/packages/api/src/interfaces/plugin.ts b/packages/api/src/plugin.ts similarity index 96% rename from packages/api/src/interfaces/plugin.ts rename to packages/api/src/plugin.ts index fcce919c..d45457c0 100644 --- a/packages/api/src/interfaces/plugin.ts +++ b/packages/api/src/plugin.ts @@ -78,7 +78,7 @@ export namespace plugin { scan(target: any): PluginLoadMetadata[] /** * 读取插件 返回插件加载元信息 - * @param target + * @param target */ read(target: any): PluginLoadMetadata /** @@ -162,6 +162,14 @@ export namespace plugin { * 插件作者 不填默认为 Unknow */ author?: string | string[] + /** + * 脚本依赖 + */ + depends?: string[] + /** + * 插件依赖 + */ + nativeDepends?: string[] /** * 插件源文件 必须指定为 __filename */ diff --git a/packages/api/src/interfaces/server/index.ts b/packages/api/src/server.ts similarity index 74% rename from packages/api/src/interfaces/server/index.ts rename to packages/api/src/server.ts index df972ca1..c721b7cb 100644 --- a/packages/api/src/interfaces/server/index.ts +++ b/packages/api/src/server.ts @@ -1,10 +1,7 @@ import * as reflect from '@ccms/common/dist/reflect' -import { injectable, inject } from '@ccms/container' +import { injectable, Autowired, ContainerInstance, Container, postConstruct } from '@ccms/container' -import { NativePluginManager } from './native_plugin' -import { constants } from '../../constants' - -export { NativePluginManager } from './native_plugin' +import { constants } from './constants' export namespace server { /** @@ -19,6 +16,24 @@ export namespace server { * Runtime Server Instance */ export const ServerInstance = Symbol("ServerInstance") + @injectable() + export abstract class NativePluginManager { + has(name: string): boolean { + return true + } + load(name: string): boolean { + throw new Error("Method not implemented.") + } + unload(name: string): boolean { + throw new Error("Method not implemented.") + } + reload(name: string): boolean { + throw new Error("Method not implemented.") + } + delete(name: string): boolean { + throw new Error("Method not implemented.") + } + } /** * MiaoScript Server */ @@ -48,9 +63,6 @@ export namespace server { getPluginsFolder(): string { throw new Error("Method not implemented.") } - getNativePluginManager(): NativePluginManager { - throw new Error("Method not implemented.") - } getDedicatedServer?(): any { throw new Error("Method not implemented.") } @@ -63,8 +75,9 @@ export namespace server { } @injectable() export class ServerChecker { - @inject(ServerType) + @Autowired(ServerType) private serverType: string + check(servers: string[]) { // Not set servers -> allow if (!servers || !servers.length) return true @@ -80,14 +93,17 @@ export namespace server { } @injectable() export abstract class ReflectServer extends server.Server { + @Autowired(ContainerInstance) + private container: Container + protected pipeline: any protected rootLogger: any constructor() { super() - this.reflect() } + @postConstruct() protected reflect() { try { let consoleServer = this.getDedicatedServer() @@ -107,7 +123,11 @@ export namespace server { if (connection.class.name.indexOf('ServerConnection') !== -1 || connection.class.name.indexOf('NetworkSystem') !== -1) { break } connection = undefined - } catch (error) { } + } catch (error) { + if (global.debug) { + console.ex(error) + } + } } if (!connection) { console.error("Can't found ServerConnection!"); return } for (const field of constants.Reflect.Field.listeningChannels) { @@ -115,16 +135,30 @@ export namespace server { promise = reflect.on(connection).get(field).get().get(0) if (promise.class.name.indexOf('Promise') !== -1) { break } promise = undefined - } catch (error) { } + } catch (error) { + if (global.debug) { + console.ex(error) + } + } } if (!promise) { console.error("Can't found listeningChannels!"); return } this.pipeline = reflect.on(promise).get('channel').get().pipeline() + this.container.bind(constants.ServiceIdentifier.NettyPipeline).toConstantValue(this.pipeline) } protected reflectRootLogger(consoleServer: any) { try { this.rootLogger = reflect.on(consoleServer).get('LOGGER').get().parent } catch (error) { - try { this.rootLogger = reflect.on(consoleServer).get(0).get().parent } catch (error) { } + if (global.debug) { + console.ex(error) + } + try { + this.rootLogger = reflect.on(consoleServer).get(0).get().parent + } catch (error) { + if (global.debug) { + console.ex(error) + } + } } if (this.rootLogger && this.rootLogger.class.name.indexOf('Logger') === -1) { console.error('Error Logger Class: ' + this.rootLogger.class.name) @@ -135,6 +169,7 @@ export namespace server { this.rootLogger = this.rootLogger.parent } if (!this.rootLogger) { console.error("Can't found rootLogger!") } + this.container.bind(constants.ServiceIdentifier.RootLogger).toConstantValue(this.rootLogger) } } } diff --git a/packages/api/src/interfaces/web.ts b/packages/api/src/web.ts similarity index 100% rename from packages/api/src/interfaces/web.ts rename to packages/api/src/web.ts diff --git a/packages/common/src/reflect.ts b/packages/common/src/reflect.ts index 8e2e4c6e..278d5da7 100644 --- a/packages/common/src/reflect.ts +++ b/packages/common/src/reflect.ts @@ -1,14 +1,15 @@ /// +const JavaClass = Java.type('java.lang.Class') +const JavaObject = Java.type('java.lang.Object') +const NoSuchFieldException = Java.type('java.lang.NoSuchFieldException') +const fieldCache = new Map() +const methodCache = new Map() + /** * 反射工具类 * Created by MiaoWoo on 2017/2/9 0009. */ -const JavaClass = Java.type('java.lang.Class') -const JavaObject = Java.type('java.lang.Object') -const NoSuchFieldException = Java.type('java.lang.NoSuchFieldException') -const methodCache = [] - class Reflect { private obj: java.lang.Object private class: java.lang.Class @@ -34,15 +35,26 @@ class Reflect { return Java.from(declaredMethods(this.class)) } - field(name: string | java.lang.String): Reflect { - try { - // Try getting a public field - let field = this.class.getField(name) - return on(field.get(this.obj)) - } catch (ex) { - // Try again, getting a non-public field - return on(accessible(declaredField(this.class, name)).get(this.obj)) + field(nameOrIndex: string | java.lang.String | number, declared = false): java.lang.reflect.Field { + if (nameOrIndex == undefined || nameOrIndex == null) throw new Error(`reflect field name can't be ${nameOrIndex} from ${this.class.getName()}!`) + let key = this.class.getName() + ':' + nameOrIndex + ':' + declared + if (fieldCache.has(key)) { + return fieldCache.get(key) } + let field = null + if (typeof nameOrIndex == "number") { + field = this.fields(declared)[nameOrIndex] + } else { + try { + // Try getting a public field + field = this.class.getField(nameOrIndex) + } catch (ex) { + // Try again, getting a non-public field + field = declaredField(this.class, nameOrIndex) + } + } + if (!field) throw new Error(`can't reflect field ${typeof nameOrIndex == "number" ? 'index' : 'name'} ${nameOrIndex} from ${this.class.getName()}!`) + return accessible(field) } fields(declared = false): java.lang.reflect.Field[] { @@ -50,7 +62,7 @@ class Reflect { } values(declared = false) { - return this.fields(declared).reduce((cache, field) => { return cache[field.getName()] = this.field(field.getName()).get() }, {}) as any + return this.fields(declared).reduce((cache, field) => { return cache[field.getName()] = this.get(field.getName()).get() }, {}) as any } call(...args: any[]): Reflect { @@ -64,17 +76,12 @@ class Reflect { get(index: number, declared?: boolean): Reflect get(prop: string): Reflect get(param?: string | number, declared: boolean = true): Reflect | any { - if (param == undefined || param == null) return this.obj - if (typeof param == "number") { - return on(accessible(this.fields(declared)[param]).get(this.obj)) - } - if (typeof param == "string") { - return this.field(param) - } + if (arguments.length === 0) return this.obj + return on(this.field(param, declared).get(this.obj)) } - set(name: any, value: any): Reflect { - accessible(declaredField(this.class, name)).set(this.obj, value) + set(param: string | number, value: any, declared: boolean = true): Reflect { + this.field(param, declared).set(this.obj, value) return this } @@ -136,64 +143,43 @@ function declaredField(clazz: java.lang.Class, name: string | java.lang.Str return field } -function declaredMethod(clazz: java.lang.Class, name: string, ...clazzs: java.lang.Class[]): java.lang.reflect.Method { - let key = clazz.getName() + '.' + name + ':' + (clazzs || []).join(':') - if (!methodCache[key]) { +function declaredMethod(clazz: java.lang.Class, nameOrIndex: string | number, ...clazzs: java.lang.Class[]): java.lang.reflect.Method { + let key = clazz.getName() + '.' + nameOrIndex + ':' + (clazzs || []).map(c => c.getName()).join(':') + if (methodCache.has(key)) { return methodCache.get(key) } + if (typeof nameOrIndex === "number") { + methodCache.set(key, declaredMethods(clazz)[nameOrIndex]) + } else { try { - // @ts-ignore - methodCache[key] = clazz.getMethod(name, clazzs) + methodCache.set(key, clazz.getMethod(nameOrIndex, clazzs as any)) } catch (ex) { try { - methodCache[key] = clazz.getDeclaredMethod(name, clazzs as any) + methodCache.set(key, clazz.getDeclaredMethod(nameOrIndex, clazzs as any)) } catch (ex) { for (const m of Java.from(declaredMethods(clazz))) { - if (m.getName() == name) { - methodCache[key] = m + if (m.getName() == nameOrIndex) { + methodCache.set(key, m) break } } } } } - return methodCache[key] + if (!methodCache.has(key)) throw new Error(`can't reflect method ${typeof nameOrIndex == "number" ? 'index' : 'name'} ${nameOrIndex} from ${clazz.getName()}!`) + return methodCache.get(key) } function declaredMethods(clazz: java.lang.Class) { return clazz.getDeclaredMethods() } -let classMethodsCache: any[] = [] - function mapToObject(javaObj) { - if (!javaObj || !javaObj.class) { throw new TypeError(`参数 ${javaObj} 不是一个Java对象!`) } - let target = {} - getJavaObjectMethods(javaObj).forEach(t => mapMethod(target, javaObj, t)) + if (!Java.isJavaObject(javaObj)) { throw new TypeError(`参数 ${javaObj} 不是一个Java对象!`) } + let target = Proxy.newProxy(javaObj, { + apply: (target, name, args) => { return args ? javaObj[name](args) : javaObj[name]() } + }) return target } -function getJavaObjectMethods(javaObj) { - let className = javaObj.class.name - if (!classMethodsCache[className]) { - let names: any[] = [] - let methods = javaObj.class.methods - for (let i in methods) { - names.push(methods[i].name) - } - classMethodsCache[className] = names - } - return classMethodsCache[className] -} - -function mapMethod(target, source, name) { - target[name] = function __SimpleDynamicMethod__(...args) { - if (args.length > 0) { - return source[name](args) - } else { - return source[name]() - } - } -} - function on(obj) { // if (!obj || !obj.class) { throw new TypeError(`参数 ${obj} 不是一个Java对象!`) } return new Reflect(obj) diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index e0ba363a..7f7cc27f 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -99,6 +99,7 @@ function initialize() { console.i18n("ms.core.initialize.detect", { scope: global.scope, type }) container.bind(server.ServerType).toConstantValue(type) container.bind(server.ServerChecker).toSelf().inSingletonScope() + container.bind(server.NativePluginManager).toSelf().inSingletonScope() console.i18n("ms.core.package.initialize", { scope: global.scope, type }) require(`${global.scope}/${type}`).default(container) require(`${global.scope}/plugin`) diff --git a/packages/nashorn/src/index.ts b/packages/nashorn/src/index.ts index cb5d4692..5c9ccfa7 100644 --- a/packages/nashorn/src/index.ts +++ b/packages/nashorn/src/index.ts @@ -81,8 +81,18 @@ declare global { i18n(name: string, ...params: any[]): void } interface ProxyConstructor { - newProxy(target: T, handler: ProxyHandler): T + newProxy(target: T, handler: ProxyHandle): T } } - +export interface ProxyHandle extends ProxyHandler { + setPrototypeOf?(target: T, v: any): any + // get: (target: any, name: string) => any + // set: (target: any, name: string, value: any) => boolean + // construct: (target: any, ...args: any[]) => any + // has: (target: any, name: string) => boolean + // ownKeys: (target: any) => string[] + values?: (target: T) => any[] + // call: (target: any, name: string, ...args: any[]) => any + // deleteProperty: (target: any, name: string) => boolean +} export { } diff --git a/packages/ployfill/src/proxy.ts b/packages/ployfill/src/proxy.ts index f7eb7558..32b99f44 100644 --- a/packages/ployfill/src/proxy.ts +++ b/packages/ployfill/src/proxy.ts @@ -1,17 +1,9 @@ +import { ProxyHandle } from '@ccms/nashorn' + // Nashorn JSAdapter See https://wiki.openjdk.java.net/display/Nashorn/Nashorn+extensions#Nashornextensions-JSAdapterconstructor let createProxy = eval(` function(handle){ return new JSAdapter(handle) } `) -export interface ProxyHandle extends ProxyHandler { - // get: (target: any, name: string) => any - // set: (target: any, name: string, value: any) => boolean - // construct: (target: any, ...args: any[]) => any - // has: (target: any, name: string) => boolean - // ownKeys: (target: any) => string[] - values: (target: any) => any[]; - // call: (target: any, name: string, ...args: any[]) => any - // deleteProperty: (target: any, name: string) => boolean -} export class Proxy { static newProxy(target: any, handle: Partial): any { return new Proxy(target, handle) diff --git a/packages/plugin/src/manager.ts b/packages/plugin/src/manager.ts index e4faa9c7..000409bf 100644 --- a/packages/plugin/src/manager.ts +++ b/packages/plugin/src/manager.ts @@ -1,6 +1,6 @@ import i18n from '@ccms/i18n' -import { plugin, server, event } from '@ccms/api' -import { inject, provideSingleton, Container, ContainerInstance, Autowired } from '@ccms/container' +import { plugin, server } from '@ccms/api' +import { provideSingleton, Container, ContainerInstance, Autowired } from '@ccms/container' import './config' import { interfaces } from './interfaces' @@ -13,15 +13,17 @@ const Thread = Java.type('java.lang.Thread') @provideSingleton(plugin.PluginManager) export class PluginManagerImpl implements plugin.PluginManager { - @inject(ContainerInstance) + @Autowired(ContainerInstance) private container: Container - @inject(plugin.PluginInstance) + @Autowired(plugin.PluginInstance) private pluginInstance: any - @inject(server.ServerType) + @Autowired(server.ServerType) private serverType: string @Autowired() private serverChecker: server.ServerChecker + @Autowired() + private nativePluginManager: server.NativePluginManager @Autowired() private taskManager: PluginTaskManager @@ -224,15 +226,28 @@ export class PluginManagerImpl implements plugin.PluginManager { try { this.buildPlugin(metadata) } catch (error) { - console.console(error) + console.console(`§4无法加载插件 §b${metadata.name} §4构建插件失败!`) + console.ex(error) } }) } + private checkDepends(depends: string | string[]) { + if (!depends) return true + for (const depend of depends) { if (!this.metadataMap.has(depend)) return false } + return true + } + private checkNativeDepends(depends: string | string[]) { + if (!depends) return true + for (const depend of depends) { if (!this.nativePluginManager.has(depend)) return false } + return true + } private buildPlugin(metadata: plugin.PluginMetadata) { - if (!this.loaderMap.has(metadata.type)) { throw new Error(`§4无法加载插件 §c${metadata.name} §4请检查 §c${metadata.type} §4加载器是否正常启用!`) } if (this.instanceMap.has(metadata.name)) { throw new Error(`Plugin ${metadata.name} is already load from ${metadata.source}...`) } + if (!this.loaderMap.has(metadata.type)) { throw new Error(`§4无法加载插件 §b${metadata.name} §4请检查 §c${metadata.type} §4加载器是否正常启用!`) } if (!this.serverChecker.check(metadata.servers)) { throw new Error(`§6插件 §b${metadata.name} §c服务器类型不兼容(${metadata.servers.join(',')}) §6忽略加载...`) } + if (!this.checkDepends(metadata.depends)) { throw new Error(`§4无法加载插件 §b${metadata.name} §4请检查依赖 §3${metadata.depends.join(',')} §4是否安装完整!`) } + if (!this.checkNativeDepends(metadata.nativeDepends)) { throw new Error(`§4无法加载插件 §b${metadata.name} §4请检查插件依赖 §3${metadata.nativeDepends.join(',')} §4是否安装完整!`) } let pluginInstance = this.loaderMap.get(metadata.type).build(metadata) if (!pluginInstance) { throw new Error(`§4加载器 §c${metadata.type} §4加载插件 §c${metadata.name} §4失败!`) } this.instanceMap.set(metadata.name, pluginInstance) diff --git a/packages/spring/src/server.ts b/packages/spring/src/server.ts index 1c903cba..118eee42 100644 --- a/packages/spring/src/server.ts +++ b/packages/spring/src/server.ts @@ -1,31 +1,32 @@ -import { server } from '@ccms/api' -import { provideSingleton, inject } from '@ccms/container' -import { NativePluginManager } from '@ccms/api' +import { constants, server } from '@ccms/api' +import { provideSingleton, postConstruct, Autowired, Container, ContainerInstance } from '@ccms/container' import { CommandMap } from './internal/command' @provideSingleton(server.Server) -export class SpringServer implements server.Server { - @inject(CommandMap) +export class SpringServer extends server.Server { + @Autowired(ContainerInstance) + private container: Container + @Autowired() private commandMap: CommandMap + private nettyPipeline = base.getInstance().getAutowireCapableBeanFactory() + private rootLogger = Packages.org.slf4j.LoggerFactory.getLogger("root") || global.logger + + @postConstruct() + initialize() { + this.container.bind(constants.ServiceIdentifier.NettyPipeline).toConstantValue(this.nettyPipeline) + this.container.bind(constants.ServiceIdentifier.RootLogger).toConstantValue(this.rootLogger) + } + getVersion(): string { return "SpringFramework" } - getPlayer(name: string) { - throw new Error("Method not implemented.") - } - getOnlinePlayers(): any[] { - throw new Error("Method not implemented.") - } getConsoleSender() { return { name: 'CONSOLE', sendMessage: (message: string) => console.console(message) } } - getService(service: string) { - throw new Error("Method not implemented.") - } dispatchCommand(sender: any, command: string): boolean { let cmd_args = command.split(" ") return this.commandMap.dispatch(sender, cmd_args.shift(), cmd_args || []) @@ -33,16 +34,10 @@ export class SpringServer implements server.Server { dispatchConsoleCommand(command: string): boolean { return this.dispatchCommand(this.getConsoleSender(), command) } - getPluginsFolder(): string { - throw new Error("Method not implemented.") - } - getNativePluginManager(): NativePluginManager { - throw new Error("Method not implemented.") - } getNettyPipeline() { - return base.getInstance().getAutowireCapableBeanFactory() + return this.nettyPipeline } getRootLogger() { - return Packages.org.slf4j.LoggerFactory.getLogger("root") || global.logger + return this.rootLogger } }