use std::ptr;
use jni_sys::{jobject, jstring};
use futures::channel::oneshot;
use crate::errors::opt_to_res;
use crate::{cache, errors, jni_utils, Instance, InvocationArg, Jvm};
use super::logger::debug;
impl Jvm {
pub async fn invoke_async(
&self,
instance: &Instance,
method_name: &str,
inv_args: &[InvocationArg],
) -> errors::Result<Instance> {
debug(&format!(
"Asynchronously invoking method {} of class {} using {} arguments",
method_name,
instance.class_name,
inv_args.len()
));
let (sender, rx) = oneshot::channel::<errors::Result<Instance>>();
unsafe {
Self::handle_channel_sender(self, sender, &instance, &method_name, inv_args.as_ref())?;
}
let instance = rx.await?;
Self::do_return(self.jni_env, instance)?
}
pub async fn invoke_into_sendable_async(
instance: Instance,
method_name: String,
inv_args: Vec<InvocationArg>,
) -> errors::Result<Instance> {
debug(&format!(
"Asynchronously invoking (2) method {} of class {} using {} arguments",
method_name,
instance.class_name,
inv_args.len()
));
let (sender, rx) = oneshot::channel::<errors::Result<Instance>>();
unsafe {
let s = Jvm::attach_thread()?;
Self::handle_channel_sender(&s, sender, &instance, &method_name, inv_args.as_ref())?;
drop(s);
}
let instance = rx.await?;
let new_jvm = Jvm::attach_thread()?;
let new_jni_env = new_jvm.jni_env;
Self::do_return(new_jni_env, instance)?
}
unsafe fn handle_channel_sender(s: &Jvm, sender: oneshot::Sender<errors::Result<Instance>>, instance: &Instance, method_name: &str, inv_args: &[InvocationArg]) -> errors::Result<()> {
let tx = Box::new(sender);
let raw_ptr = Box::into_raw(tx);
let address_string = format!("{:p}", raw_ptr);
let address = i64::from_str_radix(&address_string[2..], 16).unwrap();
let method_name_jstring: jstring =
jni_utils::global_jobject_from_str(&method_name, s.jni_env)?;
let size = inv_args.len() as i32;
let array_ptr = {
let j = (opt_to_res(cache::get_jni_new_object_array())?)(
s.jni_env,
size,
cache::get_invocation_arg_class()?,
ptr::null_mut(),
);
jni_utils::create_global_ref_from_local_ref(j, s.jni_env)?
};
let mut inv_arg_jobjects: Vec<jobject> = Vec::with_capacity(size as usize);
for i in 0..size {
let inv_arg_java =
inv_args[i as usize].as_java_ptr_with_global_ref(s.jni_env)?;
(opt_to_res(cache::get_jni_set_object_array_element())?)(
s.jni_env,
array_ptr,
i,
inv_arg_java,
);
inv_arg_jobjects.push(inv_arg_java);
}
let _ = (opt_to_res(cache::get_jni_call_void_method())?)(
s.jni_env,
instance.jinstance,
cache::get_invoke_async_method()?,
address,
method_name_jstring,
array_ptr,
);
Self::do_return(s.jni_env, ())?;
for inv_arg_jobject in inv_arg_jobjects {
jni_utils::delete_java_ref(s.jni_env, inv_arg_jobject);
}
jni_utils::delete_java_ref(s.jni_env, array_ptr);
jni_utils::delete_java_ref(s.jni_env, method_name_jstring);
Ok(())
}
}
#[cfg(test)]
mod api_unit_tests {
use super::*;
use crate::{api, JvmBuilder, MavenArtifact};
use futures::Future;
use tokio;
fn create_tests_jvm() -> errors::Result<Jvm> {
let jvm: Jvm = JvmBuilder::new().build()?;
jvm.deploy_artifact(&MavenArtifact::from(format!("io.github.astonbitecode:j4rs-testing:{}", api::j4rs_version()).as_str()))?;
Ok(jvm)
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn invoke_async_success_w_tokio() -> errors::Result<()> {
let s_test = "j4rs_rust";
let jvm = create_tests_jvm()?;
let my_test = jvm.create_instance("org.astonbitecode.j4rs.tests.MyTest", InvocationArg::empty())?;
let instance = jvm
.invoke_async(
&my_test,
"getStringWithFuture",
&[InvocationArg::try_from(s_test)?],
)
.await?;
let string: String = jvm.to_rust(instance)?;
assert_eq!(s_test, string);
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn invoke_async_failure_w_tokio() -> errors::Result<()> {
let s_test = "Boom!";
let jvm = create_tests_jvm()?;
let my_test = jvm.create_instance("org.astonbitecode.j4rs.tests.MyTest", InvocationArg::empty())?;
let instance_result = jvm
.invoke_async(
&my_test,
"getErrorWithFuture",
&[InvocationArg::try_from(s_test)?],
)
.await;
assert!(instance_result.is_err());
let error = instance_result.err().unwrap();
println!("{}", error);
Ok(())
}
#[async_std::test]
async fn invoke_async_success_w_async_std() -> errors::Result<()> {
let s_test = "j4rs_rust";
let jvm = create_tests_jvm()?;
let my_test = jvm.create_instance("org.astonbitecode.j4rs.tests.MyTest", InvocationArg::empty())?;
let instance = jvm
.invoke_async(
&my_test,
"getStringWithFuture",
&[InvocationArg::try_from(s_test)?],
)
.await?;
let string: String = jvm.to_rust(instance)?;
assert_eq!(s_test, string);
Ok(())
}
#[async_std::test]
async fn invoke_async_failure_w_async_std() -> errors::Result<()> {
let s_test = "Boom!";
let jvm = create_tests_jvm()?;
let my_test = jvm.create_instance("org.astonbitecode.j4rs.tests.MyTest", InvocationArg::empty())?;
let instance_result = jvm
.invoke_async(
&my_test,
"getErrorWithFuture",
&[InvocationArg::try_from(s_test)?],
)
.await;
assert!(instance_result.is_err());
let error = instance_result.err().unwrap();
println!("{}", error);
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn invoke_async_and_reuse_instance() -> errors::Result<()> {
let s_test1 = "j4rs_rust1";
let s_test2 = "j4rs_rust2";
let jvm = create_tests_jvm()?;
let my_test = jvm.create_instance("org.astonbitecode.j4rs.tests.MyTest", InvocationArg::empty())?;
let instance1 = jvm
.invoke_async(
&my_test,
"getStringWithFuture",
&[InvocationArg::try_from(s_test1)?],
)
.await?;
let instance2 = jvm
.invoke_async(
&my_test,
"getStringWithFuture",
&[InvocationArg::try_from(s_test2)?],
)
.await?;
let string1: String = jvm.to_rust(instance1)?;
let string2: String = jvm.to_rust(instance2)?;
assert_eq!(s_test1, string1);
assert_eq!(s_test2, string2);
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
async fn invoke_static_async() -> errors::Result<()> {
let s_test = "j4rs_rust";
let jvm = create_tests_jvm()?;
let my_test = jvm.static_class("org.astonbitecode.j4rs.tests.MyTest")?;
let instance = jvm
.invoke_async(
&my_test,
"getErrorWithFutureStatic",
&[InvocationArg::try_from(s_test)?],
)
.await?;
let string: String = jvm.to_rust(instance)?;
assert_eq!(s_test, string);
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
async fn invoke_async_error_before_executing_async() -> errors::Result<()> {
let s_test = "j4rs_rust";
let jvm = create_tests_jvm()?;
let my_test = jvm.create_instance("org.astonbitecode.j4rs.tests.MyTest", InvocationArg::empty())?;
let instance_result = jvm
.invoke_async(&my_test, "echo", &[InvocationArg::try_from(s_test)?])
.await;
assert!(instance_result.is_err());
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn invoke_void_future() -> errors::Result<()> {
let s_test = "j4rs_rust";
let jvm = create_tests_jvm()?;
let my_test = jvm.create_instance("org.astonbitecode.j4rs.tests.MyTest", InvocationArg::empty())?;
let instance_res = jvm
.invoke_async(
&my_test,
"executeVoidFuture",
&[InvocationArg::try_from(s_test)?],
)
.await;
assert!(instance_res.is_ok());
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn invoke_into_sendable_async_success() -> errors::Result<()> {
let s_test = "j4rs_rust";
let jvm = create_tests_jvm()?;
let my_test = jvm.create_instance("org.astonbitecode.j4rs.tests.MyTest", InvocationArg::empty())?;
let instance = Jvm::invoke_into_sendable_async(
my_test,
"getStringWithFuture".to_string(),
vec![InvocationArg::try_from(s_test)?],
)
.await?;
let string: String = jvm.to_rust(instance)?;
assert_eq!(s_test, string);
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn future_is_send() -> errors::Result<()> {
let s_test = "j4rs_rust";
let jvm = create_tests_jvm()?;
let my_test = jvm.create_instance("org.astonbitecode.j4rs.tests.MyTest", InvocationArg::empty())?;
let f = Jvm::invoke_into_sendable_async(
my_test,
"executeVoidFuture".to_string(),
vec![InvocationArg::try_from(s_test)?],
);
check_send(f);
Ok(())
}
fn check_send<F:Future>(_:F) where F:Send + 'static {}
async fn _memory_leaks_invoke_async_instances() -> errors::Result<()> {
let jvm = create_tests_jvm()?;
let instance = jvm.create_instance("org.astonbitecode.j4rs.tests.MyTest", InvocationArg::empty() as &[InvocationArg; 0])?;
for i in 0..100000000 {
if i % 100000 == 0 {
println!("{}", i);
}
let ia = InvocationArg::try_from(i.to_string())?;
let _s = jvm
.invoke_async(&instance, "getStringWithFuture", &[ia])
.await?;
}
Ok(())
}
}