use std::sync::OnceLock;
use jni::{
objects::{GlobalRef, JMethodID, JObject},
signature::ReturnType,
sys::jvalue,
JNIEnv,
};
pub struct CachedIfaceMethod {
cell: OnceLock<Resolved>,
}
struct Resolved {
_class: GlobalRef,
method: JMethodID,
}
impl CachedIfaceMethod {
pub const fn new() -> Self {
Self {
cell: OnceLock::new(),
}
}
fn resolve(
&self,
env: &mut JNIEnv,
class_fqn: &str,
method: &str,
descr: &str,
) -> Result<&Resolved, String> {
if let Some(r) = self.cell.get() {
return Ok(r);
}
let class = env
.find_class(class_fqn)
.map_err(|e| format!("find callback interface {class_fqn}: {e}"))?;
let id = env
.get_method_id(&class, method, descr)
.map_err(|e| format!("resolve {class_fqn}.{method}{descr}: {e}"))?;
let class = env
.new_global_ref(&class)
.map_err(|e| format!("global-ref callback interface {class_fqn}: {e}"))?;
let _ = self.cell.set(Resolved {
_class: class,
method: id,
});
Ok(self.cell.get().expect("cell was just set"))
}
pub fn call_object<'local>(
&self,
env: &mut JNIEnv<'local>,
class_fqn: &str,
method: &str,
descr: &str,
obj: &JObject,
args: &[jvalue],
) -> Result<JObject<'local>, String> {
let r = self.resolve(env, class_fqn, method, descr)?;
unsafe { env.call_method_unchecked(obj, r.method, ReturnType::Object, args) }
.and_then(|v| v.l())
.map_err(|e| format!("invoke {class_fqn}.{method}: {e}"))
}
}
impl Default for CachedIfaceMethod {
fn default() -> Self {
Self::new()
}
}