diff --git a/rec-engine/rec-engine-script/src/main/java/cn/icanci/rec/engine/script/RecScriptEngine.java b/rec-engine/rec-engine-script/src/main/java/cn/icanci/rec/engine/script/RecScriptEngine.java new file mode 100644 index 0000000000000000000000000000000000000000..9ed9d3dab91ec5b0ea7e1290ab9fb5d44dda4711 --- /dev/null +++ b/rec-engine/rec-engine-script/src/main/java/cn/icanci/rec/engine/script/RecScriptEngine.java @@ -0,0 +1,58 @@ +package cn.icanci.rec.engine.script; + +import cn.icanci.rec.common.enums.ScriptTypeEnum; +import cn.icanci.rec.engine.script.context.RecScriptEngineContext; + +import javax.script.Bindings; + +/** + * 脚本执行引擎抽象顶级接口 + * + * @author icanci + * @since 1.0 Created in 2022/11/12 22:12 + */ +public interface RecScriptEngine { + + /** + * 执行脚本 + * + * @param scriptType 脚本类型 + * @param bindings 脚本运行时参数 + * @param script 脚本内容 + * @return 脚本执行返回上下文 + */ + RecScriptEngineContext eval(ScriptTypeEnum scriptType, Bindings bindings, String script); + + /** + * 执行脚本 + * + * @param scriptType 脚本类型 + * @param script 脚本内容 + * @return 脚本执行返回上下文 + */ + RecScriptEngineContext eval(ScriptTypeEnum scriptType, String script); + + /** + * 执行脚本 + * + * @param scriptType 脚本类型 + * @param bindings 脚本运行时参数 + * @param script 脚本内容 + * @param clazz 脚本执行返回类型 + * @param 泛型 + * @return 脚本执行返回上下文 + */ + RecScriptEngineContext eval(ScriptTypeEnum scriptType, Bindings bindings, String script, Class clazz); + + /** + * 执行脚本 + * + * @param scriptType 脚本类型 + * @param script 脚本内容 + * @param clazz 脚本执行返回类型 + * @param 泛型 + * @return 脚本执行返回上下文 + */ + RecScriptEngineContext eval(ScriptTypeEnum scriptType, String script, Class clazz); + +} diff --git a/rec-engine/rec-engine-script/src/main/java/cn/icanci/rec/engine/script/RecScriptEngineManager.java b/rec-engine/rec-engine-script/src/main/java/cn/icanci/rec/engine/script/RecScriptEngineManager.java index ae9dfb9c862ece6d4e303537bf396d88364665f2..84f8109b195b4309058826842d5f4597f63af514 100644 --- a/rec-engine/rec-engine-script/src/main/java/cn/icanci/rec/engine/script/RecScriptEngineManager.java +++ b/rec-engine/rec-engine-script/src/main/java/cn/icanci/rec/engine/script/RecScriptEngineManager.java @@ -1,25 +1,18 @@ package cn.icanci.rec.engine.script; -import javax.script.ScriptEngineManager; +import cn.icanci.rec.engine.script.impl.RecScriptEngineImpl; /** * @author icanci * @since 1.0 Created in 2022/11/12 22:27 */ -public class RecScriptEngineManager { - private static final ScriptEngineManager scriptEngineManager = new ScriptEngineManager(); - - // public T doRun(T t) { - // - // } - - public static void main(String[] args) { - // List engineFactories = scriptEngineManager.getEngineFactories(); - // for (ScriptEngineFactory engineFactory : engineFactories) { - // ScriptEngine scriptEngine = engineFactory.getScriptEngine(); - // } - -// ScriptEngine scriptEngine = new ScriptEngineImpl(); -// Object o = scriptEngine.doEval(); +public final class RecScriptEngineManager { + /** + * 获取 REC 执行引擎 + * + * @return 返回 REC 执行引擎 + */ + public static RecScriptEngine getRecScriptEngine() { + return new RecScriptEngineImpl(); } } diff --git a/rec-engine/rec-engine-script/src/main/java/cn/icanci/rec/engine/script/ScriptEngine.java b/rec-engine/rec-engine-script/src/main/java/cn/icanci/rec/engine/script/ScriptEngine.java deleted file mode 100644 index 5f2d5796327c3c6c709c885212771f32706445b8..0000000000000000000000000000000000000000 --- a/rec-engine/rec-engine-script/src/main/java/cn/icanci/rec/engine/script/ScriptEngine.java +++ /dev/null @@ -1,27 +0,0 @@ -package cn.icanci.rec.engine.script; - -/** - * 脚本执行引擎抽象顶级接口 - * - * @author icanci - * @since 1.0 Created in 2022/11/12 22:12 - */ -public interface ScriptEngine { - /** - * 执行脚本 - * - * @param script script - * @return 返回执行内容 - */ - Object eval(String script); - - /** - * 执行脚本 - * - * @param t 执行类型 - * @param script 脚本参数 - * @param 泛型 - * @return 返回指定类型的结果值 - */ - T doEval(T t,String script); -} diff --git a/rec-engine/rec-engine-script/src/main/java/cn/icanci/rec/engine/script/ScriptEngineImpl.java b/rec-engine/rec-engine-script/src/main/java/cn/icanci/rec/engine/script/ScriptEngineImpl.java deleted file mode 100644 index a43c5964e25d54f30126d3cbeda15e67a5b041a2..0000000000000000000000000000000000000000 --- a/rec-engine/rec-engine-script/src/main/java/cn/icanci/rec/engine/script/ScriptEngineImpl.java +++ /dev/null @@ -1,17 +0,0 @@ -package cn.icanci.rec.engine.script; - -/** - * @author icanci - * @since 1.0 Created in 2022/11/12 22:46 - */ -public class ScriptEngineImpl implements ScriptEngine { - @Override - public Object eval(String script) { - return null; - } - - @Override - public T doEval(T defaultVal, String script) { - return (T) eval(script); - } -} diff --git a/rec-engine/rec-engine-script/src/main/java/cn/icanci/rec/engine/script/context/RecScriptEngineContext.java b/rec-engine/rec-engine-script/src/main/java/cn/icanci/rec/engine/script/context/RecScriptEngineContext.java new file mode 100644 index 0000000000000000000000000000000000000000..4c88aefeac247fd8f373a0af2d0bc09e7584aff1 --- /dev/null +++ b/rec-engine/rec-engine-script/src/main/java/cn/icanci/rec/engine/script/context/RecScriptEngineContext.java @@ -0,0 +1,170 @@ +package cn.icanci.rec.engine.script.context; + +import cn.icanci.rec.common.enums.ScriptTypeEnum; + +import java.io.Serializable; +import java.util.StringJoiner; + +import javax.script.Bindings; +import javax.script.Compilable; +import javax.script.CompiledScript; +import javax.script.ScriptEngine; + +/** + * 脚本引擎上下文 + * 脚本一次执行的结果信息等 + * + * @author icanci + * @since 1.0 Created in 2022/11/13 12:42 + */ +public class RecScriptEngineContext implements Serializable { + + private static final long serialVersionUID = 6215311466648575581L; + + /** + * 脚本类型 + */ + private ScriptTypeEnum scriptType; + /** + * 脚本内容 + */ + private String scriptContent; + /** + * 脚本执行引擎 + */ + private ScriptEngine scriptEngine; + /** + * 脚本执行引擎编译器,如果脚本执行引擎实现了Compilable + * 那么 scriptEngine 和 compilable 则是同一个对象 + */ + private Compilable compilable; + /** + * 通过 Compilable 编译 scriptContent 之后的实例对象 + */ + private CompiledScript compiledScript; + /** + * 脚本执行之后的结果 + */ + private T retVal; + /** + * 脚本执行出错之后的结果 + */ + private Throwable throwable; + /** + * 脚本执行结果返回类型,如果指定了此类型 + */ + private Class type; + /** + * 脚本执行请求参数 + */ + private Bindings bindings; + + // ============================ ability method ============================ + + /** + * 获取真正的执行结果,并且对返回结果进行校验 + * + * @return 获取真正的执行结果 + */ + public T getRealRetVal() { + try { + if (retVal == null) { + throw new NullPointerException("The script engine execution result is null!"); + } + + if (type == Integer.class) { + return (T) Integer.valueOf(String.valueOf(retVal)); + } + if (type == Double.class) { + return (T) Double.valueOf(String.valueOf(retVal)); + } + if (type == Long.class) { + return (T) Long.valueOf(String.valueOf(retVal)); + } + if (type == String.class) { + return (T) String.valueOf(retVal); + } + } catch (Throwable e) { + if (this.throwable != null) { + this.throwable.addSuppressed(e); + } + this.throwable = e; + } + return null; + } + + // ============================ getter/setter ============================ + public ScriptTypeEnum getScriptType() { + return scriptType; + } + + public void setScriptType(ScriptTypeEnum scriptType) { + this.scriptType = scriptType; + } + + public String getScriptContent() { + return scriptContent; + } + + public void setScriptContent(String scriptContent) { + this.scriptContent = scriptContent; + } + + public ScriptEngine getScriptEngine() { + return scriptEngine; + } + + public void setScriptEngine(ScriptEngine scriptEngine) { + this.scriptEngine = scriptEngine; + } + + public Compilable getCompilable() { + return compilable; + } + + public void setCompilable(Compilable compilable) { + this.compilable = compilable; + } + + public CompiledScript getCompiledScript() { + return compiledScript; + } + + public void setCompiledScript(CompiledScript compiledScript) { + this.compiledScript = compiledScript; + } + + public void setRetVal(T retVal) { + this.retVal = retVal; + } + + public Throwable getThrowable() { + return throwable; + } + + public void setThrowable(Throwable throwable) { + this.throwable = throwable; + } + + public Class getType() { + return type; + } + + public void setType(Class type) { + this.type = type; + } + + public Bindings getBindings() { + return bindings; + } + + public void setBindings(Bindings bindings) { + this.bindings = bindings; + } + + @Override + public String toString() { + return new StringJoiner(",").add("scriptType=" + scriptType).add("scriptContent=" + scriptContent).add("scriptEngine=" + scriptEngine).add("compilable=" + compilable) + .add("compiledScript=" + compiledScript).add("retVal=" + retVal).add("throwable=" + throwable).add("type=" + type).add("bindings=" + bindings).toString(); + } +} diff --git a/rec-engine/rec-engine-script/src/main/java/cn/icanci/rec/engine/script/enums/ResultTypeMapEnum.java b/rec-engine/rec-engine-script/src/main/java/cn/icanci/rec/engine/script/enums/ResultTypeMapEnum.java new file mode 100644 index 0000000000000000000000000000000000000000..a0ff5915a5146c7703d503d4d5cf7e1f491d46ff --- /dev/null +++ b/rec-engine/rec-engine-script/src/main/java/cn/icanci/rec/engine/script/enums/ResultTypeMapEnum.java @@ -0,0 +1,69 @@ +package cn.icanci.rec.engine.script.enums; + +import cn.icanci.rec.common.enums.ResultTypeEnum; + +import java.util.HashMap; +import java.util.Map; + +/** + * 类型映射 + * + * @author icanci + * @since 1.0 Created in 2022/11/13 11:57 + */ +public enum ResultTypeMapEnum { + /** + * INTEGER + */ + INTEGER(ResultTypeEnum.INTEGER, Integer.class), + /** + * LONG + */ + LONG(ResultTypeEnum.LONG, Long.class), + /** + * DOUBLE + */ + DOUBLE(ResultTypeEnum.DOUBLE, Double.class), + /** + * STRING + */ + STRING(ResultTypeEnum.STRING, String.class), + + ; + + private static final Map MAP = new HashMap<>(); + + static { + ResultTypeMapEnum[] values = ResultTypeMapEnum.values(); + for (ResultTypeMapEnum resultTypeMap : values) { + MAP.put(resultTypeMap.resultType, resultTypeMap.clazz); + } + } + + private final ResultTypeEnum resultType; + private final Class clazz; + + /** + * 根据 ResultTypeEnum 获取对应的类型 + * + * @param resultType resultType + * @return 返回对应的类型 + */ + public Class getClassByResultType(ResultTypeEnum resultType) { + return MAP.get(resultType); + } + + ResultTypeMapEnum(ResultTypeEnum resultType, Class clazz) { + this.resultType = resultType; + this.clazz = clazz; + } + + public ResultTypeEnum getResultType() { + return resultType; + } + + public Class getClazz() { + return clazz; + } + +} diff --git a/rec-engine/rec-engine-script/src/main/java/cn/icanci/rec/engine/script/enums/ScriptTypeFactoryEnum.java b/rec-engine/rec-engine-script/src/main/java/cn/icanci/rec/engine/script/enums/ScriptTypeFactoryEnum.java new file mode 100644 index 0000000000000000000000000000000000000000..c4ebb6000a5cab4f439bcf996fece25a98b8d54b --- /dev/null +++ b/rec-engine/rec-engine-script/src/main/java/cn/icanci/rec/engine/script/enums/ScriptTypeFactoryEnum.java @@ -0,0 +1,75 @@ +package cn.icanci.rec.engine.script.enums; + +import cn.icanci.rec.common.enums.ScriptTypeEnum; +import jdk.nashorn.api.scripting.NashornScriptEngineFactory; + +import java.util.HashMap; +import java.util.Map; + +import javax.script.ScriptEngineFactory; + +import org.codehaus.groovy.jsr223.GroovyScriptEngineFactory; +import org.mvel2.jsr223.MvelScriptEngineFactory; + +/** + * @author icanci + * @since 1.0 Created in 2022/11/13 12:07 + */ +public enum ScriptTypeFactoryEnum { + /** + * GROOVY + */ + GROOVY(ScriptTypeEnum.GROOVY, new GroovyScriptEngineFactory()), + /** + * MVEL2 + */ + MVEL2(ScriptTypeEnum.MVEL2, new MvelScriptEngineFactory()), + /** + * JAVA_SCRIPT + * + * Tip: NashornScriptEngineFactory 支持执行 + * nashorn,Nashorn,js,JS,JavaScript,javascript,ECMAScript,ecmascript + * 此处只用到了JavaScript + * + * Warning: Nashorn engine is planned to be removed from a future JDK release + */ + JAVA_SCRIPT(ScriptTypeEnum.JAVA_SCRIPT, new NashornScriptEngineFactory()), + + ; + + private static final Map MAP = new HashMap<>(); + + static { + ScriptTypeFactoryEnum[] values = ScriptTypeFactoryEnum.values(); + for (ScriptTypeFactoryEnum value : values) { + MAP.put(value.scriptType, value.factory); + } + } + + private final ScriptTypeEnum scriptType; + private final ScriptEngineFactory factory; + + /** + * 根据 ScriptTypeEnum 获取对应的类型 + * + * @param scriptType scriptType + * @return 返回对应的类型 + */ + public ScriptEngineFactory getScriptEngineFactoryByScriptType(ScriptTypeEnum scriptType) { + return MAP.get(scriptType); + } + + ScriptTypeFactoryEnum(ScriptTypeEnum scriptType, ScriptEngineFactory factory) { + this.scriptType = scriptType; + this.factory = factory; + } + + public ScriptTypeEnum getScriptType() { + return scriptType; + } + + public ScriptEngineFactory getFactory() { + return factory; + } + +} diff --git a/rec-engine/rec-engine-script/src/main/java/cn/icanci/rec/engine/script/factory/ScriptEngineFactory.java b/rec-engine/rec-engine-script/src/main/java/cn/icanci/rec/engine/script/factory/ScriptEngineFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..4e12b682f8fa5b53eb204fe833c1e80e582696bb --- /dev/null +++ b/rec-engine/rec-engine-script/src/main/java/cn/icanci/rec/engine/script/factory/ScriptEngineFactory.java @@ -0,0 +1,39 @@ +package cn.icanci.rec.engine.script.factory; + +import cn.icanci.rec.common.enums.ScriptTypeEnum; +import cn.icanci.rec.engine.script.enums.ScriptTypeFactoryEnum; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import javax.script.ScriptEngine; + +/** + * @author icanci + * @since 1.0 Created in 2022/11/13 12:18 + */ +public final class ScriptEngineFactory { + + /** + * 只有一个实例 + */ + private static final Map SCRIPT_ENGINE_CACHE = new ConcurrentHashMap<>(); + + static { + ScriptTypeFactoryEnum[] scriptTypeFactory = ScriptTypeFactoryEnum.values(); + for (ScriptTypeFactoryEnum factory : scriptTypeFactory) { + // ConcurrentHashMap#key、value can not be null !!! + SCRIPT_ENGINE_CACHE.put(factory.getScriptType(), factory.getFactory().getScriptEngine()); + } + } + + /** + * 根据 scriptType 类型获取执行引擎 + * + * @param scriptType scriptType + * @return 返回执行引擎 + */ + public static ScriptEngine getScriptEngine(ScriptTypeEnum scriptType) { + return SCRIPT_ENGINE_CACHE.get(scriptType); + } +} diff --git a/rec-engine/rec-engine-script/src/main/java/cn/icanci/rec/engine/script/impl/RecScriptEngineImpl.java b/rec-engine/rec-engine-script/src/main/java/cn/icanci/rec/engine/script/impl/RecScriptEngineImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..7394b04702d68bbde29079abc9041a3dde2b9b48 --- /dev/null +++ b/rec-engine/rec-engine-script/src/main/java/cn/icanci/rec/engine/script/impl/RecScriptEngineImpl.java @@ -0,0 +1,79 @@ +package cn.icanci.rec.engine.script.impl; + +import cn.icanci.rec.common.enums.ScriptTypeEnum; +import cn.icanci.rec.engine.script.RecScriptEngine; +import cn.icanci.rec.engine.script.context.RecScriptEngineContext; +import cn.icanci.rec.engine.script.factory.ScriptEngineFactory; + +import javax.script.*; + +/** + * @author icanci + * @since 1.0 Created in 2022/11/12 22:46 + */ +public class RecScriptEngineImpl implements RecScriptEngine { + + @Override + public RecScriptEngineContext eval(ScriptTypeEnum scriptType, Bindings bindings, String script) { + RecScriptEngineContext context = new RecScriptEngineContext<>(); + context.setScriptType(scriptType); + context.setScriptContent(script); + context.setType(Object.class); + context.setBindings(bindings); + try { + ScriptEngine scriptEngine = ScriptEngineFactory.getScriptEngine(scriptType); + context.setScriptEngine(scriptEngine); + if (scriptEngine instanceof Compilable) { + Compilable compilable = (Compilable) scriptEngine; + context.setCompilable(compilable); + CompiledScript compile = compilable.compile(script); + context.setCompiledScript(compile); + Object eval = compile.eval(bindings); + context.setRetVal(eval); + } else { + Object eval = scriptEngine.eval(script, bindings); + context.setRetVal(eval); + } + } catch (Throwable t) { + context.setThrowable(t); + } + return context; + } + + @Override + public RecScriptEngineContext eval(ScriptTypeEnum scriptType, String script) { + return eval(scriptType, new SimpleBindings(), script); + } + + @Override + public RecScriptEngineContext eval(ScriptTypeEnum scriptType, Bindings bindings, String script, Class clazz) { + RecScriptEngineContext context = new RecScriptEngineContext<>(); + context.setScriptType(scriptType); + context.setScriptContent(script); + context.setType(clazz); + context.setBindings(bindings); + try { + ScriptEngine scriptEngine = ScriptEngineFactory.getScriptEngine(scriptType); + context.setScriptEngine(scriptEngine); + if (scriptEngine instanceof Compilable) { + Compilable compilable = (Compilable) scriptEngine; + context.setCompilable(compilable); + CompiledScript compile = compilable.compile(script); + context.setCompiledScript(compile); + Object eval = compile.eval(bindings); + context.setRetVal((T) eval); + } else { + Object eval = scriptEngine.eval(script, bindings); + context.setRetVal((T) eval); + } + } catch (Throwable t) { + context.setThrowable(t); + } + return context; + } + + @Override + public RecScriptEngineContext eval(ScriptTypeEnum scriptType, String script, Class clazz) { + return eval(scriptType, new SimpleBindings(), script, clazz); + } +} diff --git a/rec-engine/rec-engine-script/src/test/java/cn/icanci/rec/engine/script/test/RecScriptEngineManagerTest.java b/rec-engine/rec-engine-script/src/test/java/cn/icanci/rec/engine/script/test/RecScriptEngineManagerTest.java new file mode 100644 index 0000000000000000000000000000000000000000..043d7492580a16921dda7f3f012bbfdd48eed9c1 --- /dev/null +++ b/rec-engine/rec-engine-script/src/test/java/cn/icanci/rec/engine/script/test/RecScriptEngineManagerTest.java @@ -0,0 +1,24 @@ +package cn.icanci.rec.engine.script.test; + +import cn.icanci.rec.common.enums.ScriptTypeEnum; +import cn.icanci.rec.engine.script.RecScriptEngine; +import cn.icanci.rec.engine.script.RecScriptEngineManager; +import cn.icanci.rec.engine.script.context.RecScriptEngineContext; + +import org.junit.Test; + +/** + * @author icanci + * @since 1.0 Created in 2022/11/13 13:37 + */ +public class RecScriptEngineManagerTest { + @Test + public void testRecScriptEngineManager() { + RecScriptEngine recScriptEngine = RecScriptEngineManager.getRecScriptEngine(); + RecScriptEngineContext context = recScriptEngine.eval(ScriptTypeEnum.MVEL2, "1+2 +'e'", Integer.class); + System.out.println(context); + Integer retVal = context.getRealRetVal(); + System.out.println(retVal); + System.out.println(context); + } +}