feat(java): converted remaining glue to toolbox

This commit is contained in:
zaaarf 2024-09-23 00:26:11 +02:00
parent 7056dc341c
commit 1d0d87d521
No known key found for this signature in database
GPG key ID: 102E445F4C3F829B
6 changed files with 257 additions and 643 deletions

View file

@ -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. /// Get the name of the buffer.
#[no_mangle] #[jni(package = "mp.code", class = "BufferController", ptr)]
pub extern "system" fn Java_mp_code_BufferController_get_1name( fn get_name(controller: &mut crate::buffer::Controller) -> String {
mut env: JNIEnv, controller.path().to_string() //TODO: &str is built into the newer version
_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()
} }
/// Gets the contents of the buffers. /// Get the contents of the buffers.
#[no_mangle] #[jni(package = "mp.code", class = "BufferController", ptr)]
pub extern "system" fn Java_mp_code_BufferController_get_1content( fn get_content(controller: &mut crate::buffer::Controller) -> Result<String, ControllerError> {
mut env: JNIEnv, super::tokio().block_on(controller.content())
_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()
} }
/// Tries to fetch a [TextChange], or returns null if there's nothing. /// Try to fetch a [TextChange], or return null if there's nothing.
#[no_mangle] #[jni(package = "mp.code", class = "BufferController", ptr)]
pub extern "system" fn Java_mp_code_BufferController_try_1recv( fn try_recv(controller: &mut crate::buffer::Controller) -> Result<Option<TextChange>, ControllerError> {
mut env: JNIEnv, super::tokio().block_on(controller.try_recv())
_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)
} }
/// Blocks until it receives a [TextChange]. /// Block until it receives a [TextChange].
#[no_mangle] #[jni(package = "mp.code", class = "BufferController", ptr)]
pub extern "system" fn Java_mp_code_BufferController_recv( fn recv(controller: &mut crate::buffer::Controller) -> Result<TextChange, ControllerError> {
mut env: JNIEnv, super::tokio().block_on(controller.recv())
_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()
} }
/// Receive from Java, converts and sends a [TextChange]. /// Send a [TextChange] to the server.
#[no_mangle] #[jni(package = "mp.code", class = "BufferController")]
pub extern "system" fn Java_mp_code_BufferController_send<'local>( fn send(controller: &mut crate::buffer::Controller, change: TextChange) -> Result<(), ControllerError> {
mut env: JNIEnv<'local>, super::tokio().block_on(controller.send(change))
_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, {});
}
} }
/// Registers a callback for buffer changes. /// Register a callback for buffer changes.
#[no_mangle] #[jni(package = "mp.code", class = "BufferController")]
pub extern "system" fn Java_mp_code_BufferController_callback<'local>( fn callback<'local>(env: &mut JNIEnv<'local>, controller: &mut crate::buffer::Controller, cb: JObject<'local>) {
mut env: JNIEnv,
_class: JClass<'local>,
self_ptr: jlong,
cb: JObject<'local>,
) {
null_check!(env, cb, {}); 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 { let Ok(cb_ref) = env.new_global_ref(cb) else {
env.throw_new("mp/code/exceptions/JNIException", "Failed to pin callback reference!") env.throw_new("mp/code/exceptions/JNIException", "Failed to pin callback reference!")
.expect("Failed to throw exception!"); .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() let mut env = jvm.attach_current_thread_permanently()
.expect("failed attaching to main JVM thread"); .expect("failed attaching to main JVM thread");
if let Err(e) = env.with_local_frame(5, |env| { if let Err(e) = env.with_local_frame(5, |env| {
use crate::ffi::java::JObjectify; use jni_toolbox::IntoJavaObject;
let jcontroller = controller.jobjectify(env)?; let jcontroller = controller.into_java(env)?;
if let Err(e) = env.call_method( if let Err(e) = env.call_method(
&cb_ref, &cb_ref,
"accept", "accept",
@ -119,46 +68,26 @@ pub extern "system" fn Java_mp_code_BufferController_callback<'local>(
}); });
} }
/// Clears the callback for buffer changes. /// Clear the callback for buffer changes.
#[no_mangle] #[jni(package = "mp.code", class = "BufferController")]
pub extern "system" fn Java_mp_code_BufferController_clear_1callback( fn clear_callback(controller: &mut crate::buffer::Controller) {
_env: JNIEnv, controller.clear_callback()
_class: JClass,
self_ptr: jlong,
) {
unsafe { Box::leak(Box::from_raw(self_ptr as *mut crate::buffer::Controller)) }
.clear_callback();
} }
/// Blocks until there is a new value available. /// Block until there is a new value available.
#[no_mangle] #[jni(package = "mp.code", class = "BufferController")]
pub extern "system" fn Java_mp_code_BufferController_poll( fn poll(controller: &mut crate::buffer::Controller) -> Result<(), ControllerError> {
mut env: JNIEnv, super::tokio().block_on(controller.poll())
_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);
} }
/// Stops the controller. /// Stop the controller.
#[no_mangle] #[jni(package = "mp.code", class = "BufferController")]
pub extern "system" fn Java_mp_code_BufferController_stop( fn stop(controller: &mut crate::buffer::Controller) -> bool {
_env: JNIEnv, controller.stop()
_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
} }
/// Called by the Java GC to drop a [crate::buffer::Controller]. /// Called by the Java GC to drop a [crate::buffer::Controller].
#[no_mangle] #[jni(package = "mp.code", class = "BufferController")]
pub extern "system" fn Java_mp_code_BufferController_free( fn free(input: jni::sys::jlong) {
_env: JNIEnv, let _ = unsafe { Box::from_raw(input as *mut crate::buffer::Controller) };
_class: JClass,
self_ptr: jlong,
) {
let _ = unsafe { Box::from_raw(self_ptr as *mut crate::buffer::Controller) };
} }

View file

@ -1,144 +1,74 @@
use jni::{objects::{JClass, JObject, JString}, sys::{jboolean, jlong, jobject, jobjectArray}, JNIEnv}; use jni_toolbox::jni;
use jni_toolbox::{jni, FromJava, IntoJava, JniToolboxError}; use crate::{api::Config, client::Client, errors::{ConnectionError, RemoteError}, Workspace};
use crate::{api::Config, client::Client, errors::{ConnectionError, RemoteError}, ffi::java::{handle_error, null_check}, Workspace};
use super::{Deobjectify, JExceptable, JObjectify, tokio};
impl<'j> IntoJava<'j> for Client {
type T = jobject;
fn into_java(self, env: &mut jni::JNIEnv<'j>) -> Result<Self::T, jni::errors::Error> {
// Ok(Box::into_raw(Box::new(self)))
todo!()
}
}
impl<'j> FromJava<'j> for Client {
type T = jobject;
fn from_java(env: &mut jni::JNIEnv<'j>, value: Self::T) -> Result<Self, jni::errors::Error> {
let x = unsafe { Box::leak(Box::from_raw(value as *mut Client)) };
todo!();
Ok(x.clone())
}
}
impl<'j> FromJava<'j> for Config {
type T = JObject<'j>;
fn from_java(env: &mut jni::JNIEnv<'j>, value: Self::T) -> Result<Self, jni::errors::Error> {
Ok(Config::deobjectify(env, value)?)
}
}
impl<'j> IntoJava<'j> for crate::api::User {
type T = jobject;
fn into_java(self, env: &mut jni::JNIEnv<'j>) -> Result<Self::T, jni::errors::Error> {
Ok(self.jobjectify(env)?.into_raw())
}
}
impl<'j> IntoJava<'j> for Workspace {
type T = jobject;
fn into_java(self, env: &mut jni::JNIEnv<'j>) -> Result<Self::T, jni::errors::Error> {
Ok(self.jobjectify(env)?.into_raw())
}
}
impl JniToolboxError for ConnectionError {
fn jclass(&self) -> String { // TODO pick class based on underlying type
"mp/code/exceptions/ConnectionRemoteException".to_string()
}
}
impl JniToolboxError for RemoteError {
fn jclass(&self) -> String { // TODO pick class based on underlying type
"mp/code/exceptions/ConnectionRemoteException".to_string()
}
}
/// Connect using the given credentials to the default server, and return a [Client] to interact with it.
#[jni(package = "mp.code", class = "Client", ptr)] #[jni(package = "mp.code", class = "Client", ptr)]
fn connect(config: Config) -> Result<Client, ConnectionError> { fn connect(config: Config) -> Result<Client, ConnectionError> {
tokio().block_on(Client::connect(config)) super::tokio().block_on(Client::connect(config))
}
fn asd(arg: String) -> Result<Vec<String>, String> {
Ok(arg.split('/').map(|x| x.to_string()).collect())
} }
/// Gets the current [crate::api::User]. /// Gets the current [crate::api::User].
#[jni(package = "mp.code", class = "Client", ptr)] #[jni(package = "mp.code", class = "Client", ptr)]
fn get_user(client: Client) -> crate::api::User { fn get_user(client: &mut Client) -> crate::api::User {
client.user().clone() client.user().clone()
} }
/// Join a [Workspace] and return a pointer to it. /// Join a [Workspace] and return a pointer to it.
#[jni(package = "mp.code", class = "Client", ptr)] #[jni(package = "mp.code", class = "Client", ptr)]
fn join_workspace(client: Client, workspace: String) -> Result<Workspace, ConnectionError> { fn join_workspace(client: &mut Client, workspace: String) -> Result<Workspace, ConnectionError> {
tokio().block_on(client.join_workspace(workspace)) super::tokio().block_on(client.join_workspace(workspace))
} }
/// Create a workspace on server, if allowed to.
#[jni(package = "mp.code", class = "Client")] #[jni(package = "mp.code", class = "Client")]
fn create_workspace(client: Client, workspace: String) -> Result<(), RemoteError> { fn create_workspace(client: &mut Client, workspace: String) -> Result<(), RemoteError> {
tokio().block_on(client.create_workspace(workspace)) super::tokio().block_on(client.create_workspace(workspace))
} }
/// Delete a workspace on server, if allowed to. /// Delete a workspace on server, if allowed to.
#[jni(package = "mp.code", class = "Client")] #[jni(package = "mp.code", class = "Client")]
fn delete_workspace(client: Client, workspace: String) -> Result<(), RemoteError> { fn delete_workspace(client: &mut Client, workspace: String) -> Result<(), RemoteError> {
tokio().block_on(client.delete_workspace(workspace)) super::tokio().block_on(client.delete_workspace(workspace))
} }
/// Invite another user to an owned workspace. /// Invite another user to an owned workspace.
#[jni(package = "mp.code", class = "Client")] #[jni(package = "mp.code", class = "Client")]
fn invite_to_workspace(client: Client, workspace: String, user: String) -> Result<(), RemoteError> { fn invite_to_workspace(client: &mut Client, workspace: String, user: String) -> Result<(), RemoteError> {
tokio().block_on(client.invite_to_workspace(workspace, user)) super::tokio().block_on(client.invite_to_workspace(workspace, user))
} }
/// List available workspaces. /// List available workspaces.
#[jni(package = "mp.code", class = "Client", ptr)] #[jni(package = "mp.code", class = "Client", ptr)]
fn list_workspaces(client: Client, owned: bool, invited: bool) -> Result<Vec<String>, RemoteError> { fn list_workspaces(client: &mut Client, owned: bool, invited: bool) -> Result<Vec<String>, RemoteError> {
tokio().block_on(client.list_workspaces(owned, invited)) super::tokio().block_on(client.list_workspaces(owned, invited))
} }
/// List available workspaces. /// List available workspaces.
#[jni(package = "mp.code", class = "Client", ptr)] #[jni(package = "mp.code", class = "Client", ptr)]
fn active_workspaces(client: Client) -> Vec<String> { fn active_workspaces(client: &mut Client) -> Vec<String> {
client.active_workspaces() client.active_workspaces()
} }
/// Leave a [Workspace] and return whether or not the client was in such workspace. /// Leave a [Workspace] and return whether or not the client was in such workspace.
#[jni(package = "mp.code", class = "Client")] #[jni(package = "mp.code", class = "Client")]
fn leave_workspace(client: Client, workspace: String) -> bool { fn leave_workspace(client: &mut Client, workspace: String) -> bool {
client.leave_workspace(&workspace) client.leave_workspace(&workspace)
} }
/// Get a [Workspace] by name and returns a pointer to it. /// Get a [Workspace] by name and returns a pointer to it.
#[jni(package = "mp.code", class = "Client", ptr)] #[jni(package = "mp.code", class = "Client", ptr)]
fn get_workspace(client: Client, workspace: String) -> Option<Workspace> { fn get_workspace(client: &mut Client, workspace: String) -> Option<Workspace> {
client.get_workspace(&workspace) client.get_workspace(&workspace)
} }
/// Refresh the client's session token. /// Refresh the client's session token.
#[jni(package = "mp.code", class = "Client")] #[jni(package = "mp.code", class = "Client")]
fn refresh(client: Client) -> Result<(), RemoteError> { fn refresh(client: &mut Client) -> Result<(), RemoteError> {
tokio().block_on(client.refresh()) super::tokio().block_on(client.refresh())
} }
/// Called by the Java GC to drop a [Client]. /// Called by the Java GC to drop a [Client].
#[no_mangle] #[jni(package = "mp.code", class = "Client")]
pub extern "system" fn Java_mp_code_Client_free(_env: JNIEnv, _class: JClass, input: jlong) { fn free(input: jni::sys::jlong) {
let _ = unsafe { Box::from_raw(input as *mut Client) }; let _ = unsafe { Box::from_raw(input as *mut Client) };
} }
// TODO: this stays until we get rid of the arc then i'll have to find a better way
fn spawn_updater(workspace: Workspace) -> Workspace {
let w = workspace.clone();
tokio().spawn(async move {
loop {
tokio::time::sleep(std::time::Duration::from_secs(60)).await;
w.fetch_buffers().await.unwrap();
w.fetch_users().await.unwrap();
}
});
workspace
}

View file

@ -1,66 +1,31 @@
use jni::{objects::{JClass, JObject}, sys::{jboolean, jlong, jobject}, JNIEnv}; use jni::{objects::JObject, JNIEnv};
use crate::api::{Controller, Cursor}; 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. /// Try to fetch a [Cursor], or returns null if there's nothing.
#[no_mangle] #[jni(package = "mp.code", class = "CursorController", ptr)]
pub extern "system" fn Java_mp_code_CursorController_try_1recv( fn try_recv(controller: &mut crate::cursor::Controller) -> Result<Option<Cursor>, ControllerError> {
mut env: JNIEnv, super::tokio().block_on(controller.try_recv())
_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)
} }
/// Block until it receives a [Cursor]. /// Block until it receives a [Cursor].
#[no_mangle] #[jni(package = "mp.code", class = "CursorController", ptr)]
pub extern "system" fn Java_mp_code_CursorController_recv( fn recv(controller: &mut crate::cursor::Controller) -> Result<Cursor, ControllerError> {
mut env: JNIEnv, super::tokio().block_on(controller.recv())
_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()
} }
/// Receive from Java, converts and sends a [Cursor]. /// Receive from Java, converts and sends a [Cursor].
#[no_mangle] #[jni(package = "mp.code", class = "CursorController")]
pub extern "system" fn Java_mp_code_CursorController_send<'local>( fn send(controller: &mut crate::cursor::Controller, cursor: Cursor) -> Result<(), ControllerError> {
mut env: JNIEnv<'local>, super::tokio().block_on(controller.send(cursor))
_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, {});
}
} }
/// Registers a callback for cursor changes. /// Register a callback for cursor changes.
#[no_mangle] #[jni(package = "mp.code", class = "CursorController")]
pub extern "system" fn Java_mp_code_CursorController_callback<'local>( fn callback<'local>(env: &mut JNIEnv<'local>, controller: &mut crate::cursor::Controller, cb: JObject<'local>) {
mut env: JNIEnv, null_check!(env, cb, {});
_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)) };
let Ok(cb_ref) = env.new_global_ref(cb) else { let Ok(cb_ref) = env.new_global_ref(cb) else {
env.throw_new("mp/code/exceptions/JNIException", "Failed to pin callback reference!") env.throw_new("mp/code/exceptions/JNIException", "Failed to pin callback reference!")
.expect("Failed to throw exception!"); .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() let mut env = jvm.attach_current_thread_permanently()
.expect("failed attaching to main JVM thread"); .expect("failed attaching to main JVM thread");
if let Err(e) = env.with_local_frame(5, |env| { if let Err(e) = env.with_local_frame(5, |env| {
use crate::ffi::java::JObjectify; use jni_toolbox::IntoJavaObject;
let jcontroller = controller.jobjectify(env)?; let jcontroller = controller.into_java(env)?;
if let Err(e) = env.call_method( if let Err(e) = env.call_method(
&cb_ref, &cb_ref,
"accept", "accept",
@ -90,46 +55,26 @@ pub extern "system" fn Java_mp_code_CursorController_callback<'local>(
}); });
} }
/// Clears the callback for cursor changes. /// Clear the callback for cursor changes.
#[no_mangle] #[jni(package = "mp.code", class = "CursorController")]
pub extern "system" fn Java_mp_code_CursorController_clear_1callback( fn clear_callback(controller: &mut crate::cursor::Controller) {
_env: JNIEnv, controller.clear_callback()
_class: JClass,
self_ptr: jlong,
) {
unsafe { Box::leak(Box::from_raw(self_ptr as *mut crate::cursor::Controller)) }
.clear_callback();
} }
/// Blocks until there is a new value available. /// Block until there is a new value available.
#[no_mangle] #[jni(package = "mp.code", class = "CursorController")]
pub extern "system" fn Java_mp_code_CursorController_poll( fn poll(controller: &mut crate::cursor::Controller) -> Result<(), ControllerError> {
mut env: JNIEnv, super::tokio().block_on(controller.poll())
_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);
} }
/// Stops the controller. /// Stop the controller.
#[no_mangle] #[jni(package = "mp.code", class = "CursorController")]
pub extern "system" fn Java_mp_code_CursorController_stop( fn stop(controller: &mut crate::cursor::Controller) -> bool {
_env: JNIEnv, controller.stop()
_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
} }
/// Called by the Java GC to drop a [crate::cursor::Controller]. /// Called by the Java GC to drop a [crate::cursor::Controller].
#[no_mangle] #[jni(package = "mp.code", class = "CursorController")]
pub extern "system" fn Java_mp_code_CursorController_free( fn free(input: jni::sys::jlong) {
_env: JNIEnv, let _ = unsafe { Box::from_raw(input as *mut crate::cursor::Controller) };
_class: JClass,
self_ptr: jlong,
) {
let _ = unsafe { Box::from_raw(self_ptr as *mut crate::cursor::Controller) };
} }

View file

@ -1,8 +1,5 @@
use jni::{objects::{JClass, JString}, JNIEnv};
use jni_toolbox::jni; use jni_toolbox::jni;
use super::{JExceptable, null_check};
/// Calculate the XXH3 hash for a given String. /// Calculate the XXH3 hash for a given String.
#[jni(package = "mp.code", class = "Extensions")] #[jni(package = "mp.code", class = "Extensions")]
fn hash(content: String) -> i64 { fn hash(content: String) -> i64 {
@ -23,6 +20,7 @@ fn drive(block: bool) {
} }
/// Set up the tracing subscriber. /// Set up the tracing subscriber.
#[allow(non_snake_case)]
#[jni(package = "mp.code", class = "Extensions")] #[jni(package = "mp.code", class = "Extensions")]
fn setupTracing(path: Option<String>, debug: bool) { fn setupTracing(path: Option<String>, debug: bool) {
super::setup_logger(debug, path); super::setup_logger(debug, path);

View file

@ -62,26 +62,9 @@ pub(crate) fn setup_logger(debug: bool, path: Option<String>) {
} }
} }
/// 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 /// 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. /// 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) => { ($env: ident, $var: ident, $return: expr) => {
if $var.is_null() { if $var.is_null() {
let mut message = stringify!($var).to_string(); let mut message = stringify!($var).to_string();
@ -94,76 +77,38 @@ macro_rules! null_check {
} }
pub(crate) use null_check; pub(crate) use null_check;
impl jni_toolbox::JniToolboxError for crate::errors::ConnectionError {
/// A trait meant for our local result type to make converting it to Java easier. fn jclass(&self) -> String {
/// jni-rs technically has [jni::errors::ToException], but this approach keeps it stream-like. match self {
pub(crate) trait JExceptable<'local, T: Default> { crate::errors::ConnectionError::Transport(_) => "mp/code/exceptions/ConnectionTransportException",
/// Unwrap it and throws an appropriate Java exception if it's an error. crate::errors::ConnectionError::Remote(_) => "mp/code/exceptions/ConnectionRemoteException"
/// Theoretically it returns the type's default value, but the exception makes the value ignored. }.to_string()
fn jexcept(self, env: &mut jni::JNIEnv<'local>) -> T;
}
impl<'local, T: Default, E: JObjectify<'local> + std::fmt::Debug> JExceptable<'local, T> for Result<T, E> {
fn jexcept(self, env: &mut jni::JNIEnv<'local>) -> T {
if let Ok(res) = self {
res
} else {
handle_error!(env, self, Default::default());
}
} }
} }
/// Allows easy conversion for various types into Java objects. impl jni_toolbox::JniToolboxError for crate::errors::RemoteError {
/// This is similar to [TryInto], but for Java types. fn jclass(&self) -> String {
pub(crate) trait JObjectify<'local> { "mp/code/exceptions/ConnectionRemoteException".to_string()
/// Attempt to convert the given object to a [jni::objects::JObject]. }
fn jobjectify(self, env: &mut jni::JNIEnv<'local>) -> Result<jni::objects::JObject<'local>, jni::errors::Error>;
} }
macro_rules! jobjectify_error { impl jni_toolbox::JniToolboxError for crate::errors::ControllerError {
($self: ident, $type: ty, $jclass: expr) => { fn jclass(&self) -> String {
impl<'local> JObjectify<'local> for $type { match self {
fn jobjectify($self, env: &mut jni::JNIEnv<'local>) -> Result<jni::objects::JObject<'local>, jni::errors::Error> { crate::errors::ControllerError::Stopped => "mp/code/exceptions/ControllerStoppedException",
let class = env.find_class($jclass)?; crate::errors::ControllerError::Unfulfilled => "mp/code/exceptions/ControllerUnfulfilledException",
let msg = env.new_string(format!("{:#?}", $self))?; }.to_string()
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::objects::JObject<'local>, 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)])
} }
} }
/// Generates a [JObjectify] implementation for a class that is just a holder for a pointer. /// 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) => { ($type: ty, $jclass: literal) => {
impl<'local> JObjectify<'local> for $type { impl<'j> jni_toolbox::IntoJavaObject<'j> for $type {
fn jobjectify(self, env: &mut jni::JNIEnv<'local>) -> Result<jni::objects::JObject<'local>, jni::errors::Error> { type T = jni::objects::JObject<'j>;
let class = env.find_class($jclass)?; const CLASS: &'static str = $jclass;
fn into_java(self, env: &mut jni::JNIEnv<'j>) -> Result<Self::T, jni::errors::Error> {
let class = env.find_class(Self::CLASS)?;
env.new_object( env.new_object(
class, class,
"(J)V", "(J)V",
@ -174,16 +119,18 @@ macro_rules! jobjectify_ptr_class {
}; };
} }
jobjectify_ptr_class!(crate::Client, "mp/code/Client"); into_java_ptr_class!(crate::Client, "mp/code/Client");
jobjectify_ptr_class!(crate::Workspace, "mp/code/Workspace"); into_java_ptr_class!(crate::Workspace, "mp/code/Workspace");
jobjectify_ptr_class!(crate::cursor::Controller, "mp/code/CursorController"); into_java_ptr_class!(crate::cursor::Controller, "mp/code/CursorController");
jobjectify_ptr_class!(crate::buffer::Controller, "mp/code/BufferController"); into_java_ptr_class!(crate::buffer::Controller, "mp/code/BufferController");
impl<'local> JObjectify<'local> for crate::api::User { impl<'j> jni_toolbox::IntoJavaObject<'j> for crate::api::User {
fn jobjectify(self, env: &mut jni::JNIEnv<'local>) -> Result<jni::objects::JObject<'local>, jni::errors::Error> { type T = jni::objects::JObject<'j>;
let id_field = self.id.jobjectify(env)?; const CLASS: &'static str = "mp/code/data/User";
fn into_java(self, env: &mut jni::JNIEnv<'j>) -> Result<Self::T, jni::errors::Error> {
let id_field = self.id.into_java(env)?;
let name_field = env.new_string(self.name)?; 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( env.new_object(
&class, &class,
"(Ljava/util/UUID;Ljava/lang/String;)V", "(Ljava/util/UUID;Ljava/lang/String;)V",
@ -195,8 +142,10 @@ impl<'local> JObjectify<'local> for crate::api::User {
} }
} }
impl<'local> JObjectify<'local> for crate::api::Event { impl<'j> jni_toolbox::IntoJavaObject<'j> for crate::api::Event {
fn jobjectify(self, env: &mut jni::JNIEnv<'local>) -> Result<jni::objects::JObject<'local>, jni::errors::Error> { type T = jni::objects::JObject<'j>;
const CLASS: &'static str = "mp/code/Workspace$Event";
fn into_java(self, env: &mut jni::JNIEnv<'j>) -> Result<Self::T, jni::errors::Error> {
let (ordinal, arg) = match self { let (ordinal, arg) = match self {
crate::api::Event::UserJoin(arg) => (0, env.new_string(arg)?), crate::api::Event::UserJoin(arg) => (0, env.new_string(arg)?),
crate::api::Event::UserLeave(arg) => (1, env.new_string(arg)?), crate::api::Event::UserLeave(arg) => (1, env.new_string(arg)?),
@ -212,7 +161,7 @@ impl<'local> JObjectify<'local> for crate::api::Event {
)?.l()?.into(); )?.l()?.into();
let event_type = env.get_object_array_element(variants, ordinal)?; 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( env.new_object(
event_class, event_class,
"(Lmp/code/Workspace$Event$Type;Ljava/lang/String;)V", "(Lmp/code/Workspace$Event$Type;Ljava/lang/String;)V",
@ -224,15 +173,17 @@ impl<'local> JObjectify<'local> for crate::api::Event {
} }
} }
impl<'local> JObjectify<'local> for crate::workspace::DetachResult { impl<'j> jni_toolbox::IntoJavaObject<'j> for crate::workspace::DetachResult {
fn jobjectify(self, env: &mut jni::JNIEnv<'local>) -> Result<jni::objects::JObject<'local>, jni::errors::Error> { type T = jni::objects::JObject<'j>;
const CLASS: &'static str = "mp/code/data/DetachResult";
fn into_java(self, env: &mut jni::JNIEnv<'j>) -> Result<Self::T, jni::errors::Error> {
let ordinal = match self { let ordinal = match self {
crate::workspace::DetachResult::NotAttached => 0, crate::workspace::DetachResult::NotAttached => 0,
crate::workspace::DetachResult::Detaching => 1, crate::workspace::DetachResult::Detaching => 1,
crate::workspace::DetachResult::AlreadyDetached => 2 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( let variants: jni::objects::JObjectArray = env.call_method(
class, class,
"getEnumConstants", "getEnumConstants",
@ -243,66 +194,79 @@ impl<'local> JObjectify<'local> for crate::workspace::DetachResult {
} }
} }
impl<'local> JObjectify<'local> for crate::api::TextChange { impl<'j> jni_toolbox::IntoJavaObject<'j> for crate::api::TextChange {
fn jobjectify(self, env: &mut jni::JNIEnv<'local>) -> Result<jni::objects::JObject<'local>, jni::errors::Error> { type T = jni::objects::JObject<'j>;
const CLASS: &'static str = "mp/code/data/TextChange";
fn into_java(self, env: &mut jni::JNIEnv<'j>) -> Result<Self::T, jni::errors::Error> {
let content = env.new_string(self.content)?; let content = env.new_string(self.content)?;
let hash = env.find_class("java/util/OptionalLong").and_then(|class| { let hash_class = env.find_class("java/util/OptionalLong")?;
if let Some(h) = self.hash { let hash = if let Some(h) = self.hash {
env.call_static_method(class, "of", "(J)Ljava/util/OptionalLong;", &[jni::objects::JValueGen::Long(h)]) env.call_static_method(hash_class, "of", "(J)Ljava/util/OptionalLong;", &[jni::objects::JValueGen::Long(h)])
} else { } else {
env.call_static_method(class, "empty", "()Ljava/util/OptionalLong;", &[]) env.call_static_method(hash_class, "empty", "()Ljava/util/OptionalLong;", &[])
}?.l()?;
let class = env.find_class(Self::CLASS)?;
env.new_object(
class,
"(JJLjava/lang/String;Ljava/util/OptionalLong;)V",
&[
jni::objects::JValueGen::Long(self.start.into()),
jni::objects::JValueGen::Long(self.end.into()),
jni::objects::JValueGen::Object(&content),
jni::objects::JValueGen::Object(&hash)
]
)
}
}
impl<'j> jni_toolbox::IntoJavaObject<'j> for crate::api::Cursor {
type T = jni::objects::JObject<'j>;
const CLASS: &'static str = "mp/code/data/Cursor";
fn into_java(self, env: &mut jni::JNIEnv<'j>) -> Result<Self::T, 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 T = jni::sys::jobject;
fn from_java(_env: &mut jni::JNIEnv<'j>, value: Self::T) -> Result<Self, jni::errors::Error> {
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 { from_java_ptr!(crate::Client);
fn jobjectify(self, env: &mut jni::JNIEnv<'local>) -> Result<jni::objects::JObject<'local>, jni::errors::Error> { from_java_ptr!(crate::Workspace);
env.find_class("mp/code/data/Cursor").and_then(|class| { from_java_ptr!(crate::cursor::Controller);
let buffer = env.new_string(&self.buffer)?; from_java_ptr!(crate::buffer::Controller);
let user = if let Some(user) = self.user {
env.new_string(user)?.into()
} else {
jni::objects::JObject::null()
};
env.new_object( impl<'j> jni_toolbox::FromJava<'j> for crate::api::Config {
class, type T = jni::objects::JObject<'j>;
"(IIIILjava/lang/String;Ljava/lang/String;)V", fn from_java(env: &mut jni::JNIEnv<'j>, config: Self::T) -> Result<Self, jni::errors::Error> {
&[
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<T, jni::errors::Error>;
}
impl<'local> Deobjectify<'local, Self> for crate::api::Config {
fn deobjectify(env: &mut jni::JNIEnv<'local>, config: jni::objects::JObject<'local>) -> Result<Self, jni::errors::Error> {
let username = { let username = {
let jfield = env.get_field(&config, "username", "Ljava/lang/String;")?.l()?; let jfield = env.get_field(&config, "username", "Ljava/lang/String;")?.l()?;
if jfield.is_null() { if jfield.is_null() {
@ -359,8 +323,9 @@ impl<'local> Deobjectify<'local, Self> for crate::api::Config {
} }
} }
impl<'local> Deobjectify<'local, Self> for crate::api::Cursor { impl<'j> jni_toolbox::FromJava<'j> for crate::api::Cursor {
fn deobjectify(env: &mut jni::JNIEnv<'local>, cursor: jni::objects::JObject<'local>) -> Result<Self, jni::errors::Error> { type T = jni::objects::JObject<'j>;
fn from_java(env: &mut jni::JNIEnv<'j>, cursor: Self::T) -> Result<Self, jni::errors::Error> {
let start_row = env.get_field(&cursor, "startRow", "I")?.i()?; let start_row = env.get_field(&cursor, "startRow", "I")?.i()?;
let start_col = env.get_field(&cursor, "startCol", "I")?.i()?; let start_col = env.get_field(&cursor, "startCol", "I")?.i()?;
let end_row = env.get_field(&cursor, "endRow", "I")?.i()?; let end_row = env.get_field(&cursor, "endRow", "I")?.i()?;
@ -387,8 +352,9 @@ impl<'local> Deobjectify<'local, Self> for crate::api::Cursor {
} }
} }
impl<'local> Deobjectify<'local, Self> for crate::api::TextChange { impl<'j> jni_toolbox::FromJava<'j> for crate::api::TextChange {
fn deobjectify(env: &mut jni::JNIEnv<'local>, change: jni::objects::JObject<'local>) -> Result<Self, jni::errors::Error> { type T = jni::objects::JObject<'j>;
fn from_java(env: &mut jni::JNIEnv<'j>, change: Self::T) -> Result<Self, jni::errors::Error> {
let start = env.get_field(&change, "start", "J")?.j()?.clamp(0, u32::MAX.into()) as u32; 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; let end = env.get_field(&change, "end", "J")?.j()?.clamp(0, u32::MAX.into()) as u32;

View file

@ -1,240 +1,86 @@
use jni::{objects::{JClass, JObject, JString}, sys::{jboolean, jlong, jobject, jobjectArray, jstring}, JNIEnv};
use jni_toolbox::jni; use jni_toolbox::jni;
use crate::Workspace; use crate::{errors::{ConnectionError, ControllerError, RemoteError}, Workspace};
use super::{handle_error, null_check, JExceptable, JObjectify};
/// Get the workspace id. /// Get the workspace id.
#[no_mangle] #[jni(package = "mp.code", class = "Workspace", ptr)]
pub extern "system" fn Java_mp_code_Workspace_get_1workspace_1id<'local>( fn get_workspace_id(workspace: &mut Workspace) -> String {
mut env: JNIEnv<'local>, workspace.id()
_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()
} }
/// Get a cursor controller by name and returns a pointer to it. /// Get a cursor controller by name and returns a pointer to it.
#[no_mangle] #[jni(package = "mp.code", class = "Workspace", ptr)]
pub extern "system" fn Java_mp_code_Workspace_get_1cursor<'local>( fn get_cursor(workspace: &mut Workspace) -> crate::cursor::Controller {
mut env: JNIEnv<'local>, workspace.cursor()
_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()
} }
/// Get a buffer controller by name and returns a pointer to it. /// Get a buffer controller by name and returns a pointer to it.
#[no_mangle] #[jni(package = "mp.code", class = "Workspace", ptr)]
pub extern "system" fn Java_mp_code_Workspace_get_1buffer<'local>( fn get_buffer(workspace: &mut Workspace, path: String) -> Option<crate::buffer::Controller> {
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);
workspace.buffer_by_name(&path) workspace.buffer_by_name(&path)
.map(|buf| buf.jobjectify(&mut env).jexcept(&mut env))
.unwrap_or_default()
.as_raw()
} }
/// Get the filetree. /// Get the filetree.
#[jni(package = "mp.code", class = "Workspace", ptr)] #[jni(package = "mp.code", class = "Workspace", ptr)]
fn file_tree( fn get_file_tree(workspace: &mut Workspace, filter: Option<String>, strict: bool) -> Vec<String> {
mut env: JNIEnv, workspace.filetree(filter.as_deref(), strict)
_class: JClass,
self_ptr: jlong,
filter: JString,
strict: jboolean
) -> Result<jobjectArray, jni::errors::Error> {
let workspace = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Workspace)) };
let filter: Option<String> = if filter.is_null() {
None
} else {
Some(env.get_string(&filter)?.into())
};
let file_tree = workspace.filetree(filter.as_deref(), strict != 0);
let class = env.find_class("java/lang/String")?;
let array = env.new_object_array(file_tree.len() as i32, class, JObject::null())?;
for (idx, path) in file_tree.iter().enumerate() {
let element = env.new_string(path)?;
env.set_object_array_element(&array, idx as i32, element)?;
}
Ok(array.as_raw())
} }
/// Gets a list of the active buffers. /// Gets a list of the active buffers.
#[no_mangle] #[jni(package = "mp.code", class = "Workspace", ptr)]
pub extern "system" fn Java_mp_code_Workspace_active_1buffers( fn active_buffers(workspace: &mut Workspace) -> Vec<String> {
mut env: JNIEnv, workspace.buffer_list()
_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()
} }
/// Create a new buffer. /// Create a new buffer.
#[no_mangle] #[jni(package = "mp.code", class = "Workspace")]
pub extern "system" fn Java_mp_code_Workspace_create_1buffer<'local>( fn create_buffer(workspace: &mut Workspace, path: String) -> Result<(), RemoteError> {
mut env: JNIEnv, super::tokio().block_on(workspace.create(&path))
_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);
} }
/// Attach to a buffer and return a pointer to its [crate::buffer::Controller]. /// Attach to a buffer and return a pointer to its [crate::buffer::Controller].
#[no_mangle] #[jni(package = "mp.code", class = "Workspace", ptr)]
pub extern "system" fn Java_mp_code_Workspace_attach_1to_1buffer<'local>( fn attach_to_buffer(workspace: &mut Workspace, path: String) -> Result<crate::buffer::Controller, ConnectionError> {
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);
super::tokio().block_on(workspace.attach(&path)) 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. /// Detach from a buffer.
#[no_mangle] #[jni(package = "mp.code", class = "Workspace", ptr)]
pub extern "system" fn Java_mp_code_Workspace_detach_1from_1buffer<'local>( fn detach_from_buffer(workspace: &mut Workspace, path: String) -> crate::workspace::DetachResult {
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);
workspace.detach(&path) workspace.detach(&path)
.jobjectify(&mut env)
.jexcept(&mut env)
.as_raw()
} }
/// Update the local buffer list. /// Update the local buffer list.
#[no_mangle] #[jni(package = "mp.code", class = "Workspace")]
pub extern "system" fn Java_mp_code_Workspace_fetch_1buffers( fn fetch_buffers(workspace: &mut Workspace) -> Result<(), RemoteError> {
mut env: JNIEnv, super::tokio().block_on(workspace.fetch_buffers())
_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);
} }
/// Update the local user list. /// Update the local user list.
#[no_mangle] #[jni(package = "mp.code", class = "Workspace")]
pub extern "system" fn Java_mp_code_Workspace_fetch_1users( fn fetch_users(workspace: &mut Workspace) -> Result<(), RemoteError> {
mut env: JNIEnv, super::tokio().block_on(workspace.fetch_users())
_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);
} }
/// List users attached to a buffer. /// List users attached to a buffer.
#[no_mangle] #[jni(package = "mp.code", class = "Workspace", ptr)]
pub extern "system" fn Java_mp_code_Workspace_list_1buffer_1users<'local>( fn list_buffer_users(workspace: &mut Workspace, path: String) -> Result<Vec<crate::api::User>, RemoteError> {
mut env: JNIEnv, super::tokio().block_on(workspace.list_buffer_users(&path))
_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()
} }
/// Delete a buffer. /// Delete a buffer.
#[no_mangle] #[jni(package = "mp.code", class = "Workspace")]
pub extern "system" fn Java_mp_code_Workspace_delete_1buffer<'local>( fn delete_buffer(workspace: &mut Workspace, path: String) -> Result<(), RemoteError> {
mut env: JNIEnv, super::tokio().block_on(workspace.delete(&path))
_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);
} }
/// Receive a workspace event if present. /// Receive a workspace event if present.
#[no_mangle] #[jni(package = "mp.code", class = "Workspace", ptr)]
pub extern "system" fn Java_mp_code_Workspace_event( fn event(workspace: &mut Workspace) -> Result<crate::api::Event, ControllerError> {
mut env: JNIEnv, super::tokio().block_on(workspace.event())
_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())
}
} }
/// Called by the Java GC to drop a [Workspace]. /// Called by the Java GC to drop a [Workspace].
#[no_mangle] #[jni(package = "mp.code", class = "Workspace")]
pub extern "system" fn Java_mp_code_Workspace_free(_env: JNIEnv, _class: JClass, input: jlong) { fn free(input: jni::sys::jlong) {
let _ = unsafe { Box::from_raw(input as *mut Workspace) }; let _ = unsafe { Box::from_raw(input as *mut crate::Workspace) };
} }