feat(java): replace all expect/unwrap with throwing JNIException

This commit is contained in:
zaaarf 2024-08-10 02:45:20 +02:00
parent 53503ae117
commit 6bf541028d
No known key found for this signature in database
GPG key ID: 102E445F4C3F829B
7 changed files with 255 additions and 188 deletions

View file

@ -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);
}
}

View file

@ -2,33 +2,33 @@ use jni::{objects::{JClass, JObject, JValueGen}, sys::{jlong, jobject, jstring},
use crate::api::Controller; use crate::api::Controller;
use super::{util::JExceptable, RT}; use super::{JExceptable, RT};
/// Gets the name of the buffer. /// Gets the name of the buffer.
#[no_mangle] #[no_mangle]
pub extern "system" fn Java_mp_code_BufferController_get_1name( pub extern "system" fn Java_mp_code_BufferController_get_1name(
env: JNIEnv, mut env: JNIEnv,
_class: JClass, _class: JClass,
self_ptr: jlong, self_ptr: jlong,
) -> jstring { ) -> jstring {
let controller = unsafe { Box::leak(Box::from_raw(self_ptr as *mut crate::buffer::Controller)) }; let controller = unsafe { Box::leak(Box::from_raw(self_ptr as *mut crate::buffer::Controller)) };
let content = controller.name(); let content = controller.name();
env.new_string(content) env.new_string(content)
.expect("could not create jstring") .jexcept(&mut env)
.as_raw() .as_raw()
} }
/// Gets the contents of the buffers. /// Gets the contents of the buffers.
#[no_mangle] #[no_mangle]
pub extern "system" fn Java_mp_code_BufferController_get_1content( pub extern "system" fn Java_mp_code_BufferController_get_1content(
env: JNIEnv, mut env: JNIEnv,
_class: JClass, _class: JClass,
self_ptr: jlong, self_ptr: jlong,
) -> jstring { ) -> jstring {
let controller = unsafe { Box::leak(Box::from_raw(self_ptr as *mut crate::buffer::Controller)) }; let controller = unsafe { Box::leak(Box::from_raw(self_ptr as *mut crate::buffer::Controller)) };
let content = controller.content(); let content = controller.content();
env.new_string(content) env.new_string(content)
.expect("could not create jstring") .jexcept(&mut env)
.as_raw() .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. /// Utility method to convert a [crate::api::TextChange] to its Java equivalent.
fn recv_jni(env: &mut JNIEnv, change: Option<crate::api::TextChange>) -> jobject { fn recv_jni(env: &mut JNIEnv, change: Option<crate::api::TextChange>) -> jobject {
match change { match change {
None => JObject::null().as_raw(), None => JObject::default(),
Some(event) => { Some(event) => {
let class = env.find_class("mp/code/data/TextChange").expect("Couldn't find class!"); let content = env.new_string(event.content).jexcept(env);
env.find_class("mp/code/data/TextChange")
.and_then(|class| {
env.new_object( env.new_object(
class, class,
"(JJLjava/lang/String;)V", "(JJLjava/lang/String;)V",
&[ &[
JValueGen::Long(jlong::from(event.start)), JValueGen::Long(jlong::from(event.start)),
JValueGen::Long(jlong::from(event.end)), JValueGen::Long(jlong::from(event.end)),
JValueGen::Object(&env.new_string(event.content).expect("Failed to create String!")), JValueGen::Object(&content),
] ]
).expect("failed creating object").into_raw() )
} }).jexcept(env)
} }
}.as_raw()
} }
/// Receives from Java, converts and sends a [crate::api::TextChange]. /// 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, self_ptr: jlong,
input: JObject<'local> 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 start = env.get_field(&input, "start", "J").and_then(|s| s.j()).jexcept(&mut env);
let end = env.get_field(&input, "end", "J").expect("could not get field").j().expect("field was not of expected type"); 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;") let content = env.get_field(&input, "content", "Ljava/lang/String;")
.expect("could not get field") .and_then(|c| c.l())
.l() .map(|c| c.into())
.expect("field was not of expected type") .jexcept(&mut env);
.into(); let content = unsafe { env.get_string_unchecked(&content) }
let content = env.get_string(&content).expect("Failed to get String!").into(); .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)) }; let controller = unsafe { Box::leak(Box::from_raw(self_ptr as *mut crate::buffer::Controller)) };
controller.send(crate::api::TextChange { controller.send(crate::api::TextChange {

View file

@ -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 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. /// Connects to a given URL and returns a [Client] to interact with that server.
#[no_mangle] #[no_mangle]
@ -12,15 +12,21 @@ pub extern "system" fn Java_mp_code_Client_connect<'local>(
user: JString<'local>, user: JString<'local>,
pwd: JString<'local> pwd: JString<'local>
) -> jobject { ) -> jobject {
let url: String = env.get_string(&url).expect("Couldn't get java string!").into(); let url: String = env.get_string(&url)
let user: String = env.get_string(&user).expect("Couldn't get java string!").into(); .map(|s| s.into())
let pwd: String = env.get_string(&pwd).expect("Couldn't get java string!").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)) RT.block_on(crate::Client::new(&url, &user, &pwd))
.map(|client| Box::into_raw(Box::new(client)) as jlong) .map(|client| Box::into_raw(Box::new(client)) as jlong)
.map(|ptr| { .map(|ptr| {
let class = env.find_class("mp/code/Client").expect("Failed to find class"); env.find_class("mp/code/Client")
env.new_object(class, "(J)V", &[JValueGen::Long(ptr)]) .and_then(|class| env.new_object(class, "(J)V", &[JValueGen::Long(ptr)]))
.expect("Failed to initialise object") .jexcept(&mut env)
}).jexcept(&mut env).as_raw() }).jexcept(&mut env).as_raw()
} }
@ -33,14 +39,16 @@ pub extern "system" fn Java_mp_code_Client_join_1workspace<'local>(
input: JString<'local> input: JString<'local>
) -> jobject { ) -> jobject {
let client = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Client)) }; 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!") }; let workspace_id = unsafe { env.get_string_unchecked(&input) }
RT.block_on(client.join_workspace(workspace_id.to_str().expect("Not UTF-8"))) .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| spawn_updater(workspace.clone()))
.map(|workspace| Box::into_raw(Box::new(workspace)) as jlong) .map(|workspace| Box::into_raw(Box::new(workspace)) as jlong)
.map(|ptr| { .map(|ptr| {
let class = env.find_class("mp/code/Workspace").expect("Failed to find class"); env.find_class("mp/code/Workspace")
env.new_object(class, "(J)V", &[JValueGen::Long(ptr)]) .and_then(|class| env.new_object(class, "(J)V", &[JValueGen::Long(ptr)]))
.expect("Failed to initialise object") .jexcept(&mut env)
}).jexcept(&mut env).as_raw() }).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. /// Leaves a [Workspace] and returns whether or not the client was in such workspace.
#[no_mangle] #[no_mangle]
pub extern "system" fn Java_mp_code_Client_leave_1workspace<'local>( pub extern "system" fn Java_mp_code_Client_leave_1workspace<'local>(
env: JNIEnv<'local>, mut env: JNIEnv<'local>,
_class: JClass<'local>, _class: JClass<'local>,
self_ptr: jlong, self_ptr: jlong,
input: JString<'local> input: JString<'local>
) -> jboolean { ) -> jboolean {
let client = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Client)) }; 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!") }; unsafe { env.get_string_unchecked(&input) }
client.leave_workspace(workspace_id.to_str().expect("Not UTF-8")) as jboolean .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. /// Gets a [Workspace] by name and returns a pointer to it.
#[no_mangle] #[no_mangle]
@ -78,14 +88,16 @@ pub extern "system" fn Java_mp_code_Client_get_1workspace<'local>(
input: JString<'local> input: JString<'local>
) -> jobject { ) -> jobject {
let client = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Client)) }; 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!") }; let workspace_id = unsafe { env.get_string_unchecked(&input) }
client.get_workspace(workspace_id.to_str().expect("Not UTF-8")) .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(|workspace| Box::into_raw(Box::new(workspace)) as jlong)
.map(|ptr| { .map(|ptr| {
let class = env.find_class("mp/code/Workspace").expect("Failed to find class"); env.find_class("mp/code/Workspace")
env.new_object(class, "(J)V", &[JValueGen::Long(ptr)]) .and_then(|class| env.new_object(class, "(J)V", &[JValueGen::Long(ptr)]))
.expect("Failed to initialise object") .jexcept(&mut env)
}).unwrap_or(JObject::null()).as_raw() }).unwrap_or_default().as_raw()
} }
/// Sets up the tracing subscriber. /// Sets up the tracing subscriber.
@ -99,7 +111,7 @@ pub extern "system" fn Java_mp_code_Client_setup_1tracing<'local>(
true, true,
Some(path) Some(path)
.filter(|p| p.is_null()) .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))
); );
} }

View file

@ -1,7 +1,7 @@
use jni::{objects::{JClass, JObject, JString, JValueGen}, sys::{jlong, jobject}, JNIEnv}; 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. /// Tries to fetch a [crate::api::Cursor], or returns null if there's nothing.
#[no_mangle] #[no_mangle]
@ -30,9 +30,15 @@ pub extern "system" fn Java_mp_code_CursorController_recv(
/// Utility method to convert a [crate::api::Cursor] to its Java equivalent. /// Utility method to convert a [crate::api::Cursor] to its Java equivalent.
fn jni_recv(env: &mut JNIEnv, cursor: Option<crate::api::Cursor>) -> jobject { fn jni_recv(env: &mut JNIEnv, cursor: Option<crate::api::Cursor>) -> jobject {
match cursor { match cursor {
None => JObject::null().as_raw(), None => JObject::default(),
Some(event) => { Some(event) => {
let class = env.find_class("mp/code/data/Cursor").expect("Couldn't find class!"); 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( env.new_object(
class, class,
"(IIIILjava/lang/String;Ljava/lang/String;)V", "(IIIILjava/lang/String;Ljava/lang/String;)V",
@ -41,12 +47,13 @@ fn jni_recv(env: &mut JNIEnv, cursor: Option<crate::api::Cursor>) -> jobject {
JValueGen::Int(event.start.1), JValueGen::Int(event.start.1),
JValueGen::Int(event.end.0), JValueGen::Int(event.end.0),
JValueGen::Int(event.end.1), JValueGen::Int(event.end.1),
JValueGen::Object(&env.new_string(event.buffer).expect("Failed to create String!")), JValueGen::Object(&buffer),
JValueGen::Object(&env.new_string(event.user.map(|x| x.to_string()).unwrap_or_default()).expect("Failed to create String!")) JValueGen::Object(&user)
] ]
).expect("failed creating object").into_raw() )
} }).jexcept(env)
} }
}.as_raw()
} }
/// Receives from Java, converts and sends a [crate::api::Cursor]. /// 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, self_ptr: jlong,
input: JObject<'local>, 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_row = env.get_field(&input, "startRow", "I")
let start_col = env.get_field(&input, "startCol", "I").expect("could not get field").i().expect("field was not of expected type"); .and_then(|sr| sr.i())
let end_row = env.get_field(&input, "endRow", "I").expect("could not get field").i().expect("field was not of expected type"); .jexcept(&mut env);
let end_col = env.get_field(&input, "endCol", "I").expect("could not get field").i().expect("field was not of expected type"); 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;") let buffer = env.get_field(&input, "buffer", "Ljava/lang/String;")
.expect("could not get field") .and_then(|b| b.l())
.l() .map(|b| b.into())
.expect("field was not of expected type") .jexcept(&mut env);
.into(); let buffer = env.get_string(&buffer)
let buffer = env.get_string(&buffer).expect("Failed to get String!").into(); .map(|b| b.into())
.jexcept(&mut env);
let user: JString = env.get_field(&input, "user", "Ljava/lang/String;") let user: JString = env.get_field(&input, "user", "Ljava/lang/String;")
.expect("could not get field") .and_then(|u| u.l())
.l() .map(|u| u.into())
.expect("field was not of expected type") .jexcept(&mut env);
.into();
let user = if user.is_null() { let user = if user.is_null() {
None None
} else { } else {
let jstring = env.get_string(&user).expect("Failed to get String!"); let user: String = env.get_string(&user)
Some(uuid::Uuid::parse_str(jstring.to_str().expect("Not valid UTF-8")).expect("Invalid UUI!")) .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)) }; let controller = unsafe { Box::leak(Box::from_raw(self_ptr as *mut crate::cursor::Controller)) };

View file

@ -1,6 +1,5 @@
pub mod client; pub mod client;
pub mod workspace; pub mod workspace;
pub mod util;
pub mod cursor; pub mod cursor;
pub mod buffer; pub mod buffer;
@ -34,3 +33,48 @@ pub(crate) fn setup_logger(debug: bool, path: Option<String>) {
builder.with_writer(std::sync::Mutex::new(std::io::stdout())).init(); 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<T> {
/// 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<T> JExceptable<T> for crate::Result<T> 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<T> JExceptable<T> for Result<T, jni::errors::Error> 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<T> JExceptable<T> for Result<T, uuid::Error> 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()
}
}

View file

@ -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<T> {
/// 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<T> JExceptable<T> for crate::Result<T> 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()
}
}

View file

@ -1,19 +1,17 @@
use jni::{objects::{JClass, JObject, JString, JValueGen}, sys::{jlong, jobject, jobjectArray, jstring}, JNIEnv}; use jni::{objects::{JClass, JObject, JString, JValueGen}, sys::{jlong, jobject, jobjectArray, jstring}, JNIEnv};
use crate::Workspace; use crate::Workspace;
use super::{util::JExceptable, RT}; use super::{JExceptable, RT};
/// Gets the workspace id. /// Gets the workspace id.
#[no_mangle] #[no_mangle]
pub extern "system" fn Java_mp_code_Workspace_get_1workspace_1id<'local>( pub extern "system" fn Java_mp_code_Workspace_get_1workspace_1id<'local>(
env: JNIEnv<'local>, mut env: JNIEnv<'local>,
_class: JClass<'local>, _class: JClass<'local>,
self_ptr: jlong self_ptr: jlong
) -> jstring { ) -> jstring {
let workspace = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Workspace)) }; let workspace = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Workspace)) };
env.new_string(workspace.id()) env.new_string(workspace.id()).jexcept(&mut env).as_raw()
.expect("Failed to convert to Java String!")
.as_raw()
} }
/// Gets a cursor controller by name and returns a pointer to it. /// 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 self_ptr: jlong
) -> jobject { ) -> jobject {
let workspace = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Workspace)) }; 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.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)]) env.new_object(class, "(J)V", &[JValueGen::Long(Box::into_raw(Box::new(workspace.cursor())) as jlong)])
.expect("Failed to initialise object") ).jexcept(&mut env).as_raw()
.as_raw()
} }
/// Gets a buffer controller by name and returns a pointer to it. /// 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> input: JString<'local>
) -> jobject { ) -> jobject {
let workspace = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Workspace)) }; 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 path = unsafe { env.get_string_unchecked(&input) }
if let Some(buf) = workspace.buffer_by_name(path.to_str().expect("Not UTF-8!")) { .map(|path| path.to_string_lossy().to_string())
let class = env.find_class("mp/code/BufferController").expect("Failed to find class"); .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)]) env.new_object(class, "(J)V", &[JValueGen::Long(Box::into_raw(Box::new(buf)) as jlong)])
.expect("Failed to initialise object") ).jexcept(&mut env)
.as_raw() }).unwrap_or_default().as_raw()
} else {
JObject::null().as_raw()
}
} }
/// Creates a new buffer. /// Creates a new buffer.
@ -59,8 +56,11 @@ pub extern "system" fn Java_mp_code_Workspace_create_1buffer<'local>(
input: JString<'local> input: JString<'local>
) { ) {
let ws = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Workspace)) }; 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!") }; let path = unsafe { env.get_string_unchecked(&input) }
RT.block_on(ws.create(path.to_str().expect("Not UTF-8"))).jexcept(&mut env); .map(|path| path.to_string_lossy().to_string())
.jexcept(&mut env);
RT.block_on(ws.create(&path))
.jexcept(&mut env);
} }
/// Gets the filetree. /// Gets the filetree.
@ -72,16 +72,16 @@ pub extern "system" fn Java_mp_code_Workspace_get_1file_1tree(
) -> jobjectArray { ) -> jobjectArray {
let workspace = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Workspace)) }; let workspace = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Workspace)) };
let file_tree = workspace.filetree(); let file_tree = workspace.filetree();
let class = env.find_class("java/lang/String").expect("Failed to find class!"); env.find_class("java/lang/String")
let arr = env.new_object_array(file_tree.len() as i32, class, JObject::null()) .and_then(|class| env.new_object_array(file_tree.len() as i32, class, JObject::null()))
.expect("failed creating array"); .map(|arr| {
for (idx, path) in file_tree.iter().enumerate() { for (idx, path) in file_tree.iter().enumerate() {
let js = env.new_string(path).expect("Failed to create String!"); env.new_string(path)
env.set_object_array_element(&arr, idx as i32, js) .and_then(|path| env.set_object_array_element(&arr, idx as i32, path))
.expect("Failed to set array element!") .jexcept(&mut env)
} }
arr
arr.as_raw() }).jexcept(&mut env).as_raw()
} }
/// Attaches to a buffer and returns a pointer to its [crate::buffer::Controller]. /// 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> input: JString<'local>
) -> jobject { ) -> jobject {
let workspace = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Workspace)) }; 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 path = unsafe { env.get_string_unchecked(&input) }
RT.block_on(workspace.attach(path.to_str().expect("Not UTF-8!"))) .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(|buffer| Box::into_raw(Box::new(buffer)) as jlong)
.map(|ptr| { .map(|ptr| {
let class = env.find_class("mp/code/BufferController").expect("Failed to find class"); env.find_class("mp/code/BufferController")
env.new_object(class, "(J)V", &[JValueGen::Long(ptr)]) .and_then(|class| env.new_object(class, "(J)V", &[JValueGen::Long(ptr)]))
.expect("Failed to initialise object") .jexcept(&mut env)
}).jexcept(&mut env).as_raw() }).jexcept(&mut env).as_raw()
} }
@ -111,18 +113,19 @@ pub extern "system" fn Java_mp_code_Workspace_detach_1from_1buffer<'local>(
input: JString<'local> input: JString<'local>
) -> jobject { ) -> jobject {
let workspace = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Workspace)) }; 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 path = unsafe { env.get_string_unchecked(&input) }
let name = match workspace.detach(path.to_str().expect("Not UTF-8")) { .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::NotAttached => "NOT_ATTACHED",
crate::workspace::worker::DetachResult::Detaching => "DETACHED", crate::workspace::worker::DetachResult::Detaching => "DETACHED",
crate::workspace::worker::DetachResult::AlreadyDetached => "ALREADY_DETACHED" crate::workspace::worker::DetachResult::AlreadyDetached => "ALREADY_DETACHED"
}; };
let class = env.find_class("mp/code/data/DetachResult").expect("Failed to find class!"); env.find_class("mp/code/data/DetachResult")
env.get_static_field(class, name, "Lmp/code/data/DetachResult;") .and_then(|class| env.get_static_field(class, name, "Lmp/code/data/DetachResult;"))
.expect("Failed to get field!") .and_then(|res| res.l())
.l() .jexcept(&mut env)
.expect("Field was of wrong type!")
.as_raw() .as_raw()
} }
@ -157,21 +160,22 @@ pub extern "system" fn Java_mp_code_Workspace_list_1buffer_1users<'local>(
input: JString<'local>, input: JString<'local>,
) -> jobjectArray { ) -> jobjectArray {
let workspace = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Workspace)) }; 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 buffer = unsafe { env.get_string_unchecked(&input) }
let users = RT.block_on(workspace.list_buffer_users(buffer.to_str().expect("Not UTF-8!"))) .map(|buffer| buffer.to_string_lossy().to_string())
.jexcept(&mut env);
let users = RT.block_on(workspace.list_buffer_users(&buffer))
.jexcept(&mut env); .jexcept(&mut env);
let class = env.find_class("java/lang/String").expect("Failed to find class!"); env.find_class("java/lang/String")
let arr = env.new_object_array(users.len() as i32, class, JObject::null()) .and_then(|class| env.new_object_array(users.len() as i32, class, JObject::null()))
.expect("failed creating array"); .map(|arr| {
for (idx, user) in users.iter().enumerate() { for (idx, user) in users.iter().enumerate() {
let js = env.new_string(&user.id).expect("Failed to create String!"); env.new_string(&user.id)
env.set_object_array_element(&arr, idx as i32, js) .and_then(|id| env.set_object_array_element(&arr, idx as i32, id))
.expect("Failed to set array element!") .jexcept(&mut env);
} }
arr
arr.as_raw() }).jexcept(&mut env).as_raw()
} }
/// Deletes a buffer. /// Deletes a buffer.
@ -182,9 +186,11 @@ pub extern "system" fn Java_mp_code_Workspace_delete_1buffer<'local>(
self_ptr: jlong, self_ptr: jlong,
input: JString<'local>, 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)) }; 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. /// 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)) }; let workspace = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Workspace)) };
RT.block_on(workspace.event()) RT.block_on(workspace.event())
.map(|event| { .map(|event| {
let type_class = env.find_class("mp/code/Workspace$Event$Type").expect("Failed to find class!");
let (name, arg) = match event { let (name, arg) = match event {
crate::api::Event::FileTreeUpdated => ("FILE_TREE_UPDATED", None), crate::api::Event::FileTreeUpdated => ("FILE_TREE_UPDATED", None),
crate::api::Event::UserJoin(arg) => ("USER_JOIN", Some(arg)), crate::api::Event::UserJoin(arg) => ("USER_JOIN", Some(arg)),
crate::api::Event::UserLeave(arg) => ("USER_LEAVE", 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;") let event_type = env.find_class("mp/code/Workspace$Event$Type")
.expect("Failed to get field!") .and_then(|class| env.get_static_field(class, name, "Lmp/code/Workspace/Event/Type;"))
.l() .and_then(|f| f.l())
.expect("Field was not of expected type!"); .jexcept(&mut env);
let arg = arg.map(|s| env.new_string(s).jexcept(&mut env))
let arg = arg.map(|s| env.new_string(s).expect("Failed to create String!"))
.unwrap_or_default(); .unwrap_or_default();
let event_class = env.find_class("mp/code/Workspace$Event").expect("Failed to find class!"); env.find_class("mp/code/Workspace$Event").and_then(|class|
env.new_object( env.new_object(
event_class, class,
"(Lmp/code/Workspace/Event/Type;Ljava/lang/String;)V", "(Lmp/code/Workspace/Event/Type;Ljava/lang/String;)V",
&[ &[
JValueGen::Object(&event_type), JValueGen::Object(&event_type),
JValueGen::Object(&arg) JValueGen::Object(&arg)
] ]
).expect("Failed to create object!") )
).jexcept(&mut env)
}).jexcept(&mut env).as_raw() }).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, &controllers,
Some(std::time::Duration::from_millis(timeout as u64)), Some(std::time::Duration::from_millis(timeout as u64)),
&RT, &RT,
)).jexcept(&mut env); )).jexcept(&mut env)
.map(|buf| {
if let Some(buf) = active { env.find_class("mp/code/BufferController").and_then(|class|
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)]) env.new_object(class, "(J)V", &[JValueGen::Long(Box::into_raw(Box::new(buf)) as jlong)])
.expect("Failed to initialise object") ).jexcept(&mut env)
.as_raw() }).unwrap_or_default().as_raw()
} else {
JObject::null().as_raw()
}
} }
/// Called by the Java GC to drop a [Workspace]. /// Called by the Java GC to drop a [Workspace].