+ * NB: For simplicity, we catch and wrap InterruptedException. Do NOT use this class if you
+ * are in the 1% of cases where you actually want to handle that.
+ */
+ public T get(long timeoutMS) {
+ try {
+ if (!mReadyLatch.await(timeoutMS, TimeUnit.MILLISECONDS)) {
+ throw new TimeoutException();
+ }
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ return mResult;
+ }
+
+ public T get() {
+ try {
+ mReadyLatch.await();
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ return mResult;
+ }
+
+ public static class TimeoutException extends RuntimeException {
+
+ public TimeoutException() {
+ super("Timed out waiting for future");
+ }
+ }
+}
diff --git a/doric/src/main/java/pub/doric/engine/DoricJSEngine.java b/doric/src/main/java/pub/doric/engine/DoricJSEngine.java
new file mode 100644
index 00000000..a4bf460f
--- /dev/null
+++ b/doric/src/main/java/pub/doric/engine/DoricJSEngine.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright [2019] [Doric.Pub]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package pub.doric.engine;
+
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.text.TextUtils;
+
+import com.github.pengfeizhou.jscore.JSDecoder;
+import com.github.pengfeizhou.jscore.JavaFunction;
+import com.github.pengfeizhou.jscore.JavaValue;
+
+import java.util.ArrayList;
+
+import pub.doric.DoricRegistry;
+import pub.doric.extension.bridge.DoricBridgeExtension;
+import pub.doric.extension.timer.DoricTimerExtension;
+import pub.doric.utils.DoricConstant;
+import pub.doric.utils.DoricLog;
+import pub.doric.utils.DoricUtils;
+
+/**
+ * @Description: Doric
+ * @Author: pengfei.zhou
+ * @CreateDate: 2019-07-18
+ */
+public class DoricJSEngine implements Handler.Callback, DoricTimerExtension.TimerCallback {
+
+ private HandlerThread handlerThread;
+ private final Handler mJSHandler;
+ private final DoricBridgeExtension mDoricBridgeExtension = new DoricBridgeExtension();
+ protected IDoricJSE mDoricJSE;
+ private final DoricTimerExtension mTimerExtension;
+ private final DoricRegistry mDoricRegistry = new DoricRegistry();
+
+ public DoricJSEngine() {
+ handlerThread = new HandlerThread(this.getClass().getSimpleName());
+ handlerThread.start();
+ Looper looper = handlerThread.getLooper();
+ mJSHandler = new Handler(looper, this);
+ mJSHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ initJSEngine();
+ injectGlobal();
+ initDoricRuntime();
+ }
+ });
+ mTimerExtension = new DoricTimerExtension(looper, this);
+ }
+
+ public Handler getJSHandler() {
+ return mJSHandler;
+ }
+
+ protected void initJSEngine() {
+ mDoricJSE = new DoricNativeJSExecutor();
+ }
+
+ private void injectGlobal() {
+ mDoricJSE.injectGlobalJSFunction(DoricConstant.INJECT_LOG, new JavaFunction() {
+ @Override
+ public JavaValue exec(JSDecoder[] args) {
+ try {
+ String type = args[0].string();
+ String message = args[1].string();
+ switch (type) {
+ case "w":
+ DoricLog.suffix_w("_js", message);
+ break;
+ case "e":
+ DoricLog.suffix_e("_js", message);
+ break;
+ default:
+ DoricLog.suffix_d("_js", message);
+ break;
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+ });
+ mDoricJSE.injectGlobalJSFunction(DoricConstant.INJECT_EMPTY, new JavaFunction() {
+ @Override
+ public JavaValue exec(JSDecoder[] args) {
+ return null;
+ }
+ });
+ mDoricJSE.injectGlobalJSFunction(DoricConstant.INJECT_REQUIRE, new JavaFunction() {
+ @Override
+ public JavaValue exec(JSDecoder[] args) {
+ try {
+ String name = args[0].string();
+ String content = mDoricRegistry.acquireJSBundle(name);
+ if (TextUtils.isEmpty(content)) {
+ DoricLog.e("require js bundle:%s is empty", name);
+ return new JavaValue(false);
+ }
+ mDoricJSE.loadJS(packageModuleScript(name, content), "Module://" + name);
+ return new JavaValue(true);
+ } catch (Exception e) {
+ e.printStackTrace();
+ return new JavaValue(false);
+ }
+ }
+ });
+ mDoricJSE.injectGlobalJSFunction(DoricConstant.INJECT_TIMER_SET, new JavaFunction() {
+ @Override
+ public JavaValue exec(JSDecoder[] args) {
+ try {
+ mTimerExtension.setTimer(
+ args[0].number().longValue(),
+ args[1].number().longValue(),
+ args[2].bool());
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+ });
+ mDoricJSE.injectGlobalJSFunction(DoricConstant.INJECT_TIMER_CLEAR, new JavaFunction() {
+ @Override
+ public JavaValue exec(JSDecoder[] args) {
+ try {
+ mTimerExtension.clearTimer(args[0].number().longValue());
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+ });
+ mDoricJSE.injectGlobalJSFunction(DoricConstant.INJECT_BRIDGE, new JavaFunction() {
+ @Override
+ public JavaValue exec(JSDecoder[] args) {
+ try {
+ String contextId = args[0].string();
+ String module = args[1].string();
+ String method = args[2].string();
+ String callbackId = args[3].string();
+ JSDecoder jsDecoder = args[4];
+ return mDoricBridgeExtension.callNative(contextId, module, method, callbackId, jsDecoder);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+ });
+ }
+
+ private void initDoricRuntime() {
+ loadBuiltinJS(DoricConstant.DORIC_BUNDLE_SANDBOX);
+ String libName = DoricConstant.DORIC_MODULE_LIB;
+ String libJS = DoricUtils.readAssetFile(DoricConstant.DORIC_BUNDLE_LIB);
+ mDoricJSE.loadJS(packageModuleScript(libName, libJS), "Module://" + libName);
+ }
+
+ @Override
+ public boolean handleMessage(Message msg) {
+ return false;
+ }
+
+ public void teardown() {
+ mDoricJSE.teardown();
+ mTimerExtension.teardown();
+ handlerThread.quit();
+ mJSHandler.removeCallbacksAndMessages(null);
+ }
+
+ private void loadBuiltinJS(String assetName) {
+ String script = DoricUtils.readAssetFile(assetName);
+ mDoricJSE.loadJS(script, "Assets://" + assetName);
+ }
+
+ public void prepareContext(final String contextId, final String script, final String source) {
+ mDoricJSE.loadJS(packageContextScript(contextId, script), "Context://" + source);
+ }
+
+ public void destroyContext(final String contextId) {
+ mDoricJSE.loadJS(String.format(DoricConstant.TEMPLATE_CONTEXT_DESTROY, contextId), "_Context://" + contextId);
+ }
+
+ private String packageContextScript(String contextId, String content) {
+ return String.format(DoricConstant.TEMPLATE_CONTEXT_CREATE, content, contextId, contextId, contextId);
+ }
+
+ private String packageModuleScript(String moduleName, String content) {
+ return String.format(DoricConstant.TEMPLATE_MODULE, moduleName, content);
+ }
+
+ public JSDecoder invokeDoricMethod(final String method, final Object... args) {
+ ArrayList