fn runtime() -> &'static Runtime {
static RT: OnceLock<Runtime> = OnceLock::new();
RT.get_or_init(|| Runtime::new().expect("create tokio runtime"))
}
fn jstring_to_string(env: &mut Env<'_>, s: JString) -> std::result::Result<String, jni::errors::Error> {
s.try_to_string(env)
}
fn string_to_jstring(env: &mut Env<'_>, s: impl AsRef<str>) -> jstring {
match env.new_string(s.as_ref()) {
Ok(o) => o.into_raw(),
Err(_) => std::ptr::null_mut(),
}
}
fn jni_call_string_method(env: &mut Env<'_>, obj: JObject, method_name: &str, method_sig: &str) -> std::result::Result<String, jni::errors::Error> {
use std::str::FromStr;
let class = env.get_object_class(&obj)?;
let name_jni = jni::strings::JNIString::from(method_name);
let sig_runtime = jni::signature::RuntimeMethodSignature::from_str(method_sig)?;
let sig = sig_runtime.method_signature();
let method_id = env.get_method_id(&class, name_jni, sig)?;
// SAFETY: method_id is valid from the preceding get_method_id call, and the method exists on the class.
let result = unsafe { env.call_method_unchecked(&obj, method_id, jni::signature::ReturnType::Object, &[])? }
.l()?;
// SAFETY: JNI return type guaranteed a String, so the raw jstring pointer is valid.
let jstring = unsafe { JString::from_raw(env, result.into_raw()) };
jstring_to_string(env, jstring)
}
fn throw_jni_error(env: &mut Env<'_>, msg: &str) {
// If the error class cannot be found (misconfigured AAR), fall back to a
// generic RuntimeException so the caller always gets *some* exception rather
// than a silent null/zero return that looks like a valid result.
let class_jni = jni::strings::JNIString::from(ERROR_CLASS);
let msg_jni = jni::strings::JNIString::from(msg);
if env.throw_new(&class_jni, &msg_jni).is_err() {
let fallback = jni::strings::JNIString::from("java/lang/RuntimeException");
let _ = env.throw_new(&fallback, &msg_jni);
}
}
fn run_or_throw<T, F>(env: &mut Env<'_>, f: F) -> Option<T>
where
F: FnOnce() -> T + std::panic::UnwindSafe,
{
match std::panic::catch_unwind(f) {
Ok(v) => Some(v),
Err(payload) => {
let msg = payload.downcast_ref::<String>().cloned()
.or_else(|| payload.downcast_ref::<&str>().map(|s| (*s).to_string()))
.unwrap_or_else(|| "panic in native code".to_string());
throw_jni_error(env, &format!("native panic: {msg}"));
None
}
}
}
/// Call a trait-bridge dispatcher's `dispatch(String, String) -> String` entry
/// point. Used by the generated plugin bridges to route trait method calls to
/// the JVM host object as JSON.
#[allow(dead_code)]
fn jni_dispatch_json(
env: &mut Env<'_>,
obj: &JObject,
method: &str,
args_json: &str,
) -> std::result::Result<String, jni::errors::Error> {
use std::str::FromStr;
let class = env.get_object_class(obj)?;
let name_jni = jni::strings::JNIString::from("dispatch");
let sig_runtime = jni::signature::RuntimeMethodSignature::from_str(
"(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;",
)?;
let sig = sig_runtime.method_signature();
let method_id = env.get_method_id(&class, name_jni, sig)?;
let method_j = env.new_string(method)?;
let args_j = env.new_string(args_json)?;
// SAFETY: method_id comes from get_method_id on this object's class and the
// argument values match the looked-up signature.
let call = unsafe {
env.call_method_unchecked(
obj,
method_id,
jni::signature::ReturnType::Object,
&[
jni::objects::JValue::Object(&method_j).as_jni(),
jni::objects::JValue::Object(&args_j).as_jni(),
],
)
};
let result = match call.and_then(|v| v.l()) {
Ok(o) => o,
Err(e) => {
// A pending Java exception must be cleared before further JNI calls.
if env.exception_check() {
env.exception_clear();
}
return Err(e);
}
};
// SAFETY: the dispatcher's `dispatch` method returns a Java String.
let jstr = unsafe { JString::from_raw(env, result.into_raw()) };
jstring_to_string(env, jstr)
}