From 963f2b698c3c0e09b677daa997e9208836931d0c Mon Sep 17 00:00:00 2001 From: zaaarf Date: Sun, 15 Sep 2024 01:56:51 +0200 Subject: [PATCH] feat(java): callback API, buffer send Co-authored-by: alemi --- src/ffi/java/buffer.rs | 69 ++++++++++++++++++++++++++++++++++++- src/ffi/java/cursor.rs | 12 ++++++- src/ffi/java/mod.rs | 72 +++++++++++++++++++++++++++++++++++++-- src/ffi/java/workspace.rs | 23 +++++-------- 4 files changed, 157 insertions(+), 19 deletions(-) diff --git a/src/ffi/java/buffer.rs b/src/ffi/java/buffer.rs index 57dbb5a..8315feb 100644 --- a/src/ffi/java/buffer.rs +++ b/src/ffi/java/buffer.rs @@ -1,6 +1,6 @@ use jni::{objects::{JClass, JObject, JValueGen}, sys::{jlong, jobject, jstring}, JNIEnv}; -use crate::api::Controller; +use crate::{api::Controller, ffi::java::handle_callback}; use super::{JExceptable, RT}; @@ -87,3 +87,70 @@ fn recv_jni(env: &mut JNIEnv, change: Option) -> jobject } }.as_raw() } + +#[no_mangle] +pub extern "system" fn Java_mp_code_BufferController_callback<'local>( + mut env: JNIEnv, + _class: JClass<'local>, + self_ptr: jlong, + cb: JObject<'local>, +) { + handle_callback!("mp/code/BufferController", env, self_ptr, cb, crate::buffer::Controller); +} + +/// Receive from Java, converts and sends a [crate::api::TextChange]. +#[no_mangle] +pub extern "system" fn Java_mp_code_BufferController_send<'local>( + mut env: JNIEnv, + _class: JClass<'local>, + self_ptr: jlong, + input: JObject<'local>, +) { + let Ok(start) = env.get_field(&input, "start", "I") + .and_then(|sr| sr.i()) + .jexcept(&mut env) + .try_into() + else { + env.throw_new("java/lang/IllegalArgumentException", "Start index cannot be negative!") + .expect("Failed to throw exception!"); + return; + }; + + let Ok(end) = env.get_field(&input, "end", "I") + .and_then(|er| er.i()) + .jexcept(&mut env) + .try_into() + else { + env.throw_new("java/lang/IllegalArgumentException", "End index cannot be negative!") + .expect("Failed to throw exception!"); + return; + }; + + let content = env.get_field(&input, "content", "Ljava/lang/String;") + .and_then(|b| b.l()) + .map(|b| b.into()) + .jexcept(&mut env); + let content = env.get_string(&content) + .map(|b| b.into()) + .jexcept(&mut env); + + let hash = env.get_field(&input, "hash", "Ljava/util/OptionalLong") + .and_then(|hash| hash.l()) + .and_then(|hash| { + if env.call_method(&hash, "isPresent", "()Z", &[]).and_then(|r| r.z()).jexcept(&mut env) { + env.call_method(&hash, "getAsLong", "()J", &[]) + .and_then(|r| r.j()) + .map(Some) + } else { + Ok(None) + } + }).jexcept(&mut env); + + let controller = unsafe { Box::leak(Box::from_raw(self_ptr as *mut crate::buffer::Controller)) }; + RT.block_on(controller.send(crate::api::TextChange { + start, + end, + content, + hash, + })).jexcept(&mut env); +} diff --git a/src/ffi/java/cursor.rs b/src/ffi/java/cursor.rs index 2472407..31f43a5 100644 --- a/src/ffi/java/cursor.rs +++ b/src/ffi/java/cursor.rs @@ -1,7 +1,7 @@ use jni::{objects::{JClass, JObject, JString, JValueGen}, sys::{jlong, jobject}, JNIEnv}; use crate::api::Controller; -use super::{JExceptable, RT}; +use super::{handle_callback, JExceptable, RT}; /// Try to fetch a [crate::api::Cursor], or returns null if there's nothing. #[no_mangle] @@ -56,6 +56,16 @@ fn jni_recv(env: &mut JNIEnv, cursor: Option) -> jobject { }.as_raw() } +#[no_mangle] +pub extern "system" fn Java_mp_code_CursorController_callback<'local>( + mut env: JNIEnv, + _class: JClass<'local>, + self_ptr: jlong, + cb: JObject<'local>, +) { + handle_callback!("mp/code/CursorController", env, self_ptr, cb, crate::cursor::Controller); +} + /// Receive from Java, converts and sends a [crate::api::Cursor]. #[no_mangle] pub extern "system" fn Java_mp_code_CursorController_send<'local>( diff --git a/src/ffi/java/mod.rs b/src/ffi/java/mod.rs index c75f7f5..54cc19a 100644 --- a/src/ffi/java/mod.rs +++ b/src/ffi/java/mod.rs @@ -105,7 +105,7 @@ impl JExceptable for Result where T: Default { /// This is essentially the same as [TryInto], but that can't be emplemented on non-local types. pub(crate) trait JObjectify<'local> { /// The error type, likely to be [jni::errors::Error]. - type Error; + type Error: std::fmt::Debug; /// Attempt to convert the given object to a [jni::objects::JObject]. fn jobjectify(self, env: &mut jni::JNIEnv<'local>) -> Result, Self::Error>; @@ -113,7 +113,7 @@ pub(crate) trait JObjectify<'local> { impl<'local> JObjectify<'local> for uuid::Uuid { type Error = jni::errors::Error; - fn jobjectify(self, env: &mut jni::JNIEnv<'local>) -> Result, jni::errors::Error> { + fn jobjectify(self, env: &mut jni::JNIEnv<'local>) -> Result, Self::Error> { env.find_class("java/util/UUID").and_then(|class| { let (msb, lsb) = self.as_u64_pair(); let msb = i64::from_ne_bytes(msb.to_ne_bytes()); @@ -122,3 +122,71 @@ impl<'local> JObjectify<'local> for uuid::Uuid { }) } } + +impl<'local> JObjectify<'local> for crate::cursor::Controller { + type Error = jni::errors::Error; + + fn jobjectify(self, env: &mut jni::JNIEnv<'local>) -> Result, Self::Error> { + let class = env.find_class("mp/code/CursorController")?; + env.new_object( + class, + "(J)V", + &[ + jni::objects::JValueGen::Long(Box::into_raw(Box::new(&self)) as jni::sys::jlong) + ] + ) + } +} + +impl<'local> JObjectify<'local> for crate::buffer::Controller { + type Error = jni::errors::Error; + + fn jobjectify(self, env: &mut jni::JNIEnv<'local>) -> Result, Self::Error> { + let class = env.find_class("mp/code/BufferController")?; + env.new_object( + class, + "(J)V", + &[ + jni::objects::JValueGen::Long(Box::into_raw(Box::new(&self)) as jni::sys::jlong) + ] + ) + } +} + +macro_rules! handle_callback { + ($jtype:literal, $env:ident, $self_ptr:ident, $cb:ident, $t:ty) => { + let controller = unsafe { Box::leak(Box::from_raw($self_ptr as *mut $t)) }; + + let Ok(jvm) = $env.get_java_vm() else { + $env.throw_new("mp/code/exceptions/JNIException", "Failed to get JVM reference!") + .expect("Failed to throw exception!"); + return; + }; + + let Ok(cb_ref) = $env.new_global_ref($cb) else { + $env.throw_new("mp/code/exceptions/JNIException", "Failed to pin callback reference!") + .expect("Failed to throw exception!"); + return; + }; + controller.callback(move |controller: $t| { + use std::ops::DerefMut; + use crate::ffi::java::JObjectify; + let mut guard = jvm.attach_current_thread().unwrap(); + let jcontroller = match controller.jobjectify(guard.deref_mut()) { + Err(e) => return tracing::error!("could not convert callback argument: {e:?}"), + Ok(x) => x, + }; + let sig = format!("(L{};)V", $jtype); + if let Err(e) = guard.call_method(&cb_ref, + "invoke", + &sig, + &[jni::objects::JValueGen::Object(&jcontroller)] + ) { + tracing::error!("error invoking callback: {e:?}"); + } + }); + }; +} + +pub(crate) use handle_callback; + diff --git a/src/ffi/java/workspace.rs b/src/ffi/java/workspace.rs index f024eda..540580f 100644 --- a/src/ffi/java/workspace.rs +++ b/src/ffi/java/workspace.rs @@ -22,9 +22,7 @@ pub extern "system" fn Java_mp_code_Workspace_get_1cursor<'local>( self_ptr: jlong ) -> jobject { let workspace = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Workspace)) }; - env.find_class("mp/code/CursorController").and_then(|class| - env.new_object(class, "(J)V", &[JValueGen::Long(Box::into_raw(Box::new(workspace.cursor())) as jlong)]) - ).jexcept(&mut env).as_raw() + workspace.cursor().jobjectify(&mut env).jexcept(&mut env).as_raw() } /// Get a buffer controller by name and returns a pointer to it. @@ -39,12 +37,10 @@ pub extern "system" fn Java_mp_code_Workspace_get_1buffer<'local>( let path = unsafe { env.get_string_unchecked(&input) } .map(|path| path.to_string_lossy().to_string()) .jexcept(&mut env); - - workspace.buffer_by_name(&path).map(|buf| { - env.find_class("mp/code/BufferController").and_then(|class| - env.new_object(class, "(J)V", &[JValueGen::Long(Box::into_raw(Box::new(buf)) as jlong)]) - ).jexcept(&mut env) - }).unwrap_or_default().as_raw() + workspace.buffer_by_name(&path) + .map(|buf| buf.jobjectify(&mut env).jexcept(&mut env)) + .unwrap_or_default() + .as_raw() } /// Create a new buffer. @@ -108,12 +104,9 @@ pub extern "system" fn Java_mp_code_Workspace_attach_1to_1buffer<'local>( .map(|path| path.to_string_lossy().to_string()) .jexcept(&mut env); RT.block_on(workspace.attach(&path)) - .map(|buffer| Box::into_raw(Box::new(buffer)) as jlong) - .map(|ptr| { - env.find_class("mp/code/BufferController") - .and_then(|class| env.new_object(class, "(J)V", &[JValueGen::Long(ptr)])) - .jexcept(&mut env) - }).jexcept(&mut env).as_raw() + .map(|buffer| buffer.jobjectify(&mut env).jexcept(&mut env)) + .jexcept(&mut env) + .as_raw() } /// Detach from a buffer.