use jni::errors::Error as JNIError;
use jni::objects::{GlobalRef, JClass, JObject, JValue};
use jni::{AttachGuard, JNIEnv, JavaVM};
use once_cell::sync::OnceCell;
static GLOBAL: OnceCell<Global> = OnceCell::new();
pub trait Runtime: Send + Sync {
fn java_vm(&self) -> &JavaVM;
fn context(&self) -> &GlobalRef;
fn class_loader(&self) -> &GlobalRef;
}
enum Global {
Internal {
java_vm: JavaVM,
context: GlobalRef,
loader: GlobalRef,
},
External(&'static dyn Runtime),
}
impl Global {
fn env(&self) -> Result<AttachGuard<'_>, Error> {
let vm = match self {
Global::Internal { java_vm, .. } => java_vm,
Global::External(global) => global.java_vm(),
};
Ok(vm.attach_current_thread()?)
}
fn context(&self) -> Result<(GlobalContext, AttachGuard<'_>), Error> {
let env = self.env()?;
let context = match self {
Global::Internal { context, .. } => context,
Global::External(global) => global.context(),
};
let loader = match self {
Global::Internal { loader, .. } => loader,
Global::External(global) => global.class_loader(),
};
Ok((
GlobalContext {
context: env.new_global_ref(context)?,
loader: env.new_global_ref(loader)?,
},
env,
))
}
}
struct GlobalContext {
context: GlobalRef,
loader: GlobalRef,
}
fn global() -> &'static Global {
GLOBAL
.get()
.expect("Expect rustls-platform-verifier to be initialized")
}
pub fn init_with_env(env: &mut JNIEnv, context: JObject) -> Result<(), JNIError> {
GLOBAL.get_or_try_init(|| -> Result<_, JNIError> {
let loader =
env.call_method(&context, "getClassLoader", "()Ljava/lang/ClassLoader;", &[])?;
Ok(Global::Internal {
java_vm: env.get_java_vm()?,
context: env.new_global_ref(context)?,
loader: env.new_global_ref(JObject::try_from(loader)?)?,
})
})?;
Ok(())
}
pub fn init_hosted(env: &mut JNIEnv, context: JObject) -> Result<(), JNIError> {
init_with_env(env, context)
}
pub fn init_with_runtime(runtime: &'static dyn Runtime) {
GLOBAL.get_or_init(|| Global::External(runtime));
}
pub fn init_external(runtime: &'static dyn Runtime) {
init_with_runtime(runtime);
}
pub fn init_with_refs(java_vm: JavaVM, context: GlobalRef, loader: GlobalRef) {
GLOBAL.get_or_init(|| Global::Internal {
java_vm,
context,
loader,
});
}
#[derive(Debug)]
pub(super) struct Error;
impl From<JNIError> for Error {
#[track_caller]
fn from(cause: JNIError) -> Self {
if let JNIError::JavaException = cause {
if let Ok(env) = global().env() {
let _ = env.exception_describe();
let _ = env.exception_clear();
}
}
Self
}
}
pub(super) struct LocalContext<'a, 'env> {
env: &'a mut JNIEnv<'env>,
context: GlobalRef,
loader: GlobalRef,
}
impl<'a, 'env> LocalContext<'a, 'env> {
fn load_class(&mut self, name: &str) -> Result<JClass<'env>, Error> {
let name = self.env.new_string(name)?;
let class = self.env.call_method(
&self.loader,
"loadClass",
"(Ljava/lang/String;)Ljava/lang/Class;",
&[JValue::from(&name)],
)?;
Ok(JObject::try_from(class)?.into())
}
pub(super) fn application_context(&self) -> &JObject<'_> {
&self.context
}
}
pub(super) fn with_context<F, T: 'static>(f: F) -> Result<T, Error>
where
F: FnOnce(&mut LocalContext, &mut JNIEnv) -> Result<T, Error>,
{
let (global_context, mut binding_env) = global().context()?;
let ctx_env = unsafe { binding_env.unsafe_clone() };
binding_env.with_local_frame(16, |env| {
let mut ctx_env = ctx_env;
let mut context = LocalContext {
env: &mut ctx_env,
context: global_context.context,
loader: global_context.loader,
};
f(&mut context, env)
})
}
pub(super) struct CachedClass {
name: &'static str,
class: OnceCell<GlobalRef>,
}
impl CachedClass {
pub(super) const fn new(name: &'static str) -> Self {
Self {
name,
class: OnceCell::new(),
}
}
pub(super) fn get(&self, cx: &mut LocalContext) -> Result<&JClass<'_>, Error> {
let class = self.class.get_or_try_init(|| -> Result<_, Error> {
let class = cx.load_class(self.name)?;
Ok(cx.env.new_global_ref(class)?)
})?;
Ok(class.as_obj().into())
}
}