From 6bf541028d1c917267fdfee594d036cc37ae2452 Mon Sep 17 00:00:00 2001 From: zaaarf Date: Sat, 10 Aug 2024 02:45:20 +0200 Subject: [PATCH] feat(java): replace all expect/unwrap with throwing JNIException --- .../src/mp/code/exceptions/JNIException.java | 12 ++ src/ffi/java/buffer.rs | 52 +++--- src/ffi/java/client.rs | 58 +++--- src/ffi/java/cursor.rs | 83 +++++---- src/ffi/java/mod.rs | 46 ++++- src/ffi/java/util.rs | 23 --- src/ffi/java/workspace.rs | 169 +++++++++--------- 7 files changed, 255 insertions(+), 188 deletions(-) create mode 100644 dist/java/src/mp/code/exceptions/JNIException.java delete mode 100644 src/ffi/java/util.rs diff --git a/dist/java/src/mp/code/exceptions/JNIException.java b/dist/java/src/mp/code/exceptions/JNIException.java new file mode 100644 index 0000000..37f3966 --- /dev/null +++ b/dist/java/src/mp/code/exceptions/JNIException.java @@ -0,0 +1,12 @@ +package mp.code.exceptions; + +/** + * Thrown when an error happened in some jni-rs method that would've otherwise crashed + * the program. This way, the eventual crash can happen on the Java side. + * Only catch this if you are aware of the implications. + */ +public class JNIException extends RuntimeException { + public JNIException(String message) { + super(message); + } +} diff --git a/src/ffi/java/buffer.rs b/src/ffi/java/buffer.rs index ee5b0a8..a251d02 100644 --- a/src/ffi/java/buffer.rs +++ b/src/ffi/java/buffer.rs @@ -2,33 +2,33 @@ use jni::{objects::{JClass, JObject, JValueGen}, sys::{jlong, jobject, jstring}, use crate::api::Controller; -use super::{util::JExceptable, RT}; +use super::{JExceptable, RT}; /// Gets the name of the buffer. #[no_mangle] pub extern "system" fn Java_mp_code_BufferController_get_1name( - env: JNIEnv, + 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.name(); env.new_string(content) - .expect("could not create jstring") + .jexcept(&mut env) .as_raw() } /// Gets the contents of the buffers. #[no_mangle] pub extern "system" fn Java_mp_code_BufferController_get_1content( - env: JNIEnv, + 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.content(); env.new_string(content) - .expect("could not create jstring") + .jexcept(&mut env) .as_raw() } @@ -59,20 +59,23 @@ pub extern "system" fn Java_mp_code_BufferController_recv( /// Utility method to convert a [crate::api::TextChange] to its Java equivalent. fn recv_jni(env: &mut JNIEnv, change: Option) -> jobject { match change { - None => JObject::null().as_raw(), + None => JObject::default(), Some(event) => { - let class = env.find_class("mp/code/data/TextChange").expect("Couldn't find class!"); - env.new_object( - class, - "(JJLjava/lang/String;)V", - &[ - JValueGen::Long(jlong::from(event.start)), - JValueGen::Long(jlong::from(event.end)), - JValueGen::Object(&env.new_string(event.content).expect("Failed to create String!")), - ] - ).expect("failed creating object").into_raw() + let content = env.new_string(event.content).jexcept(env); + env.find_class("mp/code/data/TextChange") + .and_then(|class| { + env.new_object( + class, + "(JJLjava/lang/String;)V", + &[ + JValueGen::Long(jlong::from(event.start)), + JValueGen::Long(jlong::from(event.end)), + JValueGen::Object(&content), + ] + ) + }).jexcept(env) } - } + }.as_raw() } /// Receives from Java, converts and sends a [crate::api::TextChange]. @@ -83,14 +86,15 @@ pub extern "system" fn Java_mp_code_BufferController_send<'local>( self_ptr: jlong, input: JObject<'local> ) { - let start = env.get_field(&input, "start", "J").expect("could not get field").j().expect("field was not of expected type"); - let end = env.get_field(&input, "end", "J").expect("could not get field").j().expect("field was not of expected type"); + let start = env.get_field(&input, "start", "J").and_then(|s| s.j()).jexcept(&mut env); + let end = env.get_field(&input, "end", "J").and_then(|e| e.j()).jexcept(&mut env); let content = env.get_field(&input, "content", "Ljava/lang/String;") - .expect("could not get field") - .l() - .expect("field was not of expected type") - .into(); - let content = env.get_string(&content).expect("Failed to get String!").into(); + .and_then(|c| c.l()) + .map(|c| c.into()) + .jexcept(&mut env); + let content = unsafe { env.get_string_unchecked(&content) } + .map(|c| c.to_string_lossy().to_string()) + .jexcept(&mut env); let controller = unsafe { Box::leak(Box::from_raw(self_ptr as *mut crate::buffer::Controller)) }; controller.send(crate::api::TextChange { diff --git a/src/ffi/java/client.rs b/src/ffi/java/client.rs index 25dc3f5..926d811 100644 --- a/src/ffi/java/client.rs +++ b/src/ffi/java/client.rs @@ -1,7 +1,7 @@ -use jni::{objects::{JClass, JObject, JString, JValueGen}, sys::{jboolean, jlong, jobject}, JNIEnv}; +use jni::{objects::{JClass, JString, JValueGen}, sys::{jboolean, jlong, jobject}, JNIEnv}; use crate::{client::Client, Workspace}; -use super::{util::JExceptable, RT}; +use super::{JExceptable, RT}; /// Connects to a given URL and returns a [Client] to interact with that server. #[no_mangle] @@ -12,15 +12,21 @@ pub extern "system" fn Java_mp_code_Client_connect<'local>( user: JString<'local>, pwd: JString<'local> ) -> jobject { - let url: String = env.get_string(&url).expect("Couldn't get java string!").into(); - let user: String = env.get_string(&user).expect("Couldn't get java string!").into(); - let pwd: String = env.get_string(&pwd).expect("Couldn't get java string!").into(); + let url: String = env.get_string(&url) + .map(|s| s.into()) + .jexcept(&mut env); + let user: String = env.get_string(&user) + .map(|s| s.into()) + .jexcept(&mut env); + let pwd: String = env.get_string(&pwd) + .map(|s| s.into()) + .jexcept(&mut env); RT.block_on(crate::Client::new(&url, &user, &pwd)) .map(|client| Box::into_raw(Box::new(client)) as jlong) .map(|ptr| { - let class = env.find_class("mp/code/Client").expect("Failed to find class"); - env.new_object(class, "(J)V", &[JValueGen::Long(ptr)]) - .expect("Failed to initialise object") + env.find_class("mp/code/Client") + .and_then(|class| env.new_object(class, "(J)V", &[JValueGen::Long(ptr)])) + .jexcept(&mut env) }).jexcept(&mut env).as_raw() } @@ -33,14 +39,16 @@ pub extern "system" fn Java_mp_code_Client_join_1workspace<'local>( input: JString<'local> ) -> jobject { let client = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Client)) }; - let workspace_id = unsafe { env.get_string_unchecked(&input).expect("Couldn't get java string!") }; - RT.block_on(client.join_workspace(workspace_id.to_str().expect("Not UTF-8"))) + let workspace_id = unsafe { env.get_string_unchecked(&input) } + .map(|wid| wid.to_string_lossy().to_string()) + .jexcept(&mut env); + RT.block_on(client.join_workspace(workspace_id)) .map(|workspace| spawn_updater(workspace.clone())) .map(|workspace| Box::into_raw(Box::new(workspace)) as jlong) .map(|ptr| { - let class = env.find_class("mp/code/Workspace").expect("Failed to find class"); - env.new_object(class, "(J)V", &[JValueGen::Long(ptr)]) - .expect("Failed to initialise object") + env.find_class("mp/code/Workspace") + .and_then(|class| env.new_object(class, "(J)V", &[JValueGen::Long(ptr)])) + .jexcept(&mut env) }).jexcept(&mut env).as_raw() } @@ -60,14 +68,16 @@ fn spawn_updater(workspace: Workspace) -> Workspace { /// Leaves a [Workspace] and returns whether or not the client was in such workspace. #[no_mangle] pub extern "system" fn Java_mp_code_Client_leave_1workspace<'local>( - env: JNIEnv<'local>, + mut env: JNIEnv<'local>, _class: JClass<'local>, self_ptr: jlong, input: JString<'local> ) -> jboolean { let client = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Client)) }; - let workspace_id = unsafe { env.get_string_unchecked(&input).expect("Couldn't get java string!") }; - client.leave_workspace(workspace_id.to_str().expect("Not UTF-8")) as jboolean + unsafe { env.get_string_unchecked(&input) } + .map(|wid| wid.to_string_lossy().to_string()) + .map(|wid| client.leave_workspace(&wid) as jboolean) + .jexcept(&mut env) } /// Gets a [Workspace] by name and returns a pointer to it. #[no_mangle] @@ -78,14 +88,16 @@ pub extern "system" fn Java_mp_code_Client_get_1workspace<'local>( input: JString<'local> ) -> jobject { let client = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Client)) }; - let workspace_id = unsafe { env.get_string_unchecked(&input).expect("Couldn't get java string!") }; - client.get_workspace(workspace_id.to_str().expect("Not UTF-8")) + let workspace_id = unsafe { env.get_string_unchecked(&input) } + .map(|wid| wid.to_string_lossy().to_string()) + .jexcept(&mut env); + client.get_workspace(&workspace_id) .map(|workspace| Box::into_raw(Box::new(workspace)) as jlong) .map(|ptr| { - let class = env.find_class("mp/code/Workspace").expect("Failed to find class"); - env.new_object(class, "(J)V", &[JValueGen::Long(ptr)]) - .expect("Failed to initialise object") - }).unwrap_or(JObject::null()).as_raw() + env.find_class("mp/code/Workspace") + .and_then(|class| env.new_object(class, "(J)V", &[JValueGen::Long(ptr)])) + .jexcept(&mut env) + }).unwrap_or_default().as_raw() } /// Sets up the tracing subscriber. @@ -99,7 +111,7 @@ pub extern "system" fn Java_mp_code_Client_setup_1tracing<'local>( true, Some(path) .filter(|p| p.is_null()) - .map(|p| env.get_string(&p).expect("couldn't get java string").into()) + .map(|p| env.get_string(&p).map(|s| s.into()).jexcept(&mut env)) ); } diff --git a/src/ffi/java/cursor.rs b/src/ffi/java/cursor.rs index 91217c4..679de98 100644 --- a/src/ffi/java/cursor.rs +++ b/src/ffi/java/cursor.rs @@ -1,7 +1,7 @@ use jni::{objects::{JClass, JObject, JString, JValueGen}, sys::{jlong, jobject}, JNIEnv}; -use crate::{api::Controller, ffi::java::util::JExceptable}; +use crate::api::Controller; -use super::RT; +use super::{JExceptable, RT}; /// Tries to fetch a [crate::api::Cursor], or returns null if there's nothing. #[no_mangle] @@ -30,23 +30,30 @@ pub extern "system" fn Java_mp_code_CursorController_recv( /// Utility method to convert a [crate::api::Cursor] to its Java equivalent. fn jni_recv(env: &mut JNIEnv, cursor: Option) -> jobject { match cursor { - None => JObject::null().as_raw(), + None => JObject::default(), Some(event) => { - let class = env.find_class("mp/code/data/Cursor").expect("Couldn't find class!"); - env.new_object( - class, - "(IIIILjava/lang/String;Ljava/lang/String;)V", - &[ - JValueGen::Int(event.start.0), - JValueGen::Int(event.start.1), - JValueGen::Int(event.end.0), - JValueGen::Int(event.end.1), - JValueGen::Object(&env.new_string(event.buffer).expect("Failed to create String!")), - JValueGen::Object(&env.new_string(event.user.map(|x| x.to_string()).unwrap_or_default()).expect("Failed to create String!")) - ] - ).expect("failed creating object").into_raw() + env.find_class("mp/code/data/Cursor") + .and_then(|class| { + let buffer = env.new_string(&event.buffer).jexcept(env); + let user = event.user + .map(|uuid| uuid.to_string()) + .map(|user| env.new_string(user).jexcept(env)) + .unwrap_or_default(); + env.new_object( + class, + "(IIIILjava/lang/String;Ljava/lang/String;)V", + &[ + JValueGen::Int(event.start.0), + JValueGen::Int(event.start.1), + JValueGen::Int(event.end.0), + JValueGen::Int(event.end.1), + JValueGen::Object(&buffer), + JValueGen::Object(&user) + ] + ) + }).jexcept(env) } - } + }.as_raw() } /// Receives from Java, converts and sends a [crate::api::Cursor]. @@ -57,28 +64,38 @@ pub extern "system" fn Java_mp_code_CursorController_send<'local>( self_ptr: jlong, input: JObject<'local>, ) { - let start_row = env.get_field(&input, "startRow", "I").expect("could not get field").i().expect("field was not of expected type"); - let start_col = env.get_field(&input, "startCol", "I").expect("could not get field").i().expect("field was not of expected type"); - let end_row = env.get_field(&input, "endRow", "I").expect("could not get field").i().expect("field was not of expected type"); - let end_col = env.get_field(&input, "endCol", "I").expect("could not get field").i().expect("field was not of expected type"); + let start_row = env.get_field(&input, "startRow", "I") + .and_then(|sr| sr.i()) + .jexcept(&mut env); + let start_col = env.get_field(&input, "startCol", "I") + .and_then(|sc| sc.i()) + .jexcept(&mut env); + let end_row = env.get_field(&input, "endRow", "I") + .and_then(|er| er.i()) + .jexcept(&mut env); + let end_col = env.get_field(&input, "endCol", "I") + .and_then(|ec| ec.i()) + .jexcept(&mut env); let buffer = env.get_field(&input, "buffer", "Ljava/lang/String;") - .expect("could not get field") - .l() - .expect("field was not of expected type") - .into(); - let buffer = env.get_string(&buffer).expect("Failed to get String!").into(); - + .and_then(|b| b.l()) + .map(|b| b.into()) + .jexcept(&mut env); + let buffer = env.get_string(&buffer) + .map(|b| b.into()) + .jexcept(&mut env); + let user: JString = env.get_field(&input, "user", "Ljava/lang/String;") - .expect("could not get field") - .l() - .expect("field was not of expected type") - .into(); + .and_then(|u| u.l()) + .map(|u| u.into()) + .jexcept(&mut env); let user = if user.is_null() { None } else { - let jstring = env.get_string(&user).expect("Failed to get String!"); - Some(uuid::Uuid::parse_str(jstring.to_str().expect("Not valid UTF-8")).expect("Invalid UUI!")) + let user: String = env.get_string(&user) + .map(|u| u.into()) + .jexcept(&mut env); + Some(uuid::Uuid::parse_str(&user).jexcept(&mut env)) }; let controller = unsafe { Box::leak(Box::from_raw(self_ptr as *mut crate::cursor::Controller)) }; diff --git a/src/ffi/java/mod.rs b/src/ffi/java/mod.rs index f35a5ec..2d6d9b3 100644 --- a/src/ffi/java/mod.rs +++ b/src/ffi/java/mod.rs @@ -1,6 +1,5 @@ pub mod client; pub mod workspace; -pub mod util; pub mod cursor; pub mod buffer; @@ -34,3 +33,48 @@ pub(crate) fn setup_logger(debug: bool, path: Option) { builder.with_writer(std::sync::Mutex::new(std::io::stdout())).init(); } } + +/// A trait meant for our [crate::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 { + /// Unwraps 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) -> T; +} + +impl JExceptable for crate::Result where T: Default { + fn jexcept(self, env: &mut jni::JNIEnv) -> T { + if let Err(err) = &self { + let msg = format!("{err}"); + match err { + crate::Error::InvalidState { .. } => env.throw_new("mp/code/exceptions/InvalidStateException", msg), + crate::Error::Deadlocked => env.throw_new("mp/code/exceptions/DeadlockedException", msg), + crate::Error::Transport { .. } => env.throw_new("mp/code/exceptions/TransportException", msg), + crate::Error::Channel { .. } => env.throw_new("mp/code/exceptions/ChannelException", msg) + }.jexcept(env); + } + self.unwrap_or_default() + } +} + +impl JExceptable for Result where T: Default { + fn jexcept(self, env: &mut jni::JNIEnv) -> T { + if let Err(err) = &self { + let msg = format!("{err}"); + env.throw_new("mp/code/exceptions/JNIException", msg) + .expect("A severe error occurred: we were unable to create a JNIException. This is an unrecoverable state."); + } + self.unwrap_or_default() + } +} + +impl JExceptable for Result where T: Default { + fn jexcept(self, env: &mut jni::JNIEnv) -> T { + if let Err(err) = &self { + let msg = format!("{err}"); + env.throw_new("java/lang/IllegalArgumentException", msg) + .expect("A severe error occurred: we were unable to create a JNIException. This is an unrecoverable state."); + } + self.unwrap_or_default() + } +} diff --git a/src/ffi/java/util.rs b/src/ffi/java/util.rs deleted file mode 100644 index a8e4d43..0000000 --- a/src/ffi/java/util.rs +++ /dev/null @@ -1,23 +0,0 @@ -use jni::JNIEnv; - -/// A trait meant for our [crate::Result] type to make converting it to Java easier. -pub(crate) trait JExceptable { - /// Unwraps 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 JNIEnv) -> T; -} - -impl JExceptable for crate::Result where T: Default { - fn jexcept(self, env: &mut JNIEnv) -> T { - if let Err(err) = &self { - let msg = format!("{err}"); - match err { - crate::Error::InvalidState { .. } => env.throw_new("mp/code/exceptions/InvalidStateException", msg), - crate::Error::Deadlocked => env.throw_new("mp/code/exceptions/DeadlockedException", msg), - crate::Error::Transport { .. } => env.throw_new("mp/code/exceptions/TransportException", msg), - crate::Error::Channel { .. } => env.throw_new("mp/code/exceptions/ChannelException", msg) - }.expect("Failed to throw exception!"); - } - self.unwrap_or_default() - } -} diff --git a/src/ffi/java/workspace.rs b/src/ffi/java/workspace.rs index 5bf0377..b3673a4 100644 --- a/src/ffi/java/workspace.rs +++ b/src/ffi/java/workspace.rs @@ -1,19 +1,17 @@ use jni::{objects::{JClass, JObject, JString, JValueGen}, sys::{jlong, jobject, jobjectArray, jstring}, JNIEnv}; use crate::Workspace; -use super::{util::JExceptable, RT}; +use super::{JExceptable, RT}; /// Gets the workspace id. #[no_mangle] pub extern "system" fn Java_mp_code_Workspace_get_1workspace_1id<'local>( - env: JNIEnv<'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()) - .expect("Failed to convert to Java String!") - .as_raw() + env.new_string(workspace.id()).jexcept(&mut env).as_raw() } /// Gets a cursor controller by name and returns a pointer to it. @@ -24,10 +22,9 @@ pub extern "system" fn Java_mp_code_Workspace_get_1cursor<'local>( self_ptr: jlong ) -> jobject { let workspace = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Workspace)) }; - let class = env.find_class("mp/code/CursorController").expect("Failed to find class"); - env.new_object(class, "(J)V", &[JValueGen::Long(Box::into_raw(Box::new(workspace.cursor())) as jlong)]) - .expect("Failed to initialise object") - .as_raw() + env.find_class("mp/code/CursorController").and_then(|class| + env.new_object(class, "(J)V", &[JValueGen::Long(Box::into_raw(Box::new(workspace.cursor())) as jlong)]) + ).jexcept(&mut env).as_raw() } /// Gets a buffer controller by name and returns a pointer to it. @@ -39,15 +36,15 @@ pub extern "system" fn Java_mp_code_Workspace_get_1buffer<'local>( input: JString<'local> ) -> jobject { let workspace = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Workspace)) }; - let path = unsafe { env.get_string_unchecked(&input).expect("Couldn't get java string!") }; - if let Some(buf) = workspace.buffer_by_name(path.to_str().expect("Not UTF-8!")) { - let class = env.find_class("mp/code/BufferController").expect("Failed to find class"); - env.new_object(class, "(J)V", &[JValueGen::Long(Box::into_raw(Box::new(buf)) as jlong)]) - .expect("Failed to initialise object") - .as_raw() - } else { - JObject::null().as_raw() - } + let path = unsafe { env.get_string_unchecked(&input) } + .map(|path| path.to_string_lossy().to_string()) + .jexcept(&mut env); + + workspace.buffer_by_name(&path).map(|buf| { + env.find_class("mp/code/BufferController").and_then(|class| + env.new_object(class, "(J)V", &[JValueGen::Long(Box::into_raw(Box::new(buf)) as jlong)]) + ).jexcept(&mut env) + }).unwrap_or_default().as_raw() } /// Creates a new buffer. @@ -59,8 +56,11 @@ pub extern "system" fn Java_mp_code_Workspace_create_1buffer<'local>( input: JString<'local> ) { let ws = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Workspace)) }; - let path = unsafe { env.get_string_unchecked(&input).expect("Couldn't get java string!") }; - RT.block_on(ws.create(path.to_str().expect("Not UTF-8"))).jexcept(&mut env); + let path = unsafe { env.get_string_unchecked(&input) } + .map(|path| path.to_string_lossy().to_string()) + .jexcept(&mut env); + RT.block_on(ws.create(&path)) + .jexcept(&mut env); } /// Gets the filetree. @@ -72,16 +72,16 @@ pub extern "system" fn Java_mp_code_Workspace_get_1file_1tree( ) -> jobjectArray { let workspace = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Workspace)) }; let file_tree = workspace.filetree(); - let class = env.find_class("java/lang/String").expect("Failed to find class!"); - let arr = env.new_object_array(file_tree.len() as i32, class, JObject::null()) - .expect("failed creating array"); - for (idx, path) in file_tree.iter().enumerate() { - let js = env.new_string(path).expect("Failed to create String!"); - env.set_object_array_element(&arr, idx as i32, js) - .expect("Failed to set array element!") - } - - arr.as_raw() + env.find_class("java/lang/String") + .and_then(|class| env.new_object_array(file_tree.len() as i32, class, JObject::null())) + .map(|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) + } + arr + }).jexcept(&mut env).as_raw() } /// Attaches to a buffer and returns a pointer to its [crate::buffer::Controller]. @@ -93,13 +93,15 @@ pub extern "system" fn Java_mp_code_Workspace_attach_1to_1buffer<'local>( input: JString<'local> ) -> jobject { let workspace = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Workspace)) }; - let path = unsafe { env.get_string_unchecked(&input).expect("Couldn't get java string!") }; - RT.block_on(workspace.attach(path.to_str().expect("Not UTF-8!"))) + let path = unsafe { env.get_string_unchecked(&input) } + .map(|path| path.to_string_lossy().to_string()) + .jexcept(&mut env); + RT.block_on(workspace.attach(&path)) .map(|buffer| Box::into_raw(Box::new(buffer)) as jlong) .map(|ptr| { - let class = env.find_class("mp/code/BufferController").expect("Failed to find class"); - env.new_object(class, "(J)V", &[JValueGen::Long(ptr)]) - .expect("Failed to initialise object") + env.find_class("mp/code/BufferController") + .and_then(|class| env.new_object(class, "(J)V", &[JValueGen::Long(ptr)])) + .jexcept(&mut env) }).jexcept(&mut env).as_raw() } @@ -111,18 +113,19 @@ pub extern "system" fn Java_mp_code_Workspace_detach_1from_1buffer<'local>( input: JString<'local> ) -> jobject { let workspace = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Workspace)) }; - let path = unsafe { env.get_string_unchecked(&input).expect("Couldn't get java string!") }; - let name = match workspace.detach(path.to_str().expect("Not UTF-8")) { + let path = unsafe { env.get_string_unchecked(&input) } + .map(|path| path.to_string_lossy().to_string()) + .jexcept(&mut env); + let name = match workspace.detach(&path) { crate::workspace::worker::DetachResult::NotAttached => "NOT_ATTACHED", crate::workspace::worker::DetachResult::Detaching => "DETACHED", crate::workspace::worker::DetachResult::AlreadyDetached => "ALREADY_DETACHED" }; - let class = env.find_class("mp/code/data/DetachResult").expect("Failed to find class!"); - env.get_static_field(class, name, "Lmp/code/data/DetachResult;") - .expect("Failed to get field!") - .l() - .expect("Field was of wrong type!") + env.find_class("mp/code/data/DetachResult") + .and_then(|class| env.get_static_field(class, name, "Lmp/code/data/DetachResult;")) + .and_then(|res| res.l()) + .jexcept(&mut env) .as_raw() } @@ -157,21 +160,22 @@ pub extern "system" fn Java_mp_code_Workspace_list_1buffer_1users<'local>( input: JString<'local>, ) -> jobjectArray { let workspace = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Workspace)) }; - let buffer = unsafe { env.get_string_unchecked(&input).expect("Couldn't get java string!") }; - let users = RT.block_on(workspace.list_buffer_users(buffer.to_str().expect("Not UTF-8!"))) + let buffer = unsafe { env.get_string_unchecked(&input) } + .map(|buffer| buffer.to_string_lossy().to_string()) + .jexcept(&mut env); + let users = RT.block_on(workspace.list_buffer_users(&buffer)) .jexcept(&mut env); - let class = env.find_class("java/lang/String").expect("Failed to find class!"); - let arr = env.new_object_array(users.len() as i32, class, JObject::null()) - .expect("failed creating array"); - - for (idx, user) in users.iter().enumerate() { - let js = env.new_string(&user.id).expect("Failed to create String!"); - env.set_object_array_element(&arr, idx as i32, js) - .expect("Failed to set array element!") - } - - arr.as_raw() + env.find_class("java/lang/String") + .and_then(|class| env.new_object_array(users.len() as i32, class, JObject::null())) + .map(|arr| { + for (idx, user) in users.iter().enumerate() { + env.new_string(&user.id) + .and_then(|id| env.set_object_array_element(&arr, idx as i32, id)) + .jexcept(&mut env); + } + arr + }).jexcept(&mut env).as_raw() } /// Deletes a buffer. @@ -182,9 +186,11 @@ pub extern "system" fn Java_mp_code_Workspace_delete_1buffer<'local>( self_ptr: jlong, input: JString<'local>, ) { - let buffer = unsafe { env.get_string_unchecked(&input).expect("Couldn't get java string!") }; let workspace = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Workspace)) }; - RT.block_on(workspace.delete(buffer.to_str().expect("Not UTF-8!"))).jexcept(&mut env); + let buffer = unsafe { env.get_string_unchecked(&input) } + .map(|buffer| buffer.to_string_lossy().to_string()) + .jexcept(&mut env); + RT.block_on(workspace.delete(&buffer)).jexcept(&mut env); } /// Receives a workspace event if present. @@ -197,29 +203,28 @@ pub extern "system" fn Java_mp_code_Workspace_event( let workspace = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Workspace)) }; RT.block_on(workspace.event()) .map(|event| { - let type_class = env.find_class("mp/code/Workspace$Event$Type").expect("Failed to find class!"); let (name, arg) = match event { crate::api::Event::FileTreeUpdated => ("FILE_TREE_UPDATED", None), crate::api::Event::UserJoin(arg) => ("USER_JOIN", Some(arg)), crate::api::Event::UserLeave(arg) => ("USER_LEAVE", Some(arg)), }; - let event_type = env.get_static_field(type_class, name, "Lmp/code/Workspace/Event/Type;") - .expect("Failed to get field!") - .l() - .expect("Field was not of expected type!"); - - let arg = arg.map(|s| env.new_string(s).expect("Failed to create String!")) + let event_type = env.find_class("mp/code/Workspace$Event$Type") + .and_then(|class| env.get_static_field(class, name, "Lmp/code/Workspace/Event/Type;")) + .and_then(|f| f.l()) + .jexcept(&mut env); + let arg = arg.map(|s| env.new_string(s).jexcept(&mut env)) .unwrap_or_default(); - let event_class = env.find_class("mp/code/Workspace$Event").expect("Failed to find class!"); - env.new_object( - event_class, - "(Lmp/code/Workspace/Event/Type;Ljava/lang/String;)V", - &[ - JValueGen::Object(&event_type), - JValueGen::Object(&arg) - ] - ).expect("Failed to create object!") + env.find_class("mp/code/Workspace$Event").and_then(|class| + env.new_object( + class, + "(Lmp/code/Workspace/Event/Type;Ljava/lang/String;)V", + &[ + JValueGen::Object(&event_type), + JValueGen::Object(&arg) + ] + ) + ).jexcept(&mut env) }).jexcept(&mut env).as_raw() } @@ -240,20 +245,16 @@ pub extern "system" fn Java_mp_code_Workspace_select_1buffer( } } - let active = RT.block_on(crate::buffer::tools::select_buffer( + RT.block_on(crate::buffer::tools::select_buffer( &controllers, Some(std::time::Duration::from_millis(timeout as u64)), &RT, - )).jexcept(&mut env); - - if let Some(buf) = active { - let class = env.find_class("mp/code/BufferController").expect("Failed to find class"); - env.new_object(class, "(J)V", &[JValueGen::Long(Box::into_raw(Box::new(buf)) as jlong)]) - .expect("Failed to initialise object") - .as_raw() - } else { - JObject::null().as_raw() - } + )).jexcept(&mut env) + .map(|buf| { + env.find_class("mp/code/BufferController").and_then(|class| + env.new_object(class, "(J)V", &[JValueGen::Long(Box::into_raw(Box::new(buf)) as jlong)]) + ).jexcept(&mut env) + }).unwrap_or_default().as_raw() } /// Called by the Java GC to drop a [Workspace].