diff --git a/Cargo.lock b/Cargo.lock index 07ca3de..f7af2d8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -238,6 +238,7 @@ dependencies = [ "dashmap", "diamond-types", "jni", + "jni-toolbox", "lazy_static", "mlua-codemp-patch", "napi", @@ -746,6 +747,28 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" +[[package]] +name = "jni-toolbox" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eeae2881d819e208fcfceea81eb5a8ca6c131c6fb1605dfe2f3a31dea061ec7c" +dependencies = [ + "jni", + "jni-toolbox-macro", + "uuid", +] + +[[package]] +name = "jni-toolbox-macro" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e480850db18f0cc95120e7bf86af772c31b3c0f0dd3d3600682d8bd8399f4ae" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + [[package]] name = "js-sys" version = "0.3.70" diff --git a/Cargo.toml b/Cargo.toml index 3da16be..fb56160 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,6 +41,7 @@ tracing-subscriber = { version = "0.3", optional = true } # glue (java) lazy_static = { version = "1.5", optional = true } jni = { version = "0.21", features = ["invocation"], optional = true } +jni-toolbox = { version = "0.2.0", optional = true, features = ["uuid"] } # glue (lua) mlua-codemp-patch = { version = "0.10.0-beta.2", features = ["module", "send", "serialize"], optional = true } @@ -69,7 +70,7 @@ async-trait = ["dep:async-trait"] serialize = ["dep:serde", "uuid/serde"] # ffi rust = [] # used for ci matrix -java = ["lazy_static", "jni", "tracing-subscriber"] +java = ["lazy_static", "jni", "tracing-subscriber", "jni-toolbox"] js = ["napi-build", "tracing-subscriber", "napi", "napi-derive"] py-noabi = ["pyo3", "tracing-subscriber", "pyo3-build-config"] py = ["py-noabi", "pyo3/abi3-py38"] diff --git a/src/ffi/java/buffer.rs b/src/ffi/java/buffer.rs index 7731617..0857118 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")] +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")] +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")] +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")] +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_object(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 543458d..e420171 100644 --- a/src/ffi/java/client.rs +++ b/src/ffi/java/client.rs @@ -1,231 +1,74 @@ -use jni::{objects::{JClass, JObject, JString}, sys::{jboolean, jlong, jobject, jobjectArray}, JNIEnv}; -use crate::{api::Config, client::Client, ffi::java::{handle_error, null_check}, Workspace}; - -use super::{Deobjectify, JExceptable, JObjectify, tokio}; +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. -#[no_mangle] -pub extern "system" fn Java_mp_code_Client_connect<'local>( - mut env: JNIEnv<'local>, - _class: JClass<'local>, - config: JObject<'local> -) -> jobject { - null_check!(env, config, std::ptr::null_mut()); - let config = Config::deobjectify(&mut env, config); - if config.is_err() { - handle_error!(&mut env, config, std::ptr::null_mut()); - } - - let client = tokio().block_on(Client::connect(config.unwrap())); - if let Ok(client) = client { - client.jobjectify(&mut env).jexcept(&mut env).as_raw() - } else { - handle_error!(&mut env, client, std::ptr::null_mut()); - } +#[jni(package = "mp.code", class = "Client", ptr)] +fn connect(config: Config) -> Result { + super::tokio().block_on(Client::connect(config)) } /// Gets the current [crate::api::User]. -#[no_mangle] -pub extern "system" fn Java_mp_code_Client_get_1user( - mut env: JNIEnv, - _class: JClass, - self_ptr: jlong -) -> jobject { - let client = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Client)) }; +#[jni(package = "mp.code", class = "Client", ptr)] +fn get_user(client: &mut Client) -> crate::api::User { client.user().clone() - .jobjectify(&mut env) - .jexcept(&mut env) - .as_raw() } /// Join a [Workspace] and return a pointer to it. -#[no_mangle] -pub extern "system" fn Java_mp_code_Client_join_1workspace<'local>( - mut env: JNIEnv<'local>, - _class: JClass<'local>, - self_ptr: jlong, - workspace_id: JString<'local> -) -> jobject { - null_check!(env, workspace_id, std::ptr::null_mut()); - let client = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Client)) }; - let workspace_id = unsafe { env.get_string_unchecked(&workspace_id) } - .map(|wid| wid.to_string_lossy().to_string()) - .jexcept(&mut env); - let workspace = tokio().block_on(client.join_workspace(workspace_id)) - .map(|workspace| spawn_updater(workspace.clone())); - if let Ok(workspace) = workspace { - workspace.jobjectify(&mut env).jexcept(&mut env).as_raw() - } else { - handle_error!(&mut env, workspace, std::ptr::null_mut()) - } +#[jni(package = "mp.code", class = "Client")] +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. -#[no_mangle] -pub extern "system" fn Java_mp_code_Client_create_1workspace<'local>( - mut env: JNIEnv<'local>, - _class: JClass<'local>, - self_ptr: jlong, - workspace_id: JString<'local> -) { - null_check!(env, workspace_id, {}); - let client = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Client)) }; - let workspace_id = unsafe { env.get_string_unchecked(&workspace_id) } - .map(|wid| wid.to_string_lossy().to_string()) - .jexcept(&mut env); - tokio() - .block_on(client.create_workspace(workspace_id)) - .jexcept(&mut env); +#[jni(package = "mp.code", class = "Client")] +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. -#[no_mangle] -pub extern "system" fn Java_mp_code_Client_delete_1workspace<'local>( - mut env: JNIEnv<'local>, - _class: JClass<'local>, - self_ptr: jlong, - workspace_id: JString<'local> -) { - null_check!(env, workspace_id, {}); - let client = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Client)) }; - let workspace_id = unsafe { env.get_string_unchecked(&workspace_id) } - .map(|wid| wid.to_string_lossy().to_string()) - .jexcept(&mut env); - tokio() - .block_on(client.delete_workspace(workspace_id)) - .jexcept(&mut env); +#[jni(package = "mp.code", class = "Client")] +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. -#[no_mangle] -pub extern "system" fn Java_mp_code_Client_invite_1to_1workspace<'local>( - mut env: JNIEnv<'local>, - _class: JClass<'local>, - self_ptr: jlong, - workspace_id: JString<'local>, - user: JString<'local> -) { - null_check!(env, workspace_id, {}); - null_check!(env, user, {}); - let client = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Client)) }; - let workspace_id = unsafe { env.get_string_unchecked(&workspace_id) } - .map(|wid| wid.to_string_lossy().to_string()) - .jexcept(&mut env); - let user_name = unsafe { env.get_string_unchecked(&user) } - .map(|wid| wid.to_string_lossy().to_string()) - .jexcept(&mut env); - tokio() - .block_on(client.invite_to_workspace(workspace_id, user_name)) - .jexcept(&mut env); +#[jni(package = "mp.code", class = "Client")] +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. -#[no_mangle] -pub extern "system" fn Java_mp_code_Client_list_1workspaces<'local>( - mut env: JNIEnv<'local>, - _class: JClass<'local>, - self_ptr: jlong, - owned: jboolean, - invited: jboolean -) -> jobjectArray { - let client = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Client)) }; - let list = tokio() - .block_on(client.list_workspaces(owned != 0, invited != 0)) - .jexcept(&mut env); - env.find_class("java/lang/String") - .and_then(|class| env.new_object_array(list.len() as i32, class, JObject::null())) - .inspect(|arr| { - for (idx, path) in 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 = "Client")] +fn list_workspaces(client: &mut Client, owned: bool, invited: bool) -> Result, RemoteError> { + super::tokio().block_on(client.list_workspaces(owned, invited)) } /// List available workspaces. -#[no_mangle] -pub extern "system" fn Java_mp_code_Client_active_1workspaces<'local>( - mut env: JNIEnv<'local>, - _class: JClass<'local>, - self_ptr: jlong -) -> jobjectArray { - let client = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Client)) }; - let list = client.active_workspaces(); - env.find_class("java/lang/String") - .and_then(|class| env.new_object_array(list.len() as i32, class, JObject::null())) - .inspect(|arr| { - for (idx, path) in 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() -} - -// 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 +#[jni(package = "mp.code", class = "Client")] +fn active_workspaces(client: &mut Client) -> Vec { + client.active_workspaces() } /// Leave a [Workspace] and return whether or not the client was in such workspace. -#[no_mangle] -pub extern "system" fn Java_mp_code_Client_leave_1workspace<'local>( - mut env: JNIEnv<'local>, - _class: JClass<'local>, - self_ptr: jlong, - workspace_id: JString<'local> -) -> jboolean { - null_check!(env, workspace_id, false as jboolean); - let client = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Client)) }; - unsafe { env.get_string_unchecked(&workspace_id) } - .map(|wid| wid.to_string_lossy().to_string()) - .map(|wid| client.leave_workspace(&wid) as jboolean) - .jexcept(&mut env) +#[jni(package = "mp.code", class = "Client")] +fn leave_workspace(client: &mut Client, workspace: String) -> bool { + client.leave_workspace(&workspace) } /// Get a [Workspace] by name and returns a pointer to it. -#[no_mangle] -pub extern "system" fn Java_mp_code_Client_get_1workspace<'local>( - mut env: JNIEnv<'local>, - _class: JClass<'local>, - self_ptr: jlong, - workspace_id: JString<'local> -) -> jobject { - null_check!(env, workspace_id, std::ptr::null_mut()); - let client = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Client)) }; - let workspace_id = unsafe { env.get_string_unchecked(&workspace_id) } - .map(|wid| wid.to_string_lossy().to_string()) - .jexcept(&mut env); - if let Some(workspace) = client.get_workspace(&workspace_id) { - workspace.jobjectify(&mut env).jexcept(&mut env).as_raw() - } else { - std::ptr::null_mut() - } +#[jni(package = "mp.code", class = "Client")] +fn get_workspace(client: &mut Client, workspace: String) -> Option { + client.get_workspace(&workspace) } /// Refresh the client's session token. -#[no_mangle] -pub extern "system" fn Java_mp_code_Client_refresh<'local>( - mut env: JNIEnv<'local>, - _class: JClass<'local>, - self_ptr: jlong, -) { - let client = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Client)) }; - tokio().block_on(client.refresh()) - .jexcept(&mut env); +#[jni(package = "mp.code", class = "Client")] +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) }; } diff --git a/src/ffi/java/cursor.rs b/src/ffi/java/cursor.rs index 73dc431..359ea2b 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")] +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")] +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_object(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 c9403e4..e509b64 100644 --- a/src/ffi/java/ext.rs +++ b/src/ffi/java/ext.rs @@ -1,30 +1,16 @@ -use jni::{objects::{JClass, JString}, sys::{jboolean, jlong}, JNIEnv}; - -use super::{JExceptable, null_check}; +use jni_toolbox::jni; /// Calculate the XXH3 hash for a given String. -#[no_mangle] -pub extern "system" fn Java_mp_code_Extensions_hash<'local>( - mut env: JNIEnv, - _class: JClass<'local>, - content: JString<'local>, -) -> jlong { - null_check!(env, content, 0 as jlong); - let content: String = env.get_string(&content) - .map(|s| s.into()) - .jexcept(&mut env); +#[jni(package = "mp.code", class = "Extensions")] +fn hash(content: String) -> i64 { let hash = crate::ext::hash(content.as_bytes()); i64::from_ne_bytes(hash.to_ne_bytes()) } /// Tells the [tokio] runtime how to drive the event loop. -#[no_mangle] -pub extern "system" fn Java_mp_code_Extensions_drive( - _env: JNIEnv, - _class: JClass, - block: jboolean -) { - if block != 0 { +#[jni(package = "mp.code", class = "Extensions")] +fn drive(block: bool) { + if block { super::tokio().block_on(std::future::pending::<()>()); } else { std::thread::spawn(|| { @@ -34,19 +20,8 @@ pub extern "system" fn Java_mp_code_Extensions_drive( } /// Set up the tracing subscriber. -#[no_mangle] #[allow(non_snake_case)] -pub extern "system" fn Java_mp_code_Extensions_setupTracing<'local>( - mut env: JNIEnv, - _class: JClass<'local>, - path: JString<'local>, - debug: jboolean -) { - super::setup_logger( - debug != 0, - Some(path) - .filter(|p| !p.is_null()) - .map(|p| env.get_string(&p).map(|s| s.into()) - .jexcept(&mut env)) - ); +#[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..6e0515a 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,37 @@ 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 { + const CLASS: &'static str = $jclass; + fn into_java_object(self, env: &mut jni::JNIEnv<'j>) -> Result, jni::errors::Error> { + let class = env.find_class(Self::CLASS)?; env.new_object( class, "(J)V", @@ -174,16 +118,17 @@ 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 { + const CLASS: &'static str = "mp/code/data/User"; + fn into_java_object(self, env: &mut jni::JNIEnv<'j>) -> Result, jni::errors::Error> { + let id_field = self.id.into_java_object(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 +140,9 @@ 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 { + const CLASS: &'static str = "mp/code/Workspace$Event"; + fn into_java_object(self, env: &mut jni::JNIEnv<'j>) -> Result, jni::errors::Error> { 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 +158,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 +170,16 @@ 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 { + const CLASS: &'static str = "mp/code/data/DetachResult"; + fn into_java_object(self, env: &mut jni::JNIEnv<'j>) -> Result, jni::errors::Error> { 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 +190,77 @@ 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 { + const CLASS: &'static str = "mp/code/data/TextChange"; + fn into_java_object(self, env: &mut jni::JNIEnv<'j>) -> Result, jni::errors::Error> { 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 { + const CLASS: &'static str = "mp/code/data/Cursor"; + fn into_java_object(self, env: &mut jni::JNIEnv<'j>) -> Result, jni::errors::Error> { + 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 From = jni::sys::jobject; + fn from_java(_env: &mut jni::JNIEnv<'j>, value: Self::From) -> 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 From = jni::objects::JObject<'j>; + fn from_java(env: &mut jni::JNIEnv<'j>, config: Self::From) -> Result { let username = { let jfield = env.get_field(&config, "username", "Ljava/lang/String;")?.l()?; if jfield.is_null() { @@ -359,8 +317,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 From = jni::objects::JObject<'j>; + fn from_java(env: &mut jni::JNIEnv<'j>, cursor: Self::From) -> 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 +346,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 From = jni::objects::JObject<'j>; + fn from_java(env: &mut jni::JNIEnv<'j>, change: Self::From) -> 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 27a9702..06d8df4 100644 --- a/src/ffi/java/workspace.rs +++ b/src/ffi/java/workspace.rs @@ -1,244 +1,86 @@ -use jni::{objects::{JClass, JObject, JString}, sys::{jboolean, jlong, jobject, jobjectArray, jstring}, JNIEnv}; -use crate::Workspace; - -use super::{handle_error, null_check, JExceptable, JObjectify}; +use jni_toolbox::jni; +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")] +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")] +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")] +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. -#[no_mangle] -pub extern "system" fn Java_mp_code_Workspace_get_1file_1tree( - mut env: JNIEnv, - _class: JClass, - self_ptr: jlong, - filter: JString, - strict: jboolean -) -> jobjectArray { - 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) - .map(|s| s.into()) - .jexcept(&mut env) - ) - }; - - let file_tree = workspace.filetree(filter.as_deref(), strict != 0); - env.find_class("java/lang/String") - .and_then(|class| env.new_object_array(file_tree.len() as i32, class, JObject::null())) - .inspect(|arr| { - for (idx, path) in file_tree.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")] +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")] +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")] +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")] +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")] +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")] +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) }; }