YumCore/src/main/java/pw/yumc/YumCore/engine/MiaoScriptEngine.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);
}
}