266 lines
8.8 KiB
Java
266 lines
8.8 KiB
Java
package pw.yumc.YumCore.engine;
|
|
|
|
import lombok.SneakyThrows;
|
|
import lombok.val;
|
|
|
|
import javax.script.ScriptEngine;
|
|
import javax.script.*;
|
|
import java.io.File;
|
|
import java.io.Reader;
|
|
import java.lang.invoke.MethodHandle;
|
|
import java.lang.invoke.MethodHandles;
|
|
import java.lang.reflect.Field;
|
|
import java.lang.reflect.Method;
|
|
import java.net.URL;
|
|
import java.nio.file.Files;
|
|
import java.nio.file.StandardCopyOption;
|
|
import java.util.HashMap;
|
|
|
|
/**
|
|
* 喵式脚本引擎
|
|
*
|
|
* @author 喵♂呜
|
|
* @since 2016年8月29日 上午7:51:43
|
|
*/
|
|
public class MiaoScriptEngine implements ScriptEngine, Invocable {
|
|
private static String MavenRepo = "https://maven.aliyun.com/repository/public";
|
|
private static MiaoScriptEngine DEFAULT;
|
|
private static final ScriptEngineManager manager;
|
|
|
|
private Object ucp;
|
|
private MethodHandle addURLMethodHandle;
|
|
private ScriptEngine engine;
|
|
|
|
static {
|
|
manager = new ScriptEngineManager(ClassLoader.getSystemClassLoader());
|
|
}
|
|
|
|
public static void setBindings(Bindings bindings) {
|
|
manager.setBindings(bindings);
|
|
}
|
|
|
|
public static Bindings getBindings() {
|
|
return manager.getBindings();
|
|
}
|
|
|
|
public MiaoScriptEngine() {
|
|
this("js");
|
|
}
|
|
|
|
public MiaoScriptEngine(final String engineType) {
|
|
this(manager, engineType, null);
|
|
}
|
|
|
|
public MiaoScriptEngine(ScriptEngineManager engineManager) {
|
|
this(engineManager, "js", null);
|
|
}
|
|
|
|
public MiaoScriptEngine(final String engineType, String engineRoot) {
|
|
this(manager, engineType, engineRoot);
|
|
}
|
|
|
|
public MiaoScriptEngine(ScriptEngineManager engineManager, final String engineType, String engineRoot) {
|
|
// JDK11 Polyfill 存在类效验问题 直接用OpenJDK的Nashorn
|
|
if (System.getProperty("java.version").startsWith("11.") && engineRoot != null) {
|
|
this.loadNetworkNashorn(engineRoot);
|
|
if (engine == null)
|
|
throw new UnsupportedOperationException("当前环境 JDK11 不支持 Nashorn 脚本类型!");
|
|
return;
|
|
}
|
|
try {
|
|
engine = engineManager.getEngineByName(engineType);
|
|
} catch (final NullPointerException ignored) {
|
|
}
|
|
if (engine == null) {
|
|
val extDirs = System.getProperty("java.ext.dirs");
|
|
if (extDirs != null) {
|
|
this.loadLocalNashorn(extDirs, engineType);
|
|
} else if (engineRoot != null) {
|
|
this.loadNetworkNashorn(engineRoot);
|
|
}
|
|
}
|
|
if (engine == null)
|
|
throw new UnsupportedOperationException("当前环境不支持 " + engineType + " 脚本类型!");
|
|
}
|
|
|
|
private void loadLocalNashorn(String extDirs, String engineType) {
|
|
val dirs = extDirs.split(File.pathSeparator);
|
|
for (String dir : dirs) {
|
|
File nashorn = new File(dir, "nashorn.jar");
|
|
if (nashorn.exists()) {
|
|
this.loadJar(nashorn);
|
|
this.createEngineByName(engineType);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void initReflect() {
|
|
try {
|
|
ClassLoader loader = Thread.currentThread().getContextClassLoader();
|
|
Field theUnsafe = sun.misc.Unsafe.class.getDeclaredField("theUnsafe");
|
|
theUnsafe.setAccessible(true);
|
|
sun.misc.Unsafe unsafe = (sun.misc.Unsafe) theUnsafe.get(null);
|
|
Field field = MethodHandles.Lookup.class.getDeclaredField("IMPL_LOOKUP");
|
|
MethodHandles.Lookup lookup = (MethodHandles.Lookup) unsafe.getObject(unsafe.staticFieldBase(field), unsafe.staticFieldOffset(field));
|
|
Field ucpField;
|
|
try {
|
|
ucpField = loader.getClass().getDeclaredField("ucp");
|
|
} catch (NoSuchFieldException e) {
|
|
ucpField = loader.getClass().getSuperclass().getDeclaredField("ucp");
|
|
}
|
|
long offset = unsafe.objectFieldOffset(ucpField);
|
|
ucp = unsafe.getObject(loader, offset);
|
|
Method method = ucp.getClass().getDeclaredMethod("addURL", URL.class);
|
|
addURLMethodHandle = lookup.unreflect(method);
|
|
} catch (Exception e) {
|
|
throw new RuntimeException(e);
|
|
}
|
|
}
|
|
|
|
@SneakyThrows
|
|
private void loadNetworkNashorn(String engineRoot) {
|
|
initReflect();
|
|
File libRootFile = new File(engineRoot, "lib");
|
|
libRootFile.mkdirs();
|
|
String libRoot = libRootFile.getCanonicalPath();
|
|
downloadJar(libRoot, "org.openjdk.nashorn", "nashorn-core", "15.3");
|
|
downloadJar(libRoot, "org.ow2.asm", "asm", "9.2");
|
|
downloadJar(libRoot, "org.ow2.asm", "asm-commons", "9.2");
|
|
downloadJar(libRoot, "org.ow2.asm", "asm-tree", "9.2");
|
|
downloadJar(libRoot, "org.ow2.asm", "asm-util", "9.2");
|
|
Class<?> NashornScriptEngineFactory = Class.forName("org.openjdk.nashorn.api.scripting.NashornScriptEngineFactory");
|
|
Method getScriptEngine = NashornScriptEngineFactory.getMethod("getScriptEngine", ClassLoader.class);
|
|
Object factory = NashornScriptEngineFactory.newInstance();
|
|
engine = (ScriptEngine) getScriptEngine.invoke(factory, Thread.currentThread().getContextClassLoader());
|
|
}
|
|
|
|
@SneakyThrows
|
|
private void loadJar(File file) {
|
|
addURLMethodHandle.invoke(ucp, file.toURI().toURL());
|
|
}
|
|
|
|
@SneakyThrows
|
|
private void downloadJar(String engineRoot, String groupId, String artifactId, String version) {
|
|
File lib = new File(engineRoot, String.format("%s-%s.jar", artifactId, version));
|
|
if (!lib.exists()) {
|
|
Files.copy(new URL(MavenRepo +
|
|
String.format("/%1$s/%2$s/%3$s/%2$s-%3$s.jar",
|
|
groupId.replace(".", "/"),
|
|
artifactId,
|
|
version)
|
|
).openStream(),
|
|
lib.toPath(),
|
|
StandardCopyOption.REPLACE_EXISTING);
|
|
}
|
|
this.loadJar(lib);
|
|
}
|
|
|
|
private void createEngineByName(String engineType) {
|
|
try {
|
|
engine = new ScriptEngineManager(Thread.currentThread().getContextClassLoader()).getEngineByName(engineType);
|
|
} catch (NullPointerException ignored) {
|
|
}
|
|
}
|
|
|
|
public ScriptEngine getEngine() {
|
|
return this.engine;
|
|
}
|
|
|
|
public static MiaoScriptEngine getDefault() {
|
|
if (DEFAULT == null) {
|
|
DEFAULT = new MiaoScriptEngine();
|
|
}
|
|
return DEFAULT;
|
|
}
|
|
|
|
@Override
|
|
public Bindings createBindings() {
|
|
return new SimpleBindings(new HashMap<>(engine.getBindings(ScriptContext.GLOBAL_SCOPE)));
|
|
}
|
|
|
|
@Override
|
|
public Object eval(final Reader reader) throws ScriptException {
|
|
return engine.eval(reader);
|
|
}
|
|
|
|
@Override
|
|
public Object eval(final Reader reader, final Bindings n) throws ScriptException {
|
|
return engine.eval(reader, n);
|
|
}
|
|
|
|
@Override
|
|
public Object eval(final Reader reader, final ScriptContext context) throws ScriptException {
|
|
return engine.eval(reader, context);
|
|
}
|
|
|
|
@Override
|
|
public Object eval(final String script) throws ScriptException {
|
|
return engine.eval(script);
|
|
}
|
|
|
|
@Override
|
|
public Object eval(final String script, final Bindings n) throws ScriptException {
|
|
return engine.eval(script, n);
|
|
}
|
|
|
|
@Override
|
|
public Object eval(final String script, final ScriptContext context) throws ScriptException {
|
|
return engine.eval(script, context);
|
|
}
|
|
|
|
@Override
|
|
public Object get(final String key) {
|
|
return engine.get(key);
|
|
}
|
|
|
|
@Override
|
|
public Bindings getBindings(final int scope) {
|
|
return engine.getBindings(scope);
|
|
}
|
|
|
|
@Override
|
|
public ScriptContext getContext() {
|
|
return engine.getContext();
|
|
}
|
|
|
|
@Override
|
|
public ScriptEngineFactory getFactory() {
|
|
return engine.getFactory();
|
|
}
|
|
|
|
@Override
|
|
public <T> T getInterface(final Class<T> clasz) {
|
|
return ((Invocable) engine).getInterface(clasz);
|
|
}
|
|
|
|
@Override
|
|
public <T> T getInterface(final Object thiz, final Class<T> clasz) {
|
|
return ((Invocable) engine).getInterface(thiz, clasz);
|
|
}
|
|
|
|
@Override
|
|
public Object invokeFunction(final String name, final Object... args) throws ScriptException, NoSuchMethodException {
|
|
return ((Invocable) engine).invokeFunction(name, args);
|
|
}
|
|
|
|
@Override
|
|
public Object invokeMethod(final Object thiz, final String name, final Object... args) throws ScriptException, NoSuchMethodException {
|
|
return ((Invocable) engine).invokeMethod(thiz, name, args);
|
|
}
|
|
|
|
@Override
|
|
public void put(final String key, final Object value) {
|
|
engine.put(key, value);
|
|
}
|
|
|
|
@Override
|
|
public void setBindings(final Bindings bindings, final int scope) {
|
|
engine.setBindings(bindings, scope);
|
|
}
|
|
|
|
@Override
|
|
public void setContext(final ScriptContext context) {
|
|
engine.setContext(context);
|
|
}
|
|
}
|