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 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<crate::api::TextChange>) -> 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!");
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(&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].
@ -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 {

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

View file

@ -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,9 +30,15 @@ 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<crate::api::Cursor>) -> 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.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",
@ -41,12 +47,13 @@ fn jni_recv(env: &mut JNIEnv, cursor: Option<crate::api::Cursor>) -> jobject {
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!"))
JValueGen::Object(&buffer),
JValueGen::Object(&user)
]
).expect("failed creating object").into_raw()
}
)
}).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)) };

View file

@ -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<String>) {
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 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.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)])
.expect("Failed to initialise object")
.as_raw()
).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");
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)])
.expect("Failed to initialise object")
.as_raw()
} else {
JObject::null().as_raw()
}
).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");
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() {
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!")
env.new_string(path)
.and_then(|path| env.set_object_array_element(&arr, idx as i32, path))
.jexcept(&mut env)
}
arr.as_raw()
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");
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() {
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!")
env.new_string(&user.id)
.and_then(|id| env.set_object_array_element(&arr, idx as i32, id))
.jexcept(&mut env);
}
arr.as_raw()
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.find_class("mp/code/Workspace$Event").and_then(|class|
env.new_object(
event_class,
class,
"(Lmp/code/Workspace/Event/Type;Ljava/lang/String;)V",
&[
JValueGen::Object(&event_type),
JValueGen::Object(&arg)
]
).expect("Failed to create object!")
)
).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");
)).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)])
.expect("Failed to initialise object")
.as_raw()
} else {
JObject::null().as_raw()
}
).jexcept(&mut env)
}).unwrap_or_default().as_raw()
}
/// Called by the Java GC to drop a [Workspace].