use crate::{
cast::{AsUpcast, TryDowncast, Upcast},
find::find_class,
into_rust::ToRustOp,
java::lang::{Class, Throwable},
link::{IntoJavaFns, JavaFunction},
not_null::NotNull,
plumbing::{FromRef, ToJavaImpl, ToJavaScalar},
raw::{self, EnvPtr, JvmPtr, ObjectPtr},
thread,
try_catch::TryCatch,
AsJRef, Error, IntoRust, Java, Local, Result, ToJava, TryJDeref,
};
use std::{
any::Any,
collections::HashMap,
ffi::{c_char, c_void, CStr, CString},
fmt::Display,
panic::AssertUnwindSafe,
};
use once_cell::sync::OnceCell;
#[cfg(test)]
mod test;
#[must_use = "JvmOps do nothing unless you call `.execute()"]
pub trait JvmOp: Clone {
type Output<'jvm>;
fn assert_not_null<T>(self) -> NotNull<Self>
where
T: JavaObject,
for<'jvm> Self: JvmOp<Output<'jvm> = Option<Local<'jvm, T>>>,
{
NotNull::new(self)
}
fn try_downcast<To>(self) -> TryDowncast<Self, To>
where
for<'jvm> Self::Output<'jvm>: TryJDeref,
To: for<'jvm> Upcast<<Self::Output<'jvm> as TryJDeref>::Java>,
{
TryDowncast::new(self)
}
fn upcast<To>(self) -> AsUpcast<Self, To>
where
for<'jvm> Self::Output<'jvm>: AsJRef<To>,
To: JavaObject,
{
AsUpcast::new(self)
}
fn catch<J>(self) -> TryCatch<Self, J>
where
J: Upcast<Throwable>,
{
TryCatch::new(self)
}
fn execute<R>(self) -> crate::Result<R>
where
for<'jvm> Self::Output<'jvm>: IntoRust<R>,
{
Jvm::with(|jvm| self.execute_with(jvm))
}
fn execute_with<'jvm, R>(self, jvm: &mut Jvm<'jvm>) -> crate::LocalResult<'jvm, R>
where
for<'j> Self::Output<'j>: IntoRust<R>,
{
ToRustOp::new(self).do_jni(jvm)
}
fn do_jni<'jvm>(self, jvm: &mut Jvm<'jvm>) -> crate::LocalResult<'jvm, Self::Output<'jvm>>;
}
pub trait JvmRefOp<T: JavaObject>: Clone {
type Output<'jvm>: AsJRef<T>;
fn into_as_jref<'jvm>(
self,
jvm: &mut Jvm<'jvm>,
) -> crate::LocalResult<'jvm, Self::Output<'jvm>>;
}
impl<J, T> JvmRefOp<T> for J
where
T: JavaObject,
J: JvmOp,
for<'jvm> <J as JvmOp>::Output<'jvm>: AsJRef<T>,
{
type Output<'jvm> = <J as JvmOp>::Output<'jvm>;
fn into_as_jref<'jvm>(
self,
jvm: &mut Jvm<'jvm>,
) -> crate::LocalResult<'jvm, <Self as JvmRefOp<T>>::Output<'jvm>> {
JvmOp::do_jni(self, jvm)
}
}
pub trait JvmScalarOp<T: JavaScalar>: for<'jvm> JvmOp<Output<'jvm> = T> {}
impl<J, T> JvmScalarOp<T> for J
where
T: JavaScalar,
J: for<'jvm> JvmOp<Output<'jvm> = T>,
{
}
static GLOBAL_JVM: OnceCell<JvmPtr> = OnceCell::new();
fn get_or_default_init_jvm() -> crate::Result<JvmPtr> {
match GLOBAL_JVM.get() {
Some(jvm) => Ok(*jvm),
None => {
Jvm::builder().launch_or_use_existing()?;
Ok(*GLOBAL_JVM
.get()
.expect("launch_or_use_existing must set GLOBAL_JVM"))
}
}
}
fn throw_java_runtime_exception(env: EnvPtr<'_>, message: &str) {
let mut jvm = Jvm(env);
let runtime_exception_clazz = crate::java::lang::RuntimeException::class(&mut jvm)
.expect("java/lang/RuntimeException not found");
let runtime_exception_clazz_ptr = runtime_exception_clazz.as_raw().as_ptr();
let encoded = cesu8::to_java_cesu8(message);
let c_string = unsafe { CString::from_vec_unchecked(encoded.into_owned()) };
let c_string_ptr = c_string.as_ptr();
unsafe {
env.invoke_unchecked(
|env| env.ThrowNew,
|jni, f| f(jni, runtime_exception_clazz_ptr, c_string_ptr),
);
};
}
fn error_to_java_exception(env: EnvPtr<'_>, error: Error<Local<'_, Throwable>>) {
let _ = match error {
Error::Thrown(t) => unsafe {
env.invoke_unchecked(|env| env.Throw, |env, f| f(env, t.as_raw().as_ptr()));
},
Error::JvmInternal(s) => {
throw_java_runtime_exception(env, &s);
}
Error::NullDeref => {
let mut jvm = Jvm(env);
let npe_clazz = crate::java::lang::NullPointerException::class(&mut jvm)
.expect("java/lang/NullPointerException not found");
let npe_clazz_ptr = npe_clazz.as_raw().as_ptr();
unsafe {
env.invoke_unchecked(
|env| env.ThrowNew,
|jni, f| f(jni, npe_clazz_ptr, std::ptr::null()),
);
}
}
e => {
throw_java_runtime_exception(env, &format!("{}", e));
}
};
}
pub unsafe fn native_function_returning_object<J, R>(
env: EnvPtr<'_>,
op: impl FnOnce() -> R,
) -> jni_sys::jobject
where
J: Upcast<crate::java::lang::Object> + Upcast<J>,
R: ToJavaImpl<J>,
{
init_jvm_from_native_function(env);
let _callback_guard = thread::attach_from_jni_callback(env);
let result = match std::panic::catch_unwind(AssertUnwindSafe(|| op())) {
Ok(result) => {
let mut jvm = Jvm(env);
let obj = result.to_java().do_jni(&mut jvm);
match obj {
Ok(Some(p)) => p.into_raw().as_ptr(),
Ok(None) => std::ptr::null_mut(),
Err(e) => {
error_to_java_exception(env, e);
std::ptr::null_mut()
}
}
}
Err(e) => {
let () = rust_panic_to_java_exception(env, e);
std::ptr::null_mut()
}
};
result
}
pub unsafe fn native_function_returning_scalar<J, R>(env: EnvPtr<'_>, op: impl FnOnce() -> R) -> J
where
J: JavaScalar,
R: ToJavaScalar<J>,
{
init_jvm_from_native_function(env);
let _callback_guard = thread::attach_from_jni_callback(env);
let result = match std::panic::catch_unwind(AssertUnwindSafe(|| op())) {
Ok(result) => {
let mut jvm = Jvm(env);
let scalar_result = R::to_java_scalar(&result, &mut jvm);
match scalar_result {
Ok(s) => s,
Err(e) => {
error_to_java_exception(env, e);
J::default()
}
}
}
Err(e) => {
rust_panic_to_java_exception(env, e);
J::default()
}
};
result
}
unsafe fn init_jvm_from_native_function(env: EnvPtr<'_>) -> Jvm<'_> {
let jvm = env.jvm_ptr().unwrap();
let global_jvm = GLOBAL_JVM.get_or_init(|| jvm);
assert_eq!(jvm, *global_jvm, "multiple JVM pointers in active use");
Jvm(env)
}
fn rust_panic_to_java_exception(env: EnvPtr<'_>, panic: Box<dyn Any + Send + 'static>) {
let message = if let Some(s) = panic.downcast_ref::<&'static str>() {
(*s).to_string()
} else if let Some(s) = panic.downcast_ref::<String>() {
s.clone()
} else {
"Unknown panic!".to_string()
};
throw_java_runtime_exception(env, &message);
}
pub(crate) fn unwrap_global_jvm() -> JvmPtr {
*GLOBAL_JVM.get().expect("JVM can't be unset")
}
pub struct Jvm<'jvm>(EnvPtr<'jvm>);
impl<'jvm> Jvm<'jvm> {
pub fn builder() -> JvmBuilder {
JvmBuilder::new()
}
pub fn attach_thread_permanently() -> crate::Result<()> {
thread::attach_permanently(get_or_default_init_jvm()?)?;
Ok(())
}
pub(crate) fn with<R>(
op: impl for<'a> FnOnce(&mut Jvm<'a>) -> crate::LocalResult<'a, R>,
) -> crate::Result<R> {
let jvm = get_or_default_init_jvm()?;
let mut guard = unsafe { thread::attach(jvm)? };
let mut jvm = Jvm(guard.env());
op(&mut jvm).map_err(|e| e.into_global(&mut jvm))
}
pub fn local<R>(&mut self, r: &R) -> Local<'jvm, R>
where
R: JavaObject,
{
Local::new(self.0, r)
}
pub fn global<R>(&mut self, r: &R) -> Java<R>
where
R: JavaObject,
{
Java::new(self.0, r)
}
#[doc(hidden)]
pub fn env(&self) -> EnvPtr<'jvm> {
self.0
}
fn register_native_methods(
&mut self,
java_functions: &[JavaFunction],
) -> crate::LocalResult<'jvm, ()> {
let mut sorted_by_class: HashMap<Local<'_, Class>, Vec<jni_sys::JNINativeMethod>> =
HashMap::default();
for java_function in java_functions {
let class = (java_function.class_fn)(self)?;
sorted_by_class
.entry(class)
.or_insert(vec![])
.push(jni_sys::JNINativeMethod {
name: java_function.name.as_ptr() as *mut c_char,
signature: java_function.signature.as_ptr() as *mut c_char,
fnPtr: java_function.pointer.as_ptr() as *mut c_void,
});
}
for (class, native_methods) in &sorted_by_class {
unsafe {
self.0
.register_native_methods(class.as_raw(), native_methods)?;
}
}
Ok(())
}
}
pub struct JvmBuilder {
options: Vec<String>,
#[cfg(feature = "dylibjvm")]
libjvm_path: Option<std::path::PathBuf>,
java_functions: Vec<JavaFunction>,
}
impl JvmBuilder {
fn new() -> Self {
let mut this = Self {
options: vec![],
#[cfg(feature = "dylibjvm")]
libjvm_path: None,
java_functions: vec![],
};
if cfg!(debug_assertions) {
this = this.custom("-Xcheck:jni");
}
if let Ok(classpath) = std::env::var("CLASSPATH") {
this = this.add_classpath(classpath);
}
this
}
pub fn add_classpath(self, classpath: impl Display) -> Self {
self.custom(format!("-Djava.class.path={classpath}"))
}
pub fn custom(mut self, opt_string: impl Into<String>) -> Self {
self.options.push(opt_string.into());
self
}
pub fn link(mut self, fns: impl IntoJavaFns) -> Self {
self.java_functions.extend(fns.into_java_fns());
self
}
#[cfg(feature = "dylibjvm")]
pub fn load_libjvm_at(mut self, path: impl AsRef<std::path::Path>) -> Self {
self.libjvm_path = Some(path.as_ref().into());
self
}
pub fn try_launch(self) -> Result<()> {
#[cfg(feature = "dylibjvm")]
if let Some(path) = self.libjvm_path {
crate::libjvm::libjvm_or_load_at(&path)?;
}
let mut already_exists = true;
GLOBAL_JVM.get_or_try_init(|| {
let jvm = unsafe { raw::try_create_jvm(self.options.into_iter()) }?;
already_exists = false;
Result::Ok(jvm)
})?;
if already_exists {
Err(Error::JvmAlreadyExists)
} else {
if !self.java_functions.is_empty() {
Jvm::with(|jvm| jvm.register_native_methods(&self.java_functions))?;
}
Ok(())
}
}
pub fn launch_or_use_existing(self) -> Result<()> {
let existing_jvm = unsafe { raw::existing_jvm() }?;
if let Some(jvm) = existing_jvm {
let _ = GLOBAL_JVM.set(jvm);
return Ok(());
}
match self.try_launch() {
Err(Error::JvmAlreadyExists) => {
GLOBAL_JVM.get_or_try_init(|| {
Result::Ok(unsafe { raw::existing_jvm() }?.expect("JVM should already exist"))
})?;
Ok(())
}
result => result,
}
}
}
pub unsafe trait JavaObject: 'static + Sized + JavaType + JavaView {
fn class<'jvm>(jvm: &mut Jvm<'jvm>) -> crate::LocalResult<'jvm, Local<'jvm, Class>>;
}
pub trait JavaView {
type OfOp<J>: FromRef<J>;
type OfOpWith<J, N>: FromRef<J>
where
N: FromRef<J>;
type OfObj<J>: FromRef<J>;
type OfObjWith<J, N>: FromRef<J>
where
N: FromRef<J>;
}
pub trait JavaObjectExt: Sized {
unsafe fn from_raw<'a>(ptr: ObjectPtr) -> &'a Self;
fn as_raw(&self) -> ObjectPtr;
}
impl<T: JavaObject> JavaObjectExt for T {
unsafe fn from_raw<'a>(ptr: ObjectPtr) -> &'a Self {
unsafe { ptr.as_ref() }
}
fn as_raw(&self) -> ObjectPtr {
let ptr: *const Self = self;
let jobj = ptr.cast_mut().cast::<jni_sys::_jobject>();
unsafe { ObjectPtr::new(jobj).unwrap_unchecked() }
}
}
pub unsafe trait JavaType: 'static {
fn array_class<'jvm>(jvm: &mut Jvm<'jvm>) -> crate::LocalResult<'jvm, Local<'jvm, Class>>;
}
unsafe impl<T: JavaObject> JavaType for T {
fn array_class<'jvm>(jvm: &mut Jvm<'jvm>) -> crate::LocalResult<'jvm, Local<'jvm, Class>> {
T::class(jvm)?.array_type().assert_not_null().do_jni(jvm)
}
}
pub trait JavaScalar: JavaType + Default {}
macro_rules! scalar {
($($rust:ty: $array_class:literal,)*) => {
$(
unsafe impl JavaType for $rust {
fn array_class<'jvm>(jvm: &mut Jvm<'jvm>) -> crate::LocalResult<'jvm, Local<'jvm, Class>> {
const CLASS_NAME: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked($array_class) };
static CLASS: OnceCell<Java<crate::java::lang::Class>> = OnceCell::new();
let global = CLASS.get_or_try_init::<_, crate::Error<Local<Throwable>>>(|| {
let class = find_class(jvm, CLASS_NAME)?;
Ok(jvm.global(&class))
})?;
Ok(jvm.local(global))
}
}
impl JavaScalar for $rust {}
)*
};
}
scalar! {
bool: b"[Z\0",
i8: b"[B\0",
i16: b"[S\0",
u16: b"[C\0",
i32: b"[I\0",
i64: b"[J\0",
f32: b"[F\0",
f64: b"[D\0",
}