diff --git a/src/ffi/java/buffer.rs b/src/ffi/java/buffer.rs index 7731617..c5847d4 100644 --- a/src/ffi/java/buffer.rs +++ b/src/ffi/java/buffer.rs @@ -1,95 +1,44 @@ -use jni::{objects::{JClass, JObject}, sys::{jboolean, jlong, jobject, jstring}, JNIEnv}; +use jni::{objects::JObject, JNIEnv}; +use jni_toolbox::jni; -use crate::api::{Controller, TextChange}; +use crate::{api::{Controller, TextChange}, errors::ControllerError}; -use super::{handle_error, null_check, tokio, Deobjectify, JExceptable, JObjectify}; +use super::null_check; -/// Gets the name of the buffer. -#[no_mangle] -pub extern "system" fn Java_mp_code_BufferController_get_1name( - mut env: JNIEnv, - _class: JClass, - self_ptr: jlong, -) -> jstring { - let controller = unsafe { Box::leak(Box::from_raw(self_ptr as *mut crate::buffer::Controller)) }; - let content = controller.path(); - env.new_string(content) - .jexcept(&mut env) - .as_raw() +/// Get the name of the buffer. +#[jni(package = "mp.code", class = "BufferController", ptr)] +fn get_name(controller: &mut crate::buffer::Controller) -> String { + controller.path().to_string() //TODO: &str is built into the newer version } -/// Gets the contents of the buffers. -#[no_mangle] -pub extern "system" fn Java_mp_code_BufferController_get_1content( - mut env: JNIEnv, - _class: JClass, - self_ptr: jlong, -) -> jstring { - let controller = unsafe { Box::leak(Box::from_raw(self_ptr as *mut crate::buffer::Controller)) }; - let content = tokio().block_on(controller.content()) - .jexcept(&mut env); - env.new_string(content) - .jexcept(&mut env) - .as_raw() +/// Get the contents of the buffers. +#[jni(package = "mp.code", class = "BufferController", ptr)] +fn get_content(controller: &mut crate::buffer::Controller) -> Result { + super::tokio().block_on(controller.content()) } -/// Tries to fetch a [TextChange], or returns null if there's nothing. -#[no_mangle] -pub extern "system" fn Java_mp_code_BufferController_try_1recv( - mut env: JNIEnv, - _class: JClass, - self_ptr: jlong, -) -> jobject { - let controller = unsafe { Box::leak(Box::from_raw(self_ptr as *mut crate::buffer::Controller)) }; - tokio().block_on(controller.try_recv()) - .jexcept(&mut env) - .map(|change| change.jobjectify(&mut env).jexcept(&mut env).as_raw()) - .unwrap_or_else(std::ptr::null_mut) +/// Try to fetch a [TextChange], or return null if there's nothing. +#[jni(package = "mp.code", class = "BufferController", ptr)] +fn try_recv(controller: &mut crate::buffer::Controller) -> Result, ControllerError> { + super::tokio().block_on(controller.try_recv()) } -/// Blocks until it receives a [TextChange]. -#[no_mangle] -pub extern "system" fn Java_mp_code_BufferController_recv( - mut env: JNIEnv, - _class: JClass, - self_ptr: jlong, -) -> jobject { - let controller = unsafe { Box::leak(Box::from_raw(self_ptr as *mut crate::buffer::Controller)) }; - tokio().block_on(controller.recv()) - .jexcept(&mut env) - .jobjectify(&mut env) - .jexcept(&mut env) - .as_raw() +/// Block until it receives a [TextChange]. +#[jni(package = "mp.code", class = "BufferController", ptr)] +fn recv(controller: &mut crate::buffer::Controller) -> Result { + super::tokio().block_on(controller.recv()) } -/// Receive from Java, converts and sends a [TextChange]. -#[no_mangle] -pub extern "system" fn Java_mp_code_BufferController_send<'local>( - mut env: JNIEnv<'local>, - _class: JClass<'local>, - self_ptr: jlong, - change: JObject<'local>, -) { - null_check!(env, change, {}); - let controller = unsafe { Box::leak(Box::from_raw(self_ptr as *mut crate::buffer::Controller)) }; - let change = TextChange::deobjectify(&mut env, change); - if let Ok(change) = change { - tokio().block_on(controller.send(change)).jexcept(&mut env) - } else { - handle_error!(&mut env, change, {}); - } +/// Send a [TextChange] to the server. +#[jni(package = "mp.code", class = "BufferController")] +fn send(controller: &mut crate::buffer::Controller, change: TextChange) -> Result<(), ControllerError> { + super::tokio().block_on(controller.send(change)) } -/// Registers a callback for buffer changes. -#[no_mangle] -pub extern "system" fn Java_mp_code_BufferController_callback<'local>( - mut env: JNIEnv, - _class: JClass<'local>, - self_ptr: jlong, - cb: JObject<'local>, -) { +/// Register a callback for buffer changes. +#[jni(package = "mp.code", class = "BufferController")] +fn callback<'local>(env: &mut JNIEnv<'local>, controller: &mut crate::buffer::Controller, cb: JObject<'local>) { null_check!(env, cb, {}); - let controller = unsafe { Box::leak(Box::from_raw(self_ptr as *mut crate::buffer::Controller)) }; 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!"); @@ -101,8 +50,8 @@ pub extern "system" fn Java_mp_code_BufferController_callback<'local>( let mut env = jvm.attach_current_thread_permanently() .expect("failed attaching to main JVM thread"); if let Err(e) = env.with_local_frame(5, |env| { - use crate::ffi::java::JObjectify; - let jcontroller = controller.jobjectify(env)?; + use jni_toolbox::IntoJavaObject; + let jcontroller = controller.into_java(env)?; if let Err(e) = env.call_method( &cb_ref, "accept", @@ -119,46 +68,26 @@ pub extern "system" fn Java_mp_code_BufferController_callback<'local>( }); } -/// Clears the callback for buffer changes. -#[no_mangle] -pub extern "system" fn Java_mp_code_BufferController_clear_1callback( - _env: JNIEnv, - _class: JClass, - self_ptr: jlong, -) { - unsafe { Box::leak(Box::from_raw(self_ptr as *mut crate::buffer::Controller)) } - .clear_callback(); +/// Clear the callback for buffer changes. +#[jni(package = "mp.code", class = "BufferController")] +fn clear_callback(controller: &mut crate::buffer::Controller) { + controller.clear_callback() } -/// Blocks until there is a new value available. -#[no_mangle] -pub extern "system" fn Java_mp_code_BufferController_poll( - mut env: JNIEnv, - _class: JClass, - self_ptr: jlong, -) { - let controller = unsafe { Box::leak(Box::from_raw(self_ptr as *mut crate::buffer::Controller)) }; - tokio().block_on(controller.poll()) - .jexcept(&mut env); +/// Block until there is a new value available. +#[jni(package = "mp.code", class = "BufferController")] +fn poll(controller: &mut crate::buffer::Controller) -> Result<(), ControllerError> { + super::tokio().block_on(controller.poll()) } -/// Stops the controller. -#[no_mangle] -pub extern "system" fn Java_mp_code_BufferController_stop( - _env: JNIEnv, - _class: JClass, - self_ptr: jlong, -) -> jboolean { - let controller = unsafe { Box::leak(Box::from_raw(self_ptr as *mut crate::buffer::Controller)) }; - controller.stop() as jboolean +/// Stop the controller. +#[jni(package = "mp.code", class = "BufferController")] +fn stop(controller: &mut crate::buffer::Controller) -> bool { + controller.stop() } /// Called by the Java GC to drop a [crate::buffer::Controller]. -#[no_mangle] -pub extern "system" fn Java_mp_code_BufferController_free( - _env: JNIEnv, - _class: JClass, - self_ptr: jlong, -) { - let _ = unsafe { Box::from_raw(self_ptr as *mut crate::buffer::Controller) }; +#[jni(package = "mp.code", class = "BufferController")] +fn free(input: jni::sys::jlong) { + let _ = unsafe { Box::from_raw(input as *mut crate::buffer::Controller) }; } diff --git a/src/ffi/java/client.rs b/src/ffi/java/client.rs index 7472f75..2f3d51b 100644 --- a/src/ffi/java/client.rs +++ b/src/ffi/java/client.rs @@ -1,144 +1,74 @@ -use jni::{objects::{JClass, JObject, JString}, sys::{jboolean, jlong, jobject, jobjectArray}, JNIEnv}; -use jni_toolbox::{jni, FromJava, IntoJava, JniToolboxError}; -use crate::{api::Config, client::Client, errors::{ConnectionError, RemoteError}, ffi::java::{handle_error, null_check}, Workspace}; - -use super::{Deobjectify, JExceptable, JObjectify, tokio}; - -impl<'j> IntoJava<'j> for Client { - type T = jobject; - - fn into_java(self, env: &mut jni::JNIEnv<'j>) -> Result { - // Ok(Box::into_raw(Box::new(self))) - todo!() - } -} - -impl<'j> FromJava<'j> for Client { - type T = jobject; - - fn from_java(env: &mut jni::JNIEnv<'j>, value: Self::T) -> Result { - let x = unsafe { Box::leak(Box::from_raw(value as *mut Client)) }; - todo!(); - Ok(x.clone()) - } -} - -impl<'j> FromJava<'j> for Config { - type T = JObject<'j>; - fn from_java(env: &mut jni::JNIEnv<'j>, value: Self::T) -> Result { - Ok(Config::deobjectify(env, value)?) - } -} - -impl<'j> IntoJava<'j> for crate::api::User { - type T = jobject; - fn into_java(self, env: &mut jni::JNIEnv<'j>) -> Result { - Ok(self.jobjectify(env)?.into_raw()) - } -} - -impl<'j> IntoJava<'j> for Workspace { - type T = jobject; - fn into_java(self, env: &mut jni::JNIEnv<'j>) -> Result { - Ok(self.jobjectify(env)?.into_raw()) - } -} - -impl JniToolboxError for ConnectionError { - fn jclass(&self) -> String { // TODO pick class based on underlying type - "mp/code/exceptions/ConnectionRemoteException".to_string() - } -} - -impl JniToolboxError for RemoteError { - fn jclass(&self) -> String { // TODO pick class based on underlying type - "mp/code/exceptions/ConnectionRemoteException".to_string() - } -} +use jni_toolbox::jni; +use crate::{api::Config, client::Client, errors::{ConnectionError, RemoteError}, Workspace}; +/// Connect using the given credentials to the default server, and return a [Client] to interact with it. #[jni(package = "mp.code", class = "Client", ptr)] fn connect(config: Config) -> Result { - tokio().block_on(Client::connect(config)) -} - -fn asd(arg: String) -> Result, String> { - Ok(arg.split('/').map(|x| x.to_string()).collect()) + super::tokio().block_on(Client::connect(config)) } /// Gets the current [crate::api::User]. #[jni(package = "mp.code", class = "Client", ptr)] -fn get_user(client: Client) -> crate::api::User { +fn get_user(client: &mut Client) -> crate::api::User { client.user().clone() } /// Join a [Workspace] and return a pointer to it. #[jni(package = "mp.code", class = "Client", ptr)] -fn join_workspace(client: Client, workspace: String) -> Result { - tokio().block_on(client.join_workspace(workspace)) +fn join_workspace(client: &mut Client, workspace: String) -> Result { + super::tokio().block_on(client.join_workspace(workspace)) } +/// Create a workspace on server, if allowed to. #[jni(package = "mp.code", class = "Client")] -fn create_workspace(client: Client, workspace: String) -> Result<(), RemoteError> { - tokio().block_on(client.create_workspace(workspace)) +fn create_workspace(client: &mut Client, workspace: String) -> Result<(), RemoteError> { + super::tokio().block_on(client.create_workspace(workspace)) } /// Delete a workspace on server, if allowed to. #[jni(package = "mp.code", class = "Client")] -fn delete_workspace(client: Client, workspace: String) -> Result<(), RemoteError> { - tokio().block_on(client.delete_workspace(workspace)) +fn delete_workspace(client: &mut Client, workspace: String) -> Result<(), RemoteError> { + super::tokio().block_on(client.delete_workspace(workspace)) } /// Invite another user to an owned workspace. #[jni(package = "mp.code", class = "Client")] -fn invite_to_workspace(client: Client, workspace: String, user: String) -> Result<(), RemoteError> { - tokio().block_on(client.invite_to_workspace(workspace, user)) +fn invite_to_workspace(client: &mut Client, workspace: String, user: String) -> Result<(), RemoteError> { + super::tokio().block_on(client.invite_to_workspace(workspace, user)) } /// List available workspaces. #[jni(package = "mp.code", class = "Client", ptr)] -fn list_workspaces(client: Client, owned: bool, invited: bool) -> Result, RemoteError> { - tokio().block_on(client.list_workspaces(owned, invited)) +fn list_workspaces(client: &mut Client, owned: bool, invited: bool) -> Result, RemoteError> { + super::tokio().block_on(client.list_workspaces(owned, invited)) } /// List available workspaces. #[jni(package = "mp.code", class = "Client", ptr)] -fn active_workspaces(client: Client) -> Vec { +fn active_workspaces(client: &mut Client) -> Vec { client.active_workspaces() } /// Leave a [Workspace] and return whether or not the client was in such workspace. #[jni(package = "mp.code", class = "Client")] -fn leave_workspace(client: Client, workspace: String) -> bool { +fn leave_workspace(client: &mut Client, workspace: String) -> bool { client.leave_workspace(&workspace) } /// Get a [Workspace] by name and returns a pointer to it. #[jni(package = "mp.code", class = "Client", ptr)] -fn get_workspace(client: Client, workspace: String) -> Option { +fn get_workspace(client: &mut Client, workspace: String) -> Option { client.get_workspace(&workspace) } /// Refresh the client's session token. #[jni(package = "mp.code", class = "Client")] -fn refresh(client: Client) -> Result<(), RemoteError> { - tokio().block_on(client.refresh()) +fn refresh(client: &mut Client) -> Result<(), RemoteError> { + super::tokio().block_on(client.refresh()) } /// Called by the Java GC to drop a [Client]. -#[no_mangle] -pub extern "system" fn Java_mp_code_Client_free(_env: JNIEnv, _class: JClass, input: jlong) { +#[jni(package = "mp.code", class = "Client")] +fn free(input: jni::sys::jlong) { let _ = unsafe { Box::from_raw(input as *mut Client) }; } - -// TODO: this stays until we get rid of the arc then i'll have to find a better way -fn spawn_updater(workspace: Workspace) -> Workspace { - let w = workspace.clone(); - tokio().spawn(async move { - loop { - tokio::time::sleep(std::time::Duration::from_secs(60)).await; - w.fetch_buffers().await.unwrap(); - w.fetch_users().await.unwrap(); - } - }); - workspace -} diff --git a/src/ffi/java/cursor.rs b/src/ffi/java/cursor.rs index 73dc431..509f868 100644 --- a/src/ffi/java/cursor.rs +++ b/src/ffi/java/cursor.rs @@ -1,66 +1,31 @@ -use jni::{objects::{JClass, JObject}, sys::{jboolean, jlong, jobject}, JNIEnv}; -use crate::api::{Controller, Cursor}; +use jni::{objects::JObject, JNIEnv}; +use jni_toolbox::jni; +use crate::{api::{Controller, Cursor}, errors::ControllerError}; -use super::{handle_error, null_check, tokio, Deobjectify, JExceptable, JObjectify}; +use super::null_check; /// Try to fetch a [Cursor], or returns null if there's nothing. -#[no_mangle] -pub extern "system" fn Java_mp_code_CursorController_try_1recv( - mut env: JNIEnv, - _class: JClass, - self_ptr: jlong, -) -> jobject { - let controller = unsafe { Box::leak(Box::from_raw(self_ptr as *mut crate::cursor::Controller)) }; - tokio().block_on(controller.try_recv()) - .jexcept(&mut env) - .map(|change| change.jobjectify(&mut env).jexcept(&mut env).as_raw()) - .unwrap_or_else(std::ptr::null_mut) +#[jni(package = "mp.code", class = "CursorController", ptr)] +fn try_recv(controller: &mut crate::cursor::Controller) -> Result, ControllerError> { + super::tokio().block_on(controller.try_recv()) } /// Block until it receives a [Cursor]. -#[no_mangle] -pub extern "system" fn Java_mp_code_CursorController_recv( - mut env: JNIEnv, - _class: JClass, - self_ptr: jlong, -) -> jobject { - let controller = unsafe { Box::leak(Box::from_raw(self_ptr as *mut crate::cursor::Controller)) }; - tokio().block_on(controller.recv()) - .jexcept(&mut env) - .jobjectify(&mut env) - .jexcept(&mut env) - .as_raw() +#[jni(package = "mp.code", class = "CursorController", ptr)] +fn recv(controller: &mut crate::cursor::Controller) -> Result { + super::tokio().block_on(controller.recv()) } /// Receive from Java, converts and sends a [Cursor]. -#[no_mangle] -pub extern "system" fn Java_mp_code_CursorController_send<'local>( - mut env: JNIEnv<'local>, - _class: JClass<'local>, - self_ptr: jlong, - cursor: JObject<'local>, -) { - null_check!(env, cursor, {}); - let controller = unsafe { Box::leak(Box::from_raw(self_ptr as *mut crate::cursor::Controller)) }; - let cursor = Cursor::deobjectify(&mut env, cursor); - if let Ok(cursor) = cursor { - tokio().block_on(controller.send(cursor)).jexcept(&mut env) - } else { - handle_error!(&mut env, cursor, {}); - } +#[jni(package = "mp.code", class = "CursorController")] +fn send(controller: &mut crate::cursor::Controller, cursor: Cursor) -> Result<(), ControllerError> { + super::tokio().block_on(controller.send(cursor)) } -/// Registers a callback for cursor changes. -#[no_mangle] -pub extern "system" fn Java_mp_code_CursorController_callback<'local>( - mut env: JNIEnv, - _class: JClass<'local>, - self_ptr: jlong, - cb: JObject<'local>, -) { - null_check!(env, cb, {}); - let controller = unsafe { Box::leak(Box::from_raw(self_ptr as *mut crate::cursor::Controller)) }; - +/// Register a callback for cursor changes. +#[jni(package = "mp.code", class = "CursorController")] +fn callback<'local>(env: &mut JNIEnv<'local>, controller: &mut crate::cursor::Controller, cb: JObject<'local>) { + null_check!(env, cb, {}); 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!"); @@ -72,8 +37,8 @@ pub extern "system" fn Java_mp_code_CursorController_callback<'local>( let mut env = jvm.attach_current_thread_permanently() .expect("failed attaching to main JVM thread"); if let Err(e) = env.with_local_frame(5, |env| { - use crate::ffi::java::JObjectify; - let jcontroller = controller.jobjectify(env)?; + use jni_toolbox::IntoJavaObject; + let jcontroller = controller.into_java(env)?; if let Err(e) = env.call_method( &cb_ref, "accept", @@ -90,46 +55,26 @@ pub extern "system" fn Java_mp_code_CursorController_callback<'local>( }); } -/// Clears the callback for cursor changes. -#[no_mangle] -pub extern "system" fn Java_mp_code_CursorController_clear_1callback( - _env: JNIEnv, - _class: JClass, - self_ptr: jlong, -) { - unsafe { Box::leak(Box::from_raw(self_ptr as *mut crate::cursor::Controller)) } - .clear_callback(); +/// Clear the callback for cursor changes. +#[jni(package = "mp.code", class = "CursorController")] +fn clear_callback(controller: &mut crate::cursor::Controller) { + controller.clear_callback() } -/// Blocks until there is a new value available. -#[no_mangle] -pub extern "system" fn Java_mp_code_CursorController_poll( - mut env: JNIEnv, - _class: JClass, - self_ptr: jlong, -) { - let controller = unsafe { Box::leak(Box::from_raw(self_ptr as *mut crate::cursor::Controller)) }; - tokio().block_on(controller.poll()) - .jexcept(&mut env); +/// Block until there is a new value available. +#[jni(package = "mp.code", class = "CursorController")] +fn poll(controller: &mut crate::cursor::Controller) -> Result<(), ControllerError> { + super::tokio().block_on(controller.poll()) } -/// Stops the controller. -#[no_mangle] -pub extern "system" fn Java_mp_code_CursorController_stop( - _env: JNIEnv, - _class: JClass, - self_ptr: jlong, -) -> jboolean { - let controller = unsafe { Box::leak(Box::from_raw(self_ptr as *mut crate::cursor::Controller)) }; - controller.stop() as jboolean +/// Stop the controller. +#[jni(package = "mp.code", class = "CursorController")] +fn stop(controller: &mut crate::cursor::Controller) -> bool { + controller.stop() } /// Called by the Java GC to drop a [crate::cursor::Controller]. -#[no_mangle] -pub extern "system" fn Java_mp_code_CursorController_free( - _env: JNIEnv, - _class: JClass, - self_ptr: jlong, -) { - let _ = unsafe { Box::from_raw(self_ptr as *mut crate::cursor::Controller) }; +#[jni(package = "mp.code", class = "CursorController")] +fn free(input: jni::sys::jlong) { + let _ = unsafe { Box::from_raw(input as *mut crate::cursor::Controller) }; } diff --git a/src/ffi/java/ext.rs b/src/ffi/java/ext.rs index 5146641..e509b64 100644 --- a/src/ffi/java/ext.rs +++ b/src/ffi/java/ext.rs @@ -1,8 +1,5 @@ -use jni::{objects::{JClass, JString}, JNIEnv}; use jni_toolbox::jni; -use super::{JExceptable, null_check}; - /// Calculate the XXH3 hash for a given String. #[jni(package = "mp.code", class = "Extensions")] fn hash(content: String) -> i64 { @@ -23,6 +20,7 @@ fn drive(block: bool) { } /// Set up the tracing subscriber. +#[allow(non_snake_case)] #[jni(package = "mp.code", class = "Extensions")] fn setupTracing(path: Option, debug: bool) { super::setup_logger(debug, path); diff --git a/src/ffi/java/mod.rs b/src/ffi/java/mod.rs index d99c157..d20f422 100644 --- a/src/ffi/java/mod.rs +++ b/src/ffi/java/mod.rs @@ -62,26 +62,9 @@ pub(crate) fn setup_logger(debug: bool, path: Option) { } } -/// Utility macro that attempts to handle an error in a [Result]. -/// MUST be called within a $result.is_err() block or similar. Failure to do so is UB. -/// Will return early with a provided return value, or panic if it fails to throw a Java exception. -macro_rules! handle_error { - ($env: expr, $result: ident, $return: expr) => { - { - let err = unsafe { $result.unwrap_err_unchecked() }; - tracing::info!("Attempting to throw error {err:#?} as a Java exception..."); - if let Err(e) = err.jobjectify($env).map(|t| t.into()).and_then(|t: jni::objects::JThrowable| $env.throw(&t)) { - panic!("Failed to throw exception: {e}"); - } - return $return; - } - }; -} -pub(crate) use handle_error; - /// Performs a null check on the given variable and throws a NullPointerException on the Java side /// if it is null. Finally, it returns with the given default value. -macro_rules! null_check { +macro_rules! null_check { // TODO replace ($env: ident, $var: ident, $return: expr) => { if $var.is_null() { let mut message = stringify!($var).to_string(); @@ -94,76 +77,38 @@ macro_rules! null_check { } pub(crate) use null_check; - -/// A trait meant for our local result type to make converting it to Java easier. -/// jni-rs technically has [jni::errors::ToException], but this approach keeps it stream-like. -pub(crate) trait JExceptable<'local, T: Default> { - /// Unwrap it and throws an appropriate Java exception if it's an error. - /// Theoretically it returns the type's default value, but the exception makes the value ignored. - fn jexcept(self, env: &mut jni::JNIEnv<'local>) -> T; -} - -impl<'local, T: Default, E: JObjectify<'local> + std::fmt::Debug> JExceptable<'local, T> for Result { - fn jexcept(self, env: &mut jni::JNIEnv<'local>) -> T { - if let Ok(res) = self { - res - } else { - handle_error!(env, self, Default::default()); - } +impl jni_toolbox::JniToolboxError for crate::errors::ConnectionError { + fn jclass(&self) -> String { + match self { + crate::errors::ConnectionError::Transport(_) => "mp/code/exceptions/ConnectionTransportException", + crate::errors::ConnectionError::Remote(_) => "mp/code/exceptions/ConnectionRemoteException" + }.to_string() } } -/// Allows easy conversion for various types into Java objects. -/// This is similar to [TryInto], but for Java types. -pub(crate) trait JObjectify<'local> { - /// Attempt to convert the given object to a [jni::objects::JObject]. - fn jobjectify(self, env: &mut jni::JNIEnv<'local>) -> Result, jni::errors::Error>; +impl jni_toolbox::JniToolboxError for crate::errors::RemoteError { + fn jclass(&self) -> String { + "mp/code/exceptions/ConnectionRemoteException".to_string() + } } -macro_rules! jobjectify_error { - ($self: ident, $type: ty, $jclass: expr) => { - impl<'local> JObjectify<'local> for $type { - fn jobjectify($self, env: &mut jni::JNIEnv<'local>) -> Result, jni::errors::Error> { - let class = env.find_class($jclass)?; - let msg = env.new_string(format!("{:#?}", $self))?; - env.new_object(class, "(Ljava/lang/String;)V", &[jni::objects::JValueGen::Object(&msg)]) - } - } - }; -} - -jobjectify_error!(self, crate::errors::RemoteError, "mp/code/exceptions/ConnectionRemoteException"); -jobjectify_error!(self, jni::errors::Error, match self { - jni::errors::Error::NullPtr(_) => "java/lang/NullPointerException", - _ => "mp/code/exceptions/JNIException" -}); -jobjectify_error!(self, uuid::Error, "java/lang/IllegalArgumentException"); -jobjectify_error!(self, crate::errors::ConnectionError, match self { - crate::errors::ConnectionError::Transport(_) => "mp/code/exceptions/ConnectionTransportException", - crate::errors::ConnectionError::Remote(_) => "mp/code/exceptions/ConnectionRemoteException" -}); -jobjectify_error!(self, crate::errors::ControllerError, match self { - crate::errors::ControllerError::Stopped => "mp/code/exceptions/ControllerStoppedException", - crate::errors::ControllerError::Unfulfilled => "mp/code/exceptions/ControllerUnfulfilledException", -}); - - -impl<'local> JObjectify<'local> for uuid::Uuid { - fn jobjectify(self, env: &mut jni::JNIEnv<'local>) -> Result, jni::errors::Error> { - let class = env.find_class("java/util/UUID")?; - let (msb, lsb) = self.as_u64_pair(); - let msb = i64::from_ne_bytes(msb.to_ne_bytes()); - let lsb = i64::from_ne_bytes(lsb.to_ne_bytes()); - env.new_object(&class, "(JJ)V", &[jni::objects::JValueGen::Long(msb), jni::objects::JValueGen::Long(lsb)]) +impl jni_toolbox::JniToolboxError for crate::errors::ControllerError { + fn jclass(&self) -> String { + match self { + crate::errors::ControllerError::Stopped => "mp/code/exceptions/ControllerStoppedException", + crate::errors::ControllerError::Unfulfilled => "mp/code/exceptions/ControllerUnfulfilledException", + }.to_string() } } /// Generates a [JObjectify] implementation for a class that is just a holder for a pointer. -macro_rules! jobjectify_ptr_class { +macro_rules! into_java_ptr_class { ($type: ty, $jclass: literal) => { - impl<'local> JObjectify<'local> for $type { - fn jobjectify(self, env: &mut jni::JNIEnv<'local>) -> Result, jni::errors::Error> { - let class = env.find_class($jclass)?; + impl<'j> jni_toolbox::IntoJavaObject<'j> for $type { + type T = jni::objects::JObject<'j>; + const CLASS: &'static str = $jclass; + fn into_java(self, env: &mut jni::JNIEnv<'j>) -> Result { + let class = env.find_class(Self::CLASS)?; env.new_object( class, "(J)V", @@ -174,16 +119,18 @@ macro_rules! jobjectify_ptr_class { }; } -jobjectify_ptr_class!(crate::Client, "mp/code/Client"); -jobjectify_ptr_class!(crate::Workspace, "mp/code/Workspace"); -jobjectify_ptr_class!(crate::cursor::Controller, "mp/code/CursorController"); -jobjectify_ptr_class!(crate::buffer::Controller, "mp/code/BufferController"); +into_java_ptr_class!(crate::Client, "mp/code/Client"); +into_java_ptr_class!(crate::Workspace, "mp/code/Workspace"); +into_java_ptr_class!(crate::cursor::Controller, "mp/code/CursorController"); +into_java_ptr_class!(crate::buffer::Controller, "mp/code/BufferController"); -impl<'local> JObjectify<'local> for crate::api::User { - fn jobjectify(self, env: &mut jni::JNIEnv<'local>) -> Result, jni::errors::Error> { - let id_field = self.id.jobjectify(env)?; +impl<'j> jni_toolbox::IntoJavaObject<'j> for crate::api::User { + type T = jni::objects::JObject<'j>; + const CLASS: &'static str = "mp/code/data/User"; + fn into_java(self, env: &mut jni::JNIEnv<'j>) -> Result { + let id_field = self.id.into_java(env)?; let name_field = env.new_string(self.name)?; - let class = env.find_class("mp/code/data/User")?; + let class = env.find_class(Self::CLASS)?; env.new_object( &class, "(Ljava/util/UUID;Ljava/lang/String;)V", @@ -195,8 +142,10 @@ impl<'local> JObjectify<'local> for crate::api::User { } } -impl<'local> JObjectify<'local> for crate::api::Event { - fn jobjectify(self, env: &mut jni::JNIEnv<'local>) -> Result, jni::errors::Error> { +impl<'j> jni_toolbox::IntoJavaObject<'j> for crate::api::Event { + type T = jni::objects::JObject<'j>; + const CLASS: &'static str = "mp/code/Workspace$Event"; + fn into_java(self, env: &mut jni::JNIEnv<'j>) -> Result { let (ordinal, arg) = match self { crate::api::Event::UserJoin(arg) => (0, env.new_string(arg)?), crate::api::Event::UserLeave(arg) => (1, env.new_string(arg)?), @@ -212,7 +161,7 @@ impl<'local> JObjectify<'local> for crate::api::Event { )?.l()?.into(); let event_type = env.get_object_array_element(variants, ordinal)?; - let event_class = env.find_class("mp/code/Workspace$Event")?; + let event_class = env.find_class(Self::CLASS)?; env.new_object( event_class, "(Lmp/code/Workspace$Event$Type;Ljava/lang/String;)V", @@ -224,15 +173,17 @@ impl<'local> JObjectify<'local> for crate::api::Event { } } -impl<'local> JObjectify<'local> for crate::workspace::DetachResult { - fn jobjectify(self, env: &mut jni::JNIEnv<'local>) -> Result, jni::errors::Error> { +impl<'j> jni_toolbox::IntoJavaObject<'j> for crate::workspace::DetachResult { + type T = jni::objects::JObject<'j>; + const CLASS: &'static str = "mp/code/data/DetachResult"; + fn into_java(self, env: &mut jni::JNIEnv<'j>) -> Result { let ordinal = match self { crate::workspace::DetachResult::NotAttached => 0, crate::workspace::DetachResult::Detaching => 1, crate::workspace::DetachResult::AlreadyDetached => 2 }; - let class = env.find_class("mp/code/data/DetachResult")?; + let class = env.find_class(Self::CLASS)?; let variants: jni::objects::JObjectArray = env.call_method( class, "getEnumConstants", @@ -243,66 +194,79 @@ impl<'local> JObjectify<'local> for crate::workspace::DetachResult { } } -impl<'local> JObjectify<'local> for crate::api::TextChange { - fn jobjectify(self, env: &mut jni::JNIEnv<'local>) -> Result, jni::errors::Error> { +impl<'j> jni_toolbox::IntoJavaObject<'j> for crate::api::TextChange { + type T = jni::objects::JObject<'j>; + const CLASS: &'static str = "mp/code/data/TextChange"; + fn into_java(self, env: &mut jni::JNIEnv<'j>) -> Result { let content = env.new_string(self.content)?; - let hash = env.find_class("java/util/OptionalLong").and_then(|class| { - if let Some(h) = self.hash { - env.call_static_method(class, "of", "(J)Ljava/util/OptionalLong;", &[jni::objects::JValueGen::Long(h)]) - } else { - env.call_static_method(class, "empty", "()Ljava/util/OptionalLong;", &[]) + let hash_class = env.find_class("java/util/OptionalLong")?; + let hash = if let Some(h) = self.hash { + env.call_static_method(hash_class, "of", "(J)Ljava/util/OptionalLong;", &[jni::objects::JValueGen::Long(h)]) + } else { + env.call_static_method(hash_class, "empty", "()Ljava/util/OptionalLong;", &[]) + }?.l()?; + + let class = env.find_class(Self::CLASS)?; + env.new_object( + class, + "(JJLjava/lang/String;Ljava/util/OptionalLong;)V", + &[ + jni::objects::JValueGen::Long(self.start.into()), + jni::objects::JValueGen::Long(self.end.into()), + jni::objects::JValueGen::Object(&content), + jni::objects::JValueGen::Object(&hash) + ] + ) + } +} + +impl<'j> jni_toolbox::IntoJavaObject<'j> for crate::api::Cursor { + type T = jni::objects::JObject<'j>; + const CLASS: &'static str = "mp/code/data/Cursor"; + fn into_java(self, env: &mut jni::JNIEnv<'j>) -> Result { + let class = env.find_class("mp/code/data/Cursor")?; + let buffer = env.new_string(&self.buffer)?; + let user = if let Some(user) = self.user { + env.new_string(user)?.into() + } else { + jni::objects::JObject::null() + }; + + env.new_object( + class, + "(IIIILjava/lang/String;Ljava/lang/String;)V", + &[ + jni::objects::JValueGen::Int(self.start.0), + jni::objects::JValueGen::Int(self.start.1), + jni::objects::JValueGen::Int(self.end.0), + jni::objects::JValueGen::Int(self.end.1), + jni::objects::JValueGen::Object(&buffer), + jni::objects::JValueGen::Object(&user) + ] + ) + } +} + +macro_rules! from_java_ptr { + ($type: ty) => { + impl<'j> jni_toolbox::FromJava<'j> for &mut $type { + type T = jni::sys::jobject; + fn from_java(_env: &mut jni::JNIEnv<'j>, value: Self::T) -> Result { + Ok(unsafe { Box::leak(Box::from_raw(value as *mut $type)) }) } - }).and_then(|o| o.l())?; - env.find_class("mp/code/data/TextChange").and_then(|class| { - env.new_object( - class, - "(JJLjava/lang/String;Ljava/util/OptionalLong;)V", - &[ - jni::objects::JValueGen::Long(self.start.into()), - jni::objects::JValueGen::Long(self.end.into()), - jni::objects::JValueGen::Object(&content), - jni::objects::JValueGen::Object(&hash) - ] - ) - }) - } + } + }; } -impl<'local> JObjectify<'local> for crate::api::Cursor { - fn jobjectify(self, env: &mut jni::JNIEnv<'local>) -> Result, jni::errors::Error> { - env.find_class("mp/code/data/Cursor").and_then(|class| { - let buffer = env.new_string(&self.buffer)?; - let user = if let Some(user) = self.user { - env.new_string(user)?.into() - } else { - jni::objects::JObject::null() - }; +from_java_ptr!(crate::Client); +from_java_ptr!(crate::Workspace); +from_java_ptr!(crate::cursor::Controller); +from_java_ptr!(crate::buffer::Controller); - env.new_object( - class, - "(IIIILjava/lang/String;Ljava/lang/String;)V", - &[ - jni::objects::JValueGen::Int(self.start.0), - jni::objects::JValueGen::Int(self.start.1), - jni::objects::JValueGen::Int(self.end.0), - jni::objects::JValueGen::Int(self.end.1), - jni::objects::JValueGen::Object(&buffer), - jni::objects::JValueGen::Object(&user) - ] - ) - }) - } -} - -/// Allows easy conversion of Java types into their Rust counterparts. -pub(crate) trait Deobjectify<'local, T: Sized> { - /// Attempt to convert the given [jni::objects::JObject] into its Rust counterpart. - fn deobjectify(env: &mut jni::JNIEnv<'local>, jobject: jni::objects::JObject<'local>) -> Result; -} - -impl<'local> Deobjectify<'local, Self> for crate::api::Config { - fn deobjectify(env: &mut jni::JNIEnv<'local>, config: jni::objects::JObject<'local>) -> Result { +impl<'j> jni_toolbox::FromJava<'j> for crate::api::Config { + type T = jni::objects::JObject<'j>; + fn from_java(env: &mut jni::JNIEnv<'j>, config: Self::T) -> Result { let username = { let jfield = env.get_field(&config, "username", "Ljava/lang/String;")?.l()?; if jfield.is_null() { @@ -359,8 +323,9 @@ impl<'local> Deobjectify<'local, Self> for crate::api::Config { } } -impl<'local> Deobjectify<'local, Self> for crate::api::Cursor { - fn deobjectify(env: &mut jni::JNIEnv<'local>, cursor: jni::objects::JObject<'local>) -> Result { +impl<'j> jni_toolbox::FromJava<'j> for crate::api::Cursor { + type T = jni::objects::JObject<'j>; + fn from_java(env: &mut jni::JNIEnv<'j>, cursor: Self::T) -> Result { let start_row = env.get_field(&cursor, "startRow", "I")?.i()?; let start_col = env.get_field(&cursor, "startCol", "I")?.i()?; let end_row = env.get_field(&cursor, "endRow", "I")?.i()?; @@ -387,8 +352,9 @@ impl<'local> Deobjectify<'local, Self> for crate::api::Cursor { } } -impl<'local> Deobjectify<'local, Self> for crate::api::TextChange { - fn deobjectify(env: &mut jni::JNIEnv<'local>, change: jni::objects::JObject<'local>) -> Result { +impl<'j> jni_toolbox::FromJava<'j> for crate::api::TextChange { + type T = jni::objects::JObject<'j>; + fn from_java(env: &mut jni::JNIEnv<'j>, change: Self::T) -> Result { let start = env.get_field(&change, "start", "J")?.j()?.clamp(0, u32::MAX.into()) as u32; let end = env.get_field(&change, "end", "J")?.j()?.clamp(0, u32::MAX.into()) as u32; diff --git a/src/ffi/java/workspace.rs b/src/ffi/java/workspace.rs index d1a35e5..a03508a 100644 --- a/src/ffi/java/workspace.rs +++ b/src/ffi/java/workspace.rs @@ -1,240 +1,86 @@ -use jni::{objects::{JClass, JObject, JString}, sys::{jboolean, jlong, jobject, jobjectArray, jstring}, JNIEnv}; use jni_toolbox::jni; -use crate::Workspace; - -use super::{handle_error, null_check, JExceptable, JObjectify}; +use crate::{errors::{ConnectionError, ControllerError, RemoteError}, Workspace}; /// Get the workspace id. -#[no_mangle] -pub extern "system" fn Java_mp_code_Workspace_get_1workspace_1id<'local>( - mut env: JNIEnv<'local>, - _class: JClass<'local>, - self_ptr: jlong -) -> jstring { - let workspace = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Workspace)) }; - env.new_string(workspace.id()).jexcept(&mut env).as_raw() +#[jni(package = "mp.code", class = "Workspace", ptr)] +fn get_workspace_id(workspace: &mut Workspace) -> String { + workspace.id() } /// Get a cursor controller by name and returns a pointer to it. -#[no_mangle] -pub extern "system" fn Java_mp_code_Workspace_get_1cursor<'local>( - mut env: JNIEnv<'local>, - _class: JClass<'local>, - self_ptr: jlong -) -> jobject { - let workspace = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Workspace)) }; - workspace.cursor().jobjectify(&mut env).jexcept(&mut env).as_raw() +#[jni(package = "mp.code", class = "Workspace", ptr)] +fn get_cursor(workspace: &mut Workspace) -> crate::cursor::Controller { + workspace.cursor() } /// Get a buffer controller by name and returns a pointer to it. -#[no_mangle] -pub extern "system" fn Java_mp_code_Workspace_get_1buffer<'local>( - mut env: JNIEnv<'local>, - _class: JClass<'local>, - self_ptr: jlong, - path: JString<'local> -) -> jobject { - null_check!(env, path, std::ptr::null_mut()); - let workspace = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Workspace)) }; - let path = unsafe { env.get_string_unchecked(&path) } - .map(|path| path.to_string_lossy().to_string()) - .jexcept(&mut env); +#[jni(package = "mp.code", class = "Workspace", ptr)] +fn get_buffer(workspace: &mut Workspace, path: String) -> Option { workspace.buffer_by_name(&path) - .map(|buf| buf.jobjectify(&mut env).jexcept(&mut env)) - .unwrap_or_default() - .as_raw() } /// Get the filetree. #[jni(package = "mp.code", class = "Workspace", ptr)] -fn file_tree( - mut env: JNIEnv, - _class: JClass, - self_ptr: jlong, - filter: JString, - strict: jboolean -) -> Result { - let workspace = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Workspace)) }; - let filter: Option = if filter.is_null() { - None - } else { - Some(env.get_string(&filter)?.into()) - }; - - let file_tree = workspace.filetree(filter.as_deref(), strict != 0); - let class = env.find_class("java/lang/String")?; - let array = env.new_object_array(file_tree.len() as i32, class, JObject::null())?; - for (idx, path) in file_tree.iter().enumerate() { - let element = env.new_string(path)?; - env.set_object_array_element(&array, idx as i32, element)?; - } - - Ok(array.as_raw()) +fn get_file_tree(workspace: &mut Workspace, filter: Option, strict: bool) -> Vec { + workspace.filetree(filter.as_deref(), strict) } /// Gets a list of the active buffers. -#[no_mangle] -pub extern "system" fn Java_mp_code_Workspace_active_1buffers( - mut env: JNIEnv, - _class: JClass, - self_ptr: jlong -) -> jobjectArray { - let workspace = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Workspace)) }; - let active_buffer_list = workspace.buffer_list(); - env.find_class("java/lang/String") - .and_then(|class| env.new_object_array(active_buffer_list.len() as i32, class, JObject::null())) - .inspect(|arr| { - for (idx, path) in active_buffer_list.iter().enumerate() { - env.new_string(path) - .and_then(|path| env.set_object_array_element(arr, idx as i32, path)) - .jexcept(&mut env) - } - }).jexcept(&mut env).as_raw() +#[jni(package = "mp.code", class = "Workspace", ptr)] +fn active_buffers(workspace: &mut Workspace) -> Vec { + workspace.buffer_list() } /// Create a new buffer. -#[no_mangle] -pub extern "system" fn Java_mp_code_Workspace_create_1buffer<'local>( - mut env: JNIEnv, - _class: JClass<'local>, - self_ptr: jlong, - path: JString<'local> -) { - null_check!(env, path, {}); - let ws = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Workspace)) }; - let path = unsafe { env.get_string_unchecked(&path) } - .map(|path| path.to_string_lossy().to_string()) - .jexcept(&mut env); - super::tokio().block_on(ws.create(&path)) - .jexcept(&mut env); +#[jni(package = "mp.code", class = "Workspace")] +fn create_buffer(workspace: &mut Workspace, path: String) -> Result<(), RemoteError> { + super::tokio().block_on(workspace.create(&path)) } /// Attach to a buffer and return a pointer to its [crate::buffer::Controller]. -#[no_mangle] -pub extern "system" fn Java_mp_code_Workspace_attach_1to_1buffer<'local>( - mut env: JNIEnv, - _class: JClass<'local>, - self_ptr: jlong, - path: JString<'local> -) -> jobject { - null_check!(env, path, std::ptr::null_mut()); - let workspace = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Workspace)) }; - let path = unsafe { env.get_string_unchecked(&path) } - .map(|path| path.to_string_lossy().to_string()) - .jexcept(&mut env); +#[jni(package = "mp.code", class = "Workspace", ptr)] +fn attach_to_buffer(workspace: &mut Workspace, path: String) -> Result { super::tokio().block_on(workspace.attach(&path)) - .map(|buffer| buffer.jobjectify(&mut env).jexcept(&mut env)) - .jexcept(&mut env) - .as_raw() } /// Detach from a buffer. -#[no_mangle] -pub extern "system" fn Java_mp_code_Workspace_detach_1from_1buffer<'local>( - mut env: JNIEnv, - _class: JClass<'local>, - self_ptr: jlong, - path: JString<'local> -) -> jobject { - null_check!(env, path, std::ptr::null_mut()); - let workspace = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Workspace)) }; - let path = unsafe { env.get_string_unchecked(&path) } - .map(|path| path.to_string_lossy().to_string()) - .jexcept(&mut env); +#[jni(package = "mp.code", class = "Workspace", ptr)] +fn detach_from_buffer(workspace: &mut Workspace, path: String) -> crate::workspace::DetachResult { workspace.detach(&path) - .jobjectify(&mut env) - .jexcept(&mut env) - .as_raw() } /// Update the local buffer list. -#[no_mangle] -pub extern "system" fn Java_mp_code_Workspace_fetch_1buffers( - mut env: JNIEnv, - _class: JClass, - self_ptr: jlong, -) { - let workspace = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Workspace)) }; - super::tokio().block_on(workspace.fetch_buffers()).jexcept(&mut env); +#[jni(package = "mp.code", class = "Workspace")] +fn fetch_buffers(workspace: &mut Workspace) -> Result<(), RemoteError> { + super::tokio().block_on(workspace.fetch_buffers()) } /// Update the local user list. -#[no_mangle] -pub extern "system" fn Java_mp_code_Workspace_fetch_1users( - mut env: JNIEnv, - _class: JClass, - self_ptr: jlong, -) { - let workspace = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Workspace)) }; - super::tokio().block_on(workspace.fetch_users()).jexcept(&mut env); +#[jni(package = "mp.code", class = "Workspace")] +fn fetch_users(workspace: &mut Workspace) -> Result<(), RemoteError> { + super::tokio().block_on(workspace.fetch_users()) } /// List users attached to a buffer. -#[no_mangle] -pub extern "system" fn Java_mp_code_Workspace_list_1buffer_1users<'local>( - mut env: JNIEnv, - _class: JClass<'local>, - self_ptr: jlong, - path: JString<'local>, -) -> jobjectArray { - null_check!(env, path, std::ptr::null_mut()); - let workspace = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Workspace)) }; - let buffer = unsafe { env.get_string_unchecked(&path) } - .map(|buffer| buffer.to_string_lossy().to_string()) - .jexcept(&mut env); - let users = super::tokio().block_on(workspace.list_buffer_users(&buffer)) - .jexcept(&mut env); - - if env.exception_check().unwrap_or(false) { // prevent illegal state - return std::ptr::null_mut(); - } - - env.find_class("java/util/UUID") - .and_then(|class| env.new_object_array(users.len() as i32, &class, JObject::null())) - .inspect(|arr| { - for (idx, user) in users.iter().enumerate() { - user.id.jobjectify(&mut env) - .and_then(|id| env.set_object_array_element(arr, idx as i32, id)) - .jexcept(&mut env); - } - }).jexcept(&mut env).as_raw() +#[jni(package = "mp.code", class = "Workspace", ptr)] +fn list_buffer_users(workspace: &mut Workspace, path: String) -> Result, RemoteError> { + super::tokio().block_on(workspace.list_buffer_users(&path)) } /// Delete a buffer. -#[no_mangle] -pub extern "system" fn Java_mp_code_Workspace_delete_1buffer<'local>( - mut env: JNIEnv, - _class: JClass<'local>, - self_ptr: jlong, - path: JString<'local>, -) { - null_check!(env, path, {}); - let workspace = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Workspace)) }; - let buffer = unsafe { env.get_string_unchecked(&path) } - .map(|buffer| buffer.to_string_lossy().to_string()) - .jexcept(&mut env); - super::tokio().block_on(workspace.delete(&buffer)) - .jexcept(&mut env); +#[jni(package = "mp.code", class = "Workspace")] +fn delete_buffer(workspace: &mut Workspace, path: String) -> Result<(), RemoteError> { + super::tokio().block_on(workspace.delete(&path)) } /// Receive a workspace event if present. -#[no_mangle] -pub extern "system" fn Java_mp_code_Workspace_event( - mut env: JNIEnv, - _class: JClass, - self_ptr: jlong -) -> jobject { - let workspace = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Workspace)) }; - let event = super::tokio().block_on(workspace.event()); - if let Ok(event) = event { - event.jobjectify(&mut env).jexcept(&mut env).as_raw() - } else { - handle_error!(&mut env, event, std::ptr::null_mut()) - } +#[jni(package = "mp.code", class = "Workspace", ptr)] +fn event(workspace: &mut Workspace) -> Result { + super::tokio().block_on(workspace.event()) } /// Called by the Java GC to drop a [Workspace]. -#[no_mangle] -pub extern "system" fn Java_mp_code_Workspace_free(_env: JNIEnv, _class: JClass, input: jlong) { - let _ = unsafe { Box::from_raw(input as *mut Workspace) }; +#[jni(package = "mp.code", class = "Workspace")] +fn free(input: jni::sys::jlong) { + let _ = unsafe { Box::from_raw(input as *mut crate::Workspace) }; }