summaryrefslogtreecommitdiff
path: root/akono/src/main/java/net/taler/akono/AkonoJni.kt
diff options
context:
space:
mode:
Diffstat (limited to 'akono/src/main/java/net/taler/akono/AkonoJni.kt')
-rw-r--r--akono/src/main/java/net/taler/akono/AkonoJni.kt151
1 files changed, 151 insertions, 0 deletions
diff --git a/akono/src/main/java/net/taler/akono/AkonoJni.kt b/akono/src/main/java/net/taler/akono/AkonoJni.kt
new file mode 100644
index 00000000..0824e268
--- /dev/null
+++ b/akono/src/main/java/net/taler/akono/AkonoJni.kt
@@ -0,0 +1,151 @@
+package net.taler.akono
+
+import android.util.Base64
+import android.util.Log
+import java.lang.Exception
+import java.nio.ByteBuffer
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.LinkedBlockingDeque
+import kotlin.concurrent.thread
+
+typealias AkonoNativePointer = ByteBuffer
+
+private const val TAG = "AkonoJni"
+
+class AkonoJni(vararg nodeArgv: String) {
+ private var messageHandler: MessageHandler? = null
+ private val initializedLatch = CountDownLatch(1)
+
+ private val workQueue = LinkedBlockingDeque<() -> Unit>()
+
+ private external fun evalJs(source: String, p: AkonoNativePointer): String
+ private external fun runNode(p: AkonoNativePointer)
+
+ private external fun makeCallbackNative(source: String, p: AkonoNativePointer)
+ private external fun putModuleCodeNative(key: String, source: String)
+
+ private external fun destroyNative(b: AkonoNativePointer)
+ private external fun initNative(nodeArgv: Array<out String>): AkonoNativePointer
+ private external fun notifyNative(b: AkonoNativePointer)
+
+ private lateinit var internalNativePointer: AkonoNativePointer
+
+ private val jniThread: Thread
+
+ private var stopped = false
+
+ /**
+ * Schedule a block do be executed in the node thread.
+ */
+ private fun scheduleNodeThread(b: () -> Unit) {
+ initializedLatch.await()
+ workQueue.put(b)
+ notifyNative()
+ }
+
+ /**
+ * Called by node/v8 from its thread.
+ */
+ private fun internalOnNotify(payload: String) {
+ messageHandler?.handleMessage(payload)
+ }
+
+
+ fun notifyNative() {
+ initializedLatch.await()
+ notifyNative(internalNativePointer)
+ }
+
+ /**
+ * Schedule Node.JS to be run.
+ */
+ fun evalSimpleJs(source: String): String {
+ val latch = CountDownLatch(1)
+ var result: String? = null
+ scheduleNodeThread {
+ result = evalJs(source, internalNativePointer)
+ latch.countDown()
+ }
+ latch.await()
+ return result ?: throw Exception("invariant failed")
+ }
+
+ fun evalNodeCode(source: String) {
+ scheduleNodeThread {
+ makeCallbackNative(source, internalNativePointer)
+ }
+ }
+
+ /**
+ * Send a message to node, calling global.__akono_onMessage.
+ */
+ fun sendMessage(message: String) {
+ val encoded = Base64.encodeToString(message.toByteArray(), Base64.NO_WRAP)
+ val source = """
+ if (global.__native_onMessage) {
+ const msg = (new Buffer('$encoded', 'base64')).toString('ascii');
+ global.__native_onMessage(msg);
+ } else {
+ console.log("WARN: no __native_onMessage defined");
+ }
+ """.trimIndent()
+ evalNodeCode(source)
+ }
+
+ fun waitStopped() {
+ Log.i(TAG, "waiting for stop")
+ scheduleNodeThread {
+ stopped = true
+ }
+ jniThread.join()
+ return
+ }
+
+ fun putModuleCode(modName: String, code: String) {
+ Log.v(TAG, "putting module code (kotlin)")
+ putModuleCodeNative(modName, code)
+ }
+
+ /**
+ * Register a message handler that is called when the JavaScript code
+ * running in [runNodeJs] calls __native_sendMessage
+ *
+ * Does not block.
+ */
+ fun setMessageHandler(handler: MessageHandler) {
+ this.messageHandler = handler
+ }
+
+
+ @Override
+ protected fun finalize() {
+ destroyNative(internalNativePointer)
+ }
+
+ init {
+ jniThread = thread {
+ internalNativePointer = initNative(nodeArgv)
+ initializedLatch.countDown()
+ while (true) {
+ runNode(internalNativePointer)
+ while (true) {
+ val w = workQueue.poll() ?: break
+ w()
+ }
+ if (stopped) {
+ break
+ }
+ }
+ }
+ }
+
+ companion object {
+ init {
+ System.loadLibrary("akono-jni")
+ }
+ }
+
+ interface MessageHandler {
+ fun handleMessage(message: String)
+ }
+}