use ::jni::{errors::Result, objects::{JObject, JClass}, JNIEnv}; use std::sync::{Arc, Mutex}; macro_rules! define_fn_adapter { ( fn_once: $fo:ident, fn_once_local: $fol:ident, fn_once_internal: $foi:ident, fn_mut: $fm:ident, fn_mut_local: $fml:ident, fn_mut_internal: $fmi:ident, fn: $f:ident, fn_local: $fl:ident, fn_internal: $fi:ident, impl_class: $ic:literal, doc_class: $dc:literal, doc_method: $dm:literal, doc_fn_once: $dfo:literal, doc_fn: $df:literal, doc_noop: $dnoop:literal, signature: $closure_name:ident: impl for<'c, 'd> Fn$args:tt -> $ret:ty, closure: $closure:expr, ) => { fn $foi<'a: 'b, 'b>( env: &'b JNIEnv<'a>, $closure_name: impl for<'c, 'd> FnOnce$args -> $ret + 'static, local: bool, ) -> Result> { let adapter = env.auto_local(fn_once_adapter(env, $closure, local)?); env.new_object( JClass::from(crate::classcache::get_class($ic).unwrap().as_obj()), "(Lio/github/gedgygedgy/rust/ops/FnAdapter;)V", &[(&adapter).into()], ) } #[doc = "Create an `"] #[doc = $dc] #[doc = "` from a given [`FnOnce`]. The closure can later be called "] #[doc = "by calling the object's `"] #[doc = $dm] #[doc = "` method. The closure can be freed without calling it by "] #[doc = "calling the object's `close()` method."] #[doc = "\n\n"] #[doc = "If the closure panics, the unwind will be caught and thrown "] #[doc = "as an `io.github.gedgygedgy.rust.panic.PanicException`."] #[doc = "\n\n"] #[doc = "It is safe to call the object's `"] #[doc = $dm] #[doc = "` method recursively, but the second call will "] #[doc = $dnoop] #[doc = "."] pub fn $fo<'a: 'b, 'b>( env: &'b JNIEnv<'a>, f: impl for<'c, 'd> FnOnce$args -> $ret + Send + 'static, ) -> Result> { $foi(env, f, false) } #[doc = "Create an `"] #[doc = $dc] #[doc = "` from a given [`FnOnce`] without checking if it is "] #[doc = "[`Send`]. Attempting to call `"] #[doc = $dm] #[doc = "` or `close()` on the resulting object from a thread other "] #[doc = "than its origin thread will result in an "] #[doc = "`io.github.gedgygedgy.rust.thread.LocalThreadException` "] #[doc = "being thrown."] pub fn $fol<'a: 'b, 'b>( env: &'b JNIEnv<'a>, f: impl for<'c, 'd> FnOnce$args -> $ret + 'static, ) -> Result> { $foi(env, f, true) } fn $fmi<'a: 'b, 'b>( env: &'b JNIEnv<'a>, mut $closure_name: impl for<'c, 'd> FnMut$args -> $ret + 'static, local: bool, ) -> Result> { let adapter = env.auto_local(fn_mut_adapter(env, $closure, local)?); env.new_object( JClass::from(crate::classcache::get_class($ic).unwrap().as_obj()), "(Lio/github/gedgygedgy/rust/ops/FnAdapter;)V", &[(&adapter).into()], ) } #[doc = "Create an `"] #[doc = $dc] #[doc = "` from a given [`FnMut`]. The closure can later be called "] #[doc = "by calling the object's `"] #[doc = $dm] #[doc = "` method. The closure can be freed without calling it by "] #[doc = "calling the object's `close()` method."] #[doc = "\n\n"] #[doc = "If the closure panics, the unwind will be caught and thrown "] #[doc = "as an `io.github.gedgygedgy.rust.panic.PanicException`."] #[doc = "\n\n"] #[doc = "Unlike [`"] #[doc = $df] #[doc = "`] and [`"] #[doc = $dfo] #[doc = "`], it is not safe to call the resulting object's `"] #[doc = $dm] #[doc = "` method recursively. The [`FnMut`] is managed with an "] #[doc = "internal [`Mutex`], so calling `"] #[doc = $dm] #[doc = "` recursively will result in a deadlock."] pub fn $fm<'a: 'b, 'b>( env: &'b JNIEnv<'a>, f: impl for<'c, 'd> FnMut$args -> $ret + Send + 'static, ) -> Result> { $fmi(env, f, false) } #[doc = "Create an `"] #[doc = $dc] #[doc = "` from a given [`FnMut`] without checking if it is "] #[doc = "[`Send`]. Attempting to call `"] #[doc = $dm] #[doc = "` or `close()` on the resulting object from a thread other "] #[doc = "than its origin thread will result in an "] #[doc = "`io.github.gedgygedgy.rust.thread.LocalThreadException` "] #[doc = "being thrown."] pub fn $fml<'a: 'b, 'b>( env: &'b JNIEnv<'a>, f: impl for<'c, 'd> FnMut$args -> $ret + 'static, ) -> Result> { $fmi(env, f, true) } fn $fi<'a: 'b, 'b>( env: &'b JNIEnv<'a>, $closure_name: impl for<'c, 'd> Fn$args -> $ret + 'static, local: bool, ) -> Result> { let adapter = env.auto_local(fn_adapter(env, $closure, local)?); env.new_object( JClass::from(crate::classcache::get_class($ic).unwrap().as_obj()), "(Lio/github/gedgygedgy/rust/ops/FnAdapter;)V", &[(&adapter).into()], ) } #[doc = "Create an `"] #[doc = $dc] #[doc = "` from a given [`Fn`]. The closure can later be called by "] #[doc = "calling the object's `"] #[doc = $dm] #[doc = "` method. The closure can be freed without calling it by "] #[doc = "calling the object's `close()` method."] #[doc = "\n\n"] #[doc = "If the closure panics, the unwind will be caught and thrown "] #[doc = "as an `io.github.gedgygedgy.rust.panic.PanicException`."] #[doc = "\n\n"] #[doc = "It is safe to call the object's `"] #[doc = $dm] #[doc = "` method recursively."] pub fn $f<'a: 'b, 'b>( env: &'b JNIEnv<'a>, f: impl for<'c, 'd> Fn$args -> $ret + Send + Sync + 'static, ) -> Result> { $fi(env, f, false) } #[doc = "Create an `"] #[doc = $dc] #[doc = "` from a given [`Fn`] without checking if it is [`Send`]. "] #[doc = "Attempting to call `"] #[doc = $dm] #[doc = "` or `close()` on the resulting object from a thread other "] #[doc = "than its origin thread will result in an "] #[doc = "`io.github.gedgygedgy.rust.thread.LocalThreadException` "] #[doc = "being thrown."] pub fn $fl<'a: 'b, 'b>( env: &'b JNIEnv<'a>, f: impl for<'c, 'd> Fn$args -> $ret + 'static, ) -> Result> { $fi(env, f, true) } }; } define_fn_adapter! { fn_once: fn_once_runnable, fn_once_local: fn_once_runnable_local, fn_once_internal: fn_once_runnable_internal, fn_mut: fn_mut_runnable, fn_mut_local: fn_mut_runnable_local, fn_mut_internal: fn_mut_runnable_internal, fn: fn_runnable, fn_local: fn_runnable_local, fn_internal: fn_runnable_internal, impl_class: "io/github/gedgygedgy/rust/ops/FnRunnableImpl", doc_class: "io.github.gedgygedgy.rust.ops.FnRunnable", doc_method: "run()", doc_fn_once: "fn_once_runnable", doc_fn: "fn_runnable", doc_noop: "be a no-op", signature: f: impl for<'c, 'd> Fn(&'d JNIEnv<'c>, JObject<'c>) -> (), closure: move |env, _obj1, obj2, _arg1, _arg2| { f(env, obj2); JObject::null() }, } define_fn_adapter! { fn_once: fn_once_bi_function, fn_once_local: fn_once_bi_function_local, fn_once_internal: fn_once_bi_function_internal, fn_mut: fn_mut_bi_function, fn_mut_local: fn_mut_bi_function_local, fn_mut_internal: fn_mut_bi_function_internal, fn: fn_bi_function, fn_local: fn_bi_function_local, fn_internal: fn_bi_function_internal, impl_class: "io/github/gedgygedgy/rust/ops/FnBiFunctionImpl", doc_class: "io.github.gedgygedgy.rust.ops.FnBiFunction", doc_method: "apply()", doc_fn_once: "fn_once_bi_function", doc_fn: "fn_bi_funciton", doc_noop: "return `null`", signature: f: impl for<'c, 'd> Fn(&'d JNIEnv<'c>, JObject<'c>, JObject<'c>, JObject<'c>) -> JObject<'c>, closure: move |env, _obj1, obj2, arg1, arg2| { f(env, obj2, arg1, arg2) }, } define_fn_adapter! { fn_once: fn_once_function, fn_once_local: fn_once_function_local, fn_once_internal: fn_once_function_internal, fn_mut: fn_mut_function, fn_mut_local: fn_mut_function_local, fn_mut_internal: fn_mut_function_internal, fn: fn_function, fn_local: fn_function_local, fn_internal: fn_function_internal, impl_class: "io/github/gedgygedgy/rust/ops/FnFunctionImpl", doc_class: "io.github.gedgygedgy.rust.ops.FnFunction", doc_method: "apply()", doc_fn_once: "fn_once_function", doc_fn: "fn_function", doc_noop: "return `null`", signature: f: impl for<'c, 'd> Fn(&'d JNIEnv<'c>, JObject<'c>, JObject<'c>) -> JObject<'c>, closure: move |env, _obj1, obj2, arg1, _arg2| { f(env, obj2, arg1) }, } struct SendSyncWrapper(T); unsafe impl Send for SendSyncWrapper {} unsafe impl Sync for SendSyncWrapper {} type FnWrapper = SendSyncWrapper< Arc< dyn for<'a, 'b> Fn( &'b JNIEnv<'a>, JObject<'a>, JObject<'a>, JObject<'a>, JObject<'a>, ) -> JObject<'a> + 'static, >, >; fn fn_once_adapter<'a: 'b, 'b>( env: &'b JNIEnv<'a>, f: impl for<'c, 'd> FnOnce( &'d JNIEnv<'c>, JObject<'c>, JObject<'c>, JObject<'c>, JObject<'c>, ) -> JObject<'c> + 'static, local: bool, ) -> Result> { let mutex = Mutex::new(Some(f)); fn_adapter( env, move |env, obj1, obj2, arg1, arg2| { let f = { let mut guard = mutex.lock().unwrap(); if let Some(f) = guard.take() { f } else { return JObject::null(); } }; f(env, obj1, obj2, arg1, arg2) }, local, ) } fn fn_mut_adapter<'a: 'b, 'b>( env: &'b JNIEnv<'a>, f: impl for<'c, 'd> FnMut( &'d JNIEnv<'c>, JObject<'c>, JObject<'c>, JObject<'c>, JObject<'c>, ) -> JObject<'c> + 'static, local: bool, ) -> Result> { let mutex = Mutex::new(f); fn_adapter( env, move |env, obj1, obj2, arg1, arg2| { let mut guard = mutex.lock().unwrap(); guard(env, obj1, obj2, arg1, arg2) }, local, ) } fn fn_adapter<'a: 'b, 'b>( env: &'b JNIEnv<'a>, f: impl for<'c, 'd> Fn( &'d JNIEnv<'c>, JObject<'c>, JObject<'c>, JObject<'c>, JObject<'c>, ) -> JObject<'c> + 'static, local: bool, ) -> Result> { let arc: Arc< dyn for<'c, 'd> Fn( &'d JNIEnv<'c>, JObject<'c>, JObject<'c>, JObject<'c>, JObject<'c>, ) -> JObject<'c>, > = Arc::from(f); let obj = env.new_object( JClass::from(crate::classcache::get_class("io/github/gedgygedgy/rust/ops/FnAdapter").unwrap().as_obj()), "(Z)V", &[local.into()] )?; env.set_rust_field::<_, _, FnWrapper>(obj, "data", SendSyncWrapper(arc))?; Ok(obj) } pub(crate) mod jni { use super::FnWrapper; use jni::{errors::Result, objects::JObject, JNIEnv, NativeMethod}; extern "C" fn fn_adapter_call_internal<'a>( env: JNIEnv<'a>, obj1: JObject<'a>, obj2: JObject<'a>, arg1: JObject<'a>, arg2: JObject<'a>, ) -> JObject<'a> { use std::panic::AssertUnwindSafe; let arc = if let Ok(f) = env.get_rust_field::<_, _, FnWrapper>(obj1, "data") { AssertUnwindSafe(f.0.clone()) } else { return JObject::null(); }; crate::exceptions::throw_unwind(&env, || arc(&env, obj1, obj2, arg1, arg2)) .unwrap_or_else(|_| JObject::null()) } extern "C" fn fn_adapter_close_internal(env: JNIEnv, obj: JObject) { let _ = crate::exceptions::throw_unwind(&env, || { let _ = env.take_rust_field::<_, _, FnWrapper>(obj, "data"); }); } pub fn init(env: &JNIEnv) -> Result<()> { use std::ffi::c_void; crate::classcache::find_add_class(env, "io/github/gedgygedgy/rust/future/Future").unwrap(); crate::classcache::find_add_class(env, "io/github/gedgygedgy/rust/future/FutureException").unwrap(); crate::classcache::find_add_class(env, "io/github/gedgygedgy/rust/ops/FnAdapter").unwrap(); crate::classcache::find_add_class(env, "io/github/gedgygedgy/rust/stream/Stream").unwrap(); crate::classcache::find_add_class(env, "io/github/gedgygedgy/rust/stream/StreamPoll").unwrap(); crate::classcache::find_add_class(env, "io/github/gedgygedgy/rust/task/Waker").unwrap(); crate::classcache::find_add_class(env, "io/github/gedgygedgy/rust/task/PollResult").unwrap(); crate::classcache::find_add_class(env, "io/github/gedgygedgy/rust/ops/FnRunnableImpl").unwrap(); crate::classcache::find_add_class(env, "io/github/gedgygedgy/rust/ops/FnBiFunctionImpl").unwrap(); crate::classcache::find_add_class(env, "io/github/gedgygedgy/rust/ops/FnFunctionImpl").unwrap(); let class = env.auto_local(env.find_class("io/github/gedgygedgy/rust/ops/FnAdapter")?); env.register_native_methods( &class, &[ NativeMethod { name: "callInternal".into(), sig: "(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;" .into(), fn_ptr: fn_adapter_call_internal as *mut c_void, }, NativeMethod { name: "closeInternal".into(), sig: "()V".into(), fn_ptr: fn_adapter_close_internal as *mut c_void, }, ], )?; Ok(()) } } #[cfg(test)] mod test { use crate::{exceptions::try_block, test_utils}; use jni::{objects::JObject, JNIEnv}; use std::{ cell::RefCell, rc::Rc, sync::{Arc, Mutex}, }; fn create_test_fn() -> ( Arc>, Box Fn(&'b JNIEnv<'a>, JObject<'a>) + Send + Sync + 'static>, ) { let arc = Arc::new(Mutex::new(0)); let arc2 = arc.clone(); ( arc, Box::new(move |_e, _o| { let mut guard = arc2.lock().unwrap(); *&mut *guard += 1; }), ) } fn create_test_fn_local() -> ( Rc>, Box Fn(&'b JNIEnv<'a>, JObject<'a>) + 'static>, ) { let rc = Rc::new(RefCell::new(0)); let rc2 = rc.clone(); ( rc, Box::new(move |_e, _o| { let mut guard = rc2.try_borrow_mut().unwrap(); *&mut *guard += 1; }), ) } fn test_data(data: &Arc>, expected: u32, expected_refcount: usize) { assert_eq!(Arc::strong_count(data), expected_refcount); let guard = data.lock().unwrap(); assert_eq!(*guard, expected); } fn test_data_local(data: &Rc>, expected: u32, expected_refcount: usize) { assert_eq!(Rc::strong_count(data), expected_refcount); let guard = data.try_borrow().unwrap(); assert_eq!(*guard, expected); } struct DropPanic; impl DropPanic { pub fn keep_alive(&self) {} } impl Drop for DropPanic { fn drop(&mut self) { panic!("DropPanic dropped"); } } fn create_drop_panic_fn( ) -> Box Fn(&'b JNIEnv<'a>, JObject<'a>) + Send + Sync + 'static> { let p = DropPanic; Box::new(move |_e, _o| { p.keep_alive(); }) } #[test] fn test_drop_panic() { test_utils::JVM_ENV.with(|env| { use std::{ mem::drop, panic::{catch_unwind, AssertUnwindSafe}, }; let dp = AssertUnwindSafe(create_drop_panic_fn()); dp(env, JObject::null()); catch_unwind(|| drop(dp)).unwrap_err(); }); } #[test] fn test_fn_once_runnable_run() { test_utils::JVM_ENV.with(|env| { let (data, f) = create_test_fn(); test_data(&data, 0, 2); let runnable = super::fn_once_runnable(env, f).unwrap(); test_data(&data, 0, 2); env.call_method(runnable, "run", "()V", &[]).unwrap(); test_data(&data, 1, 1); env.call_method(runnable, "run", "()V", &[]).unwrap(); test_data(&data, 1, 1); }); } #[test] fn test_fn_once_runnable_close() { test_utils::JVM_ENV.with(|env| { let (data, f) = create_test_fn(); test_data(&data, 0, 2); let runnable = super::fn_once_runnable(env, f).unwrap(); test_data(&data, 0, 2); env.call_method(runnable, "close", "()V", &[]).unwrap(); test_data(&data, 0, 1); env.call_method(runnable, "close", "()V", &[]).unwrap(); test_data(&data, 0, 1); }); } #[test] fn test_fn_once_runnable_run_close() { test_utils::JVM_ENV.with(|env| { let (data, f) = create_test_fn(); test_data(&data, 0, 2); let runnable = super::fn_once_runnable(env, f).unwrap(); test_data(&data, 0, 2); env.call_method(runnable, "run", "()V", &[]).unwrap(); test_data(&data, 1, 1); env.call_method(runnable, "close", "()V", &[]).unwrap(); test_data(&data, 1, 1); }); } #[test] fn test_fn_once_runnable_close_run() { test_utils::JVM_ENV.with(|env| { let (data, f) = create_test_fn(); test_data(&data, 0, 2); let runnable = super::fn_once_runnable(env, f).unwrap(); test_data(&data, 0, 2); env.call_method(runnable, "close", "()V", &[]).unwrap(); test_data(&data, 0, 1); env.call_method(runnable, "run", "()V", &[]).unwrap(); test_data(&data, 0, 1); }); } #[test] fn test_fn_once_runnable_thread() { test_utils::JVM_ENV.with(|env| { let (data, f) = create_test_fn(); test_data(&data, 0, 2); let runnable = super::fn_once_runnable(env, f).unwrap(); test_data(&data, 0, 2); let runnable = env.new_global_ref(runnable).unwrap(); let thread = std::thread::spawn(move || { test_utils::JVM_ENV.with(|env| { env.call_method(runnable.as_obj(), "run", "()V", &[]) .unwrap(); }); }); thread.join().unwrap(); test_data(&data, 1, 1); }); } #[test] fn test_fn_once_runnable_object() { test_utils::JVM_ENV.with(|env| { let obj_ref = Arc::new(Mutex::new(env.new_global_ref(JObject::null()).unwrap())); let obj_ref_2 = obj_ref.clone(); let runnable = super::fn_once_runnable(env, move |e, o| { let guard = obj_ref_2.lock().unwrap(); assert!(e.is_same_object(guard.as_obj(), o).unwrap()); }) .unwrap(); { let mut guard = obj_ref.lock().unwrap(); *guard = env.new_global_ref(runnable).unwrap(); } env.call_method(runnable, "run", "()V", &[]).unwrap(); }); } #[test] fn test_fn_once_runnable_recursive() { test_utils::JVM_ENV.with(|env| { let arc = Arc::new(Mutex::new(false)); let arc2 = arc.clone(); let runnable = super::fn_once_runnable(env, move |env, obj| { let value = { let mut guard = arc2.lock().unwrap(); let old = *guard; *guard = true; old }; if !value { env.call_method(obj, "run", "()V", &[]).unwrap(); } }) .unwrap(); env.call_method(runnable, "run", "()V", &[]).unwrap(); let guard = arc.lock().unwrap(); assert!(*guard); }) } #[test] fn test_fn_once_runnable_panic() { test_utils::JVM_ENV.with(|env| { let runnable = super::fn_once_runnable(env, |_e, _o| panic!("This is a panic")).unwrap(); let result = try_block(env, || { env.call_method(runnable, "run", "()V", &[])?; Ok(false) }) .catch("io/github/gedgygedgy/rust/panic/PanicException", |ex| { let ex = crate::exceptions::JPanicException::from_env(env, ex).unwrap(); let any = ex.take().unwrap(); let str = any.downcast::<&str>().unwrap(); assert_eq!(*str, "This is a panic"); Ok(true) }) .result() .unwrap(); assert!(result); }); } #[test] fn test_fn_once_runnable_close_self() { test_utils::JVM_ENV.with(|env| { let (data, f) = create_test_fn(); test_data(&data, 0, 2); let runnable = super::fn_once_runnable(env, move |env, obj| { env.call_method(obj, "close", "()V", &[]).unwrap(); f(env, obj); }) .unwrap(); test_data(&data, 0, 2); env.call_method(runnable, "run", "()V", &[]).unwrap(); test_data(&data, 1, 1); env.call_method(runnable, "run", "()V", &[]).unwrap(); test_data(&data, 1, 1); }); } #[test] fn test_fn_once_runnable_drop_panic() { test_utils::JVM_ENV.with(|env| { let dp = create_drop_panic_fn(); let runnable = super::fn_once_runnable(env, dp).unwrap(); let result = try_block(env, || { env.call_method(runnable, "close", "()V", &[])?; Ok(false) }) .catch("io/github/gedgygedgy/rust/panic/PanicException", |ex| { let ex = crate::exceptions::JPanicException::from_env(env, ex).unwrap(); let any = ex.take().unwrap(); let msg = any.downcast::<&str>().unwrap(); assert_eq!(*msg, "DropPanic dropped"); Ok(true) }) .result() .unwrap(); assert!(result); }); } #[test] fn test_fn_once_runnable_local_run() { test_utils::JVM_ENV.with(|env| { let (data, f) = create_test_fn_local(); test_data_local(&data, 0, 2); let runnable = super::fn_once_runnable_local(env, f).unwrap(); test_data_local(&data, 0, 2); env.call_method(runnable, "run", "()V", &[]).unwrap(); test_data_local(&data, 1, 1); env.call_method(runnable, "run", "()V", &[]).unwrap(); test_data_local(&data, 1, 1); }); } #[test] fn test_fn_once_runnable_local_thread() { test_utils::JVM_ENV.with(|env| { let (data, f) = create_test_fn_local(); test_data_local(&data, 0, 2); let runnable = super::fn_once_runnable_local(env, f).unwrap(); let runnable = env.new_global_ref(runnable).unwrap(); test_data_local(&data, 0, 2); let thread = std::thread::spawn(move || { test_utils::JVM_ENV.with(|env| { let value = crate::exceptions::try_block(env, || { env.call_method(runnable.as_obj(), "run", "()V", &[])?; Ok(false) }) .catch( "io/github/gedgygedgy/rust/thread/LocalThreadException", |_ex| Ok(true), ) .result() .unwrap(); assert!(value); let value = crate::exceptions::try_block(env, || { env.call_method(runnable.as_obj(), "close", "()V", &[])?; Ok(false) }) .catch( "io/github/gedgygedgy/rust/thread/LocalThreadException", |_ex| Ok(true), ) .result() .unwrap(); assert!(value); }); }); thread.join().unwrap(); test_data_local(&data, 0, 2); }); } #[test] fn test_fn_mut_runnable_run() { test_utils::JVM_ENV.with(|env| { let (data, f) = create_test_fn(); test_data(&data, 0, 2); let runnable = super::fn_mut_runnable(env, f).unwrap(); test_data(&data, 0, 2); env.call_method(runnable, "run", "()V", &[]).unwrap(); test_data(&data, 1, 2); env.call_method(runnable, "run", "()V", &[]).unwrap(); test_data(&data, 2, 2); }); } #[test] fn test_fn_mut_runnable_close() { test_utils::JVM_ENV.with(|env| { let (data, f) = create_test_fn(); test_data(&data, 0, 2); let runnable = super::fn_mut_runnable(env, f).unwrap(); test_data(&data, 0, 2); env.call_method(runnable, "close", "()V", &[]).unwrap(); test_data(&data, 0, 1); env.call_method(runnable, "close", "()V", &[]).unwrap(); test_data(&data, 0, 1); }); } #[test] fn test_fn_mut_runnable_run_close() { test_utils::JVM_ENV.with(|env| { let (data, f) = create_test_fn(); test_data(&data, 0, 2); let runnable = super::fn_mut_runnable(env, f).unwrap(); test_data(&data, 0, 2); env.call_method(runnable, "run", "()V", &[]).unwrap(); test_data(&data, 1, 2); env.call_method(runnable, "close", "()V", &[]).unwrap(); test_data(&data, 1, 1); }); } #[test] fn test_fn_mut_runnable_close_run() { test_utils::JVM_ENV.with(|env| { let (data, f) = create_test_fn(); test_data(&data, 0, 2); let runnable = super::fn_mut_runnable(env, f).unwrap(); test_data(&data, 0, 2); env.call_method(runnable, "close", "()V", &[]).unwrap(); test_data(&data, 0, 1); env.call_method(runnable, "run", "()V", &[]).unwrap(); test_data(&data, 0, 1); }); } #[test] fn test_fn_mut_runnable_thread() { test_utils::JVM_ENV.with(|env| { let (data, f) = create_test_fn(); test_data(&data, 0, 2); let runnable = super::fn_mut_runnable(env, f).unwrap(); test_data(&data, 0, 2); let runnable = env.new_global_ref(runnable).unwrap(); let thread = std::thread::spawn(move || { test_utils::JVM_ENV.with(|env| { env.call_method(runnable.as_obj(), "run", "()V", &[]) .unwrap(); }); }); thread.join().unwrap(); test_data(&data, 1, 2); }); } #[test] fn test_fn_mut_runnable_object() { test_utils::JVM_ENV.with(|env| { let obj_ref = Arc::new(Mutex::new(env.new_global_ref(JObject::null()).unwrap())); let obj_ref_2 = obj_ref.clone(); let runnable = super::fn_mut_runnable(env, move |e, o| { let guard = obj_ref_2.lock().unwrap(); assert!(e.is_same_object(guard.as_obj(), o).unwrap()); }) .unwrap(); { let mut guard = obj_ref.lock().unwrap(); *guard = env.new_global_ref(runnable).unwrap(); } env.call_method(runnable, "run", "()V", &[]).unwrap(); }); } #[test] fn test_fn_mut_runnable_panic() { test_utils::JVM_ENV.with(|env| { let runnable = super::fn_mut_runnable(env, |_e, _o| panic!("This is a panic")).unwrap(); let result = try_block(env, || { env.call_method(runnable, "run", "()V", &[])?; Ok(false) }) .catch("io/github/gedgygedgy/rust/panic/PanicException", |ex| { let ex = crate::exceptions::JPanicException::from_env(env, ex).unwrap(); let any = ex.take().unwrap(); let str = any.downcast::<&str>().unwrap(); assert_eq!(*str, "This is a panic"); Ok(true) }) .result() .unwrap(); assert!(result); }); } #[test] fn test_fn_mut_runnable_close_self() { test_utils::JVM_ENV.with(|env| { let (data, f) = create_test_fn(); test_data(&data, 0, 2); let runnable = super::fn_mut_runnable(env, move |env, obj| { env.call_method(obj, "close", "()V", &[]).unwrap(); f(env, obj); }) .unwrap(); test_data(&data, 0, 2); env.call_method(runnable, "run", "()V", &[]).unwrap(); test_data(&data, 1, 1); env.call_method(runnable, "run", "()V", &[]).unwrap(); test_data(&data, 1, 1); }); } #[test] fn test_fn_mut_runnable_drop_panic() { test_utils::JVM_ENV.with(|env| { let dp = create_drop_panic_fn(); let runnable = super::fn_mut_runnable(env, dp).unwrap(); let result = try_block(env, || { env.call_method(runnable, "close", "()V", &[])?; Ok(false) }) .catch("io/github/gedgygedgy/rust/panic/PanicException", |ex| { let ex = crate::exceptions::JPanicException::from_env(env, ex).unwrap(); let any = ex.take().unwrap(); let msg = any.downcast::<&str>().unwrap(); assert_eq!(*msg, "DropPanic dropped"); Ok(true) }) .result() .unwrap(); assert!(result); }); } #[test] fn test_fn_mut_runnable_local_run() { test_utils::JVM_ENV.with(|env| { let (data, f) = create_test_fn_local(); test_data_local(&data, 0, 2); let runnable = super::fn_mut_runnable_local(env, f).unwrap(); test_data_local(&data, 0, 2); env.call_method(runnable, "run", "()V", &[]).unwrap(); test_data_local(&data, 1, 2); env.call_method(runnable, "run", "()V", &[]).unwrap(); test_data_local(&data, 2, 2); }); } #[test] fn test_fn_mut_runnable_local_thread() { test_utils::JVM_ENV.with(|env| { let (data, f) = create_test_fn_local(); test_data_local(&data, 0, 2); let runnable = super::fn_mut_runnable_local(env, f).unwrap(); let runnable = env.new_global_ref(runnable).unwrap(); test_data_local(&data, 0, 2); let thread = std::thread::spawn(move || { test_utils::JVM_ENV.with(|env| { let value = crate::exceptions::try_block(env, || { env.call_method(runnable.as_obj(), "run", "()V", &[])?; Ok(false) }) .catch( "io/github/gedgygedgy/rust/thread/LocalThreadException", |_ex| Ok(true), ) .result() .unwrap(); assert!(value); let value = crate::exceptions::try_block(env, || { env.call_method(runnable.as_obj(), "close", "()V", &[])?; Ok(false) }) .catch( "io/github/gedgygedgy/rust/thread/LocalThreadException", |_ex| Ok(true), ) .result() .unwrap(); assert!(value); }); }); thread.join().unwrap(); test_data_local(&data, 0, 2); }); } #[test] fn test_fn_runnable_run() { test_utils::JVM_ENV.with(|env| { let (data, f) = create_test_fn(); test_data(&data, 0, 2); let runnable = super::fn_runnable(env, f).unwrap(); test_data(&data, 0, 2); env.call_method(runnable, "run", "()V", &[]).unwrap(); test_data(&data, 1, 2); env.call_method(runnable, "run", "()V", &[]).unwrap(); test_data(&data, 2, 2); }); } #[test] fn test_fn_runnable_close() { test_utils::JVM_ENV.with(|env| { let (data, f) = create_test_fn(); test_data(&data, 0, 2); let runnable = super::fn_runnable(env, f).unwrap(); test_data(&data, 0, 2); env.call_method(runnable, "close", "()V", &[]).unwrap(); test_data(&data, 0, 1); env.call_method(runnable, "close", "()V", &[]).unwrap(); test_data(&data, 0, 1); }); } #[test] fn test_fn_runnable_run_close() { test_utils::JVM_ENV.with(|env| { let (data, f) = create_test_fn(); test_data(&data, 0, 2); let runnable = super::fn_runnable(env, f).unwrap(); test_data(&data, 0, 2); env.call_method(runnable, "run", "()V", &[]).unwrap(); test_data(&data, 1, 2); env.call_method(runnable, "close", "()V", &[]).unwrap(); test_data(&data, 1, 1); }); } #[test] fn test_fn_runnable_close_run() { test_utils::JVM_ENV.with(|env| { let (data, f) = create_test_fn(); test_data(&data, 0, 2); let runnable = super::fn_runnable(env, f).unwrap(); test_data(&data, 0, 2); env.call_method(runnable, "close", "()V", &[]).unwrap(); test_data(&data, 0, 1); env.call_method(runnable, "run", "()V", &[]).unwrap(); test_data(&data, 0, 1); }); } #[test] fn test_fn_runnable_thread() { test_utils::JVM_ENV.with(|env| { let (data, f) = create_test_fn(); test_data(&data, 0, 2); let runnable = super::fn_runnable(env, f).unwrap(); test_data(&data, 0, 2); let runnable = env.new_global_ref(runnable).unwrap(); let thread = std::thread::spawn(move || { test_utils::JVM_ENV.with(|env| { env.call_method(runnable.as_obj(), "run", "()V", &[]) .unwrap(); }); }); thread.join().unwrap(); test_data(&data, 1, 2); }); } #[test] fn test_fn_runnable_object() { test_utils::JVM_ENV.with(|env| { let obj_ref = Arc::new(Mutex::new(env.new_global_ref(JObject::null()).unwrap())); let obj_ref_2 = obj_ref.clone(); let runnable = super::fn_runnable(env, move |e, o| { let guard = obj_ref_2.lock().unwrap(); assert!(e.is_same_object(guard.as_obj(), o).unwrap()); }) .unwrap(); { let mut guard = obj_ref.lock().unwrap(); *guard = env.new_global_ref(runnable).unwrap(); } env.call_method(runnable, "run", "()V", &[]).unwrap(); }); } #[test] fn test_fn_runnable_recursive() { test_utils::JVM_ENV.with(|env| { let arc = Arc::new(Mutex::new(false)); let arc2 = arc.clone(); let calling = Mutex::new(false); let runnable = super::fn_runnable(env, move |env, obj| { let calling_value = { let mut guard = calling.lock().unwrap(); let old = *guard; *guard = true; old }; if !calling_value { env.call_method(obj, "run", "()V", &[]).unwrap(); let mut guard = calling.lock().unwrap(); *guard = false; } else { let mut guard = arc2.lock().unwrap(); *guard = true; } }) .unwrap(); env.call_method(runnable, "run", "()V", &[]).unwrap(); let guard = arc.lock().unwrap(); assert!(*guard); }) } #[test] fn test_fn_runnable_panic() { test_utils::JVM_ENV.with(|env| { let runnable = super::fn_runnable(env, |_e, _o| panic!("This is a panic")).unwrap(); let result = try_block(env, || { env.call_method(runnable, "run", "()V", &[])?; Ok(false) }) .catch("io/github/gedgygedgy/rust/panic/PanicException", |ex| { let ex = crate::exceptions::JPanicException::from_env(env, ex).unwrap(); let any = ex.take().unwrap(); let str = any.downcast::<&str>().unwrap(); assert_eq!(*str, "This is a panic"); Ok(true) }) .result() .unwrap(); assert!(result); }); } #[test] fn test_fn_runnable_close_self() { test_utils::JVM_ENV.with(|env| { let (data, f) = create_test_fn(); test_data(&data, 0, 2); let runnable = super::fn_runnable(env, move |env, obj| { env.call_method(obj, "close", "()V", &[]).unwrap(); f(env, obj); }) .unwrap(); test_data(&data, 0, 2); env.call_method(runnable, "run", "()V", &[]).unwrap(); test_data(&data, 1, 1); env.call_method(runnable, "run", "()V", &[]).unwrap(); test_data(&data, 1, 1); }); } #[test] fn test_fn_runnable_drop_panic() { test_utils::JVM_ENV.with(|env| { let dp = create_drop_panic_fn(); let runnable = super::fn_runnable(env, dp).unwrap(); let result = try_block(env, || { env.call_method(runnable, "close", "()V", &[])?; Ok(false) }) .catch("io/github/gedgygedgy/rust/panic/PanicException", |ex| { let ex = crate::exceptions::JPanicException::from_env(env, ex).unwrap(); let any = ex.take().unwrap(); let msg = any.downcast::<&str>().unwrap(); assert_eq!(*msg, "DropPanic dropped"); Ok(true) }) .result() .unwrap(); assert!(result); }); } #[test] fn test_fn_runnable_local_run() { test_utils::JVM_ENV.with(|env| { let (data, f) = create_test_fn_local(); test_data_local(&data, 0, 2); let runnable = super::fn_runnable_local(env, f).unwrap(); test_data_local(&data, 0, 2); env.call_method(runnable, "run", "()V", &[]).unwrap(); test_data_local(&data, 1, 2); env.call_method(runnable, "run", "()V", &[]).unwrap(); test_data_local(&data, 2, 2); }); } #[test] fn test_fn_runnable_local_thread() { test_utils::JVM_ENV.with(|env| { let (data, f) = create_test_fn_local(); test_data_local(&data, 0, 2); let runnable = super::fn_runnable_local(env, f).unwrap(); let runnable = env.new_global_ref(runnable).unwrap(); test_data_local(&data, 0, 2); let thread = std::thread::spawn(move || { test_utils::JVM_ENV.with(|env| { let value = crate::exceptions::try_block(env, || { env.call_method(runnable.as_obj(), "run", "()V", &[])?; Ok(false) }) .catch( "io/github/gedgygedgy/rust/thread/LocalThreadException", |_ex| Ok(true), ) .result() .unwrap(); assert!(value); let value = crate::exceptions::try_block(env, || { env.call_method(runnable.as_obj(), "close", "()V", &[])?; Ok(false) }) .catch( "io/github/gedgygedgy/rust/thread/LocalThreadException", |_ex| Ok(true), ) .result() .unwrap(); assert!(value); }); }); thread.join().unwrap(); test_data_local(&data, 0, 2); }); } #[test] fn test_fn_bi_function_object() { test_utils::JVM_ENV.with(|env| { let arg1 = env .new_global_ref(env.new_object("java/lang/Object", "()V", &[]).unwrap()) .unwrap(); let arg2 = env .new_global_ref(env.new_object("java/lang/Object", "()V", &[]).unwrap()) .unwrap(); let ret = env .new_global_ref(env.new_object("java/lang/Object", "()V", &[]).unwrap()) .unwrap(); let arg1_clone = arg1.clone(); let arg2_clone = arg2.clone(); let ret_clone = ret.clone(); let obj_ref = Arc::new(Mutex::new(env.new_global_ref(JObject::null()).unwrap())); let obj_ref_2 = obj_ref.clone(); let bi_function = super::fn_bi_function(env, move |e, o, a1, a2| { let guard = obj_ref_2.lock().unwrap(); assert!(e.is_same_object(guard.as_obj(), o).unwrap()); assert!(e.is_same_object(arg1_clone.as_obj(), a1).unwrap()); assert!(e.is_same_object(arg2_clone.as_obj(), a2).unwrap()); ret_clone.as_obj().into_inner().into() }) .unwrap(); { let mut guard = obj_ref.lock().unwrap(); *guard = env.new_global_ref(bi_function).unwrap(); } let actual_ret = env .call_method( bi_function, "apply", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", &[arg1.as_obj().into(), arg2.as_obj().into()], ) .unwrap() .l() .unwrap(); assert!(env.is_same_object(ret.as_obj(), actual_ret).unwrap()); }); } #[test] fn test_fn_function_object() { test_utils::JVM_ENV.with(|env| { let arg = env .new_global_ref(env.new_object("java/lang/Object", "()V", &[]).unwrap()) .unwrap(); let ret = env .new_global_ref(env.new_object("java/lang/Object", "()V", &[]).unwrap()) .unwrap(); let arg_clone = arg.clone(); let ret_clone = ret.clone(); let obj_ref = Arc::new(Mutex::new(env.new_global_ref(JObject::null()).unwrap())); let obj_ref_2 = obj_ref.clone(); let function = super::fn_function(env, move |e, o, a| { let guard = obj_ref_2.lock().unwrap(); assert!(e.is_same_object(guard.as_obj(), o).unwrap()); assert!(e.is_same_object(arg_clone.as_obj(), a).unwrap()); ret_clone.as_obj().into_inner().into() }) .unwrap(); { let mut guard = obj_ref.lock().unwrap(); *guard = env.new_global_ref(function).unwrap(); } let actual_ret = env .call_method( function, "apply", "(Ljava/lang/Object;)Ljava/lang/Object;", &[arg.as_obj().into()], ) .unwrap() .l() .unwrap(); assert!(env.is_same_object(ret.as_obj(), actual_ret).unwrap()); }); } }