Skip to main content

prebindgen_jni_runtime/
iface_method.rs

1//! Process-wide cached interface-method handles for generated upcalls.
2//!
3//! Return-site callbacks (output-expansion `build` lambdas, `fold` lambdas,
4//! `onError` handlers) arrive as fresh objects on every extern call — there
5//! is no long-lived creation site to hoist a method lookup to. But each of
6//! them implements a *generated* `fun interface` whose FQN and method
7//! descriptor are fixed at codegen time, and JNI permits resolving a method
8//! ID on a supertype (the interface class) and invoking it virtually on any
9//! implementing instance. So generated code declares one
10//! `static CACHED: CachedIfaceMethod` per call site; the first call resolves
11//! and pins the interface class, every later call is a single
12//! `CallObjectMethodA` — no descriptor parsing, no symbol-table lookup
13//! (jni-rs's safe `call_method` re-does both on every call).
14
15use std::sync::OnceLock;
16
17use jni::{
18    objects::{GlobalRef, JMethodID, JObject},
19    signature::ReturnType,
20    sys::jvalue,
21    JNIEnv,
22};
23
24/// A `(pinned interface class, method ID)` pair resolved once per process.
25/// Declare as `static`; the embedded [`OnceLock`] handles the one-time
26/// resolution race (both winners produce equivalent values).
27pub struct CachedIfaceMethod {
28    cell: OnceLock<Resolved>,
29}
30
31struct Resolved {
32    /// Pins the interface class so the method ID below stays valid.
33    _class: GlobalRef,
34    method: JMethodID,
35}
36
37impl CachedIfaceMethod {
38    pub const fn new() -> Self {
39        Self {
40            cell: OnceLock::new(),
41        }
42    }
43
44    fn resolve(
45        &self,
46        env: &mut JNIEnv,
47        class_fqn: &str,
48        method: &str,
49        descr: &str,
50    ) -> Result<&Resolved, String> {
51        if let Some(r) = self.cell.get() {
52            return Ok(r);
53        }
54        let class = env
55            .find_class(class_fqn)
56            .map_err(|e| format!("find callback interface {class_fqn}: {e}"))?;
57        let id = env
58            .get_method_id(&class, method, descr)
59            .map_err(|e| format!("resolve {class_fqn}.{method}{descr}: {e}"))?;
60        let class = env
61            .new_global_ref(&class)
62            .map_err(|e| format!("global-ref callback interface {class_fqn}: {e}"))?;
63        let _ = self.cell.set(Resolved {
64            _class: class,
65            method: id,
66        });
67        Ok(self.cell.get().expect("cell was just set"))
68    }
69
70    /// Invoke the interface method on `obj`, returning its `Object` result.
71    /// Resolves (and pins) the interface class on first use.
72    ///
73    /// SAFETY contract carried for the caller: `obj` must implement the
74    /// interface named by `class_fqn`, and `descr` must be the method's
75    /// exact JVM descriptor — both are generated from the same plan, so
76    /// they agree by construction.
77    pub fn call_object<'local>(
78        &self,
79        env: &mut JNIEnv<'local>,
80        class_fqn: &str,
81        method: &str,
82        descr: &str,
83        obj: &JObject,
84        args: &[jvalue],
85    ) -> Result<JObject<'local>, String> {
86        let r = self.resolve(env, class_fqn, method, descr)?;
87        // SAFETY: see the doc contract above; the GlobalRef pins the class.
88        unsafe { env.call_method_unchecked(obj, r.method, ReturnType::Object, args) }
89            .and_then(|v| v.l())
90            .map_err(|e| format!("invoke {class_fqn}.{method}: {e}"))
91    }
92}
93
94impl Default for CachedIfaceMethod {
95    fn default() -> Self {
96        Self::new()
97    }
98}