package com.stardust.autojs.engine;
import android.util.Log;
import com.stardust.autojs.rhino.AndroidContextFactory;
import com.stardust.autojs.rhino.RhinoAndroidHelper;
import com.stardust.autojs.runtime.ScriptInterruptedException;
import com.stardust.autojs.script.ScriptSource;
import com.stardust.pio.UncheckedIOException;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.ContextFactory;
import org.mozilla.javascript.ImporterTopLevel;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.ScriptableObject;
import org.mozilla.javascript.commonjs.module.RequireBuilder;
import org.mozilla.javascript.commonjs.module.provider.SoftCachingModuleScriptProvider;
import java.io.File;
import java.io.IOException;
import java.io.Reader;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* Created by Stardust on 2017/4/2.
*/
public class RhinoJavaScriptEngine implements ScriptEngine {
private static final String LOG_TAG = "RhinoJavaScriptEngine";
private static int contextCount = 0;
private String[] mRequirePath = new String[0];
private Context mContext;
private Scriptable mScriptable;
private Thread mThread;
private RhinoJavaScriptEngineManager mEngineManager;
private Map<String, Object> mTags = new ConcurrentHashMap<>();
public RhinoJavaScriptEngine(RhinoJavaScriptEngineManager engineManager) {
mEngineManager = engineManager;
mThread = Thread.currentThread();
mContext = createContext();
mScriptable = createScope(mContext);
}
@Override
public void put(String name, Object value) {
ScriptableObject.putProperty(mScriptable, name, Context.javaToJS(value, mScriptable));
}
@Override
public Object execute(ScriptSource source) {
Reader reader = source.getNonNullScriptReader();
try {
reader = preprocess(reader);
return mContext.evaluateReader(mScriptable, reader, "<" + source.getName() + ">", 1, null);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
protected Reader preprocess(Reader script) throws IOException {
return script;
}
@Override
public void forceStop() {
Log.d(LOG_TAG, "forceStop: interrupt Thread: " + mThread);
mThread.interrupt();
}
public RhinoJavaScriptEngineManager getEngineManager() {
return mEngineManager;
}
@Override
public synchronized void destroy() {
Log.d(LOG_TAG, "on destroy");
Context.exit();
contextCount--;
Log.d(LOG_TAG, "contextCount = " + contextCount);
mEngineManager.removeEngine(this);
}
@Override
public synchronized void setTag(String key, Object value) {
mTags.put(key, value);
}
@Override
public synchronized Object getTag(String key) {
return mTags.get(key);
}
@Override
public void init() {
ScriptableObject.putProperty(mScriptable, "__engine_name__", "rhino");
ScriptableObject.putProperty(mScriptable, "__engine__", this);
mRequirePath = (String[]) getTag("__require_path__");
initRequireBuilder(mContext, mScriptable);
mContext.evaluateString(mScriptable, mEngineManager.getInitScript().getScript(), "<init>", 1, null);
}
public void setRequirePath(String... requirePath) {
setTag("__require_path__", requirePath);
}
void initRequireBuilder(Context context, Scriptable scope) {
List<URI> list = new ArrayList<>();
for (String path : mRequirePath) {
list.add(new File(path).toURI());
}
AssetAndUrlModuleSourceProvider provider = new AssetAndUrlModuleSourceProvider(getEngineManager().getContext(), list);
new RequireBuilder()
.setModuleScriptProvider(new SoftCachingModuleScriptProvider(provider))
.setSandboxed(false)
.createRequire(context, scope)
.install(scope);
}
public Context getContext() {
return mContext;
}
public Scriptable getScriptable() {
return mScriptable;
}
protected Scriptable createScope(Context context) {
ImporterTopLevel importerTopLevel = new ImporterTopLevel();
importerTopLevel.initStandardObjects(context, false);
return importerTopLevel;
}
protected Context createContext() {
if (!ContextFactory.hasExplicitGlobal()) {
ContextFactory.initGlobal(new InterruptibleAndroidContextFactory(new File(mEngineManager.getContext().getCacheDir(), "classes")));
}
Context context = new RhinoAndroidHelper(mEngineManager.getContext()).enterContext();
contextCount++;
context.setOptimizationLevel(-1);
context.setLanguageVersion(Context.VERSION_ES6);
return context;
}
private static class InterruptibleAndroidContextFactory extends AndroidContextFactory {
public InterruptibleAndroidContextFactory(File cacheDirectory) {
super(cacheDirectory);
}
@Override
protected void observeInstructionCount(Context cx, int instructionCount) {
if (Thread.currentThread().isInterrupted()) {
throw new ScriptInterruptedException();
}
}
@Override
protected Context makeContext() {
Context cx = super.makeContext();
cx.setInstructionObserverThreshold(10000);
return cx;
}
}
}