Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 58 additions & 22 deletions java-spaghetti-gen/src/emit/class_proxy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ use super::fields::RustTypeFlavor;
use super::methods::Method;
use crate::emit::Context;
use crate::emit::fields::emit_type;
use crate::parser_util::Id;

impl Class {
#[allow(clippy::vec_init_then_push)]
Expand All @@ -22,9 +21,6 @@ impl Class {

let rust_name = format_ident!("{}", &self.rust.struct_name);

let object = context
.java_to_rust_path(Id("java/lang/Object"), &self.rust.mod_)
.unwrap();
let throwable = context.throwable_rust_path(&self.rust.mod_);
let rust_proxy_name = format_ident!("{}Proxy", &self.rust.struct_name);

Expand All @@ -36,6 +32,7 @@ impl Class {
self.java.path().as_str().replace("$", "_")
);

let mut native_regs = Vec::new();
for method in methods {
let Some(rust_name) = method.rust_name() else { continue };
if method.java.is_static()
Expand All @@ -61,6 +58,14 @@ impl Class {
let native_name = format_ident!("{native_name}");
let rust_name = format_ident!("{rust_name}");

let mut native_method_desc = method.java.descriptor().to_string();
native_method_desc.insert(1, 'J');
native_regs.push((
cstring(&format!("native_{}", method.java.name())),
cstring(&native_method_desc),
native_name.clone(),
));

let ret = match &method.java.descriptor.return_type {
ReturnDescriptor::Void => quote!(()),
ReturnDescriptor::Return(desc) => emit_type(
Expand Down Expand Up @@ -146,45 +151,76 @@ impl Class {
_class: *mut (), // self class, ignore
ptr: i64,
) {
let ptr: *mut std::sync::Arc<dyn #rust_proxy_name> = ::std::ptr::with_exposed_provenance_mut(ptr as usize);
let ptr: *mut ::std::sync::Arc<dyn #rust_proxy_name> = ::std::ptr::with_exposed_provenance_mut(ptr as usize);
let _ = unsafe { Box::from_raw(ptr) };
}
));

let java_proxy_path = cstring(&java_proxy_path);

// XXX: use `OnceLock::get_or_try_init` for `__METHOD` when it becomes stable.
contents.extend(quote!(
pub fn new_proxy<'env>(
env: ::java_spaghetti::Env<'env>,
proxy: ::std::sync::Arc<dyn #rust_proxy_name>,
proxy_class: ::std::option::Option<::java_spaghetti::JClass>,
) -> Result<::java_spaghetti::Local<'env, Self>, ::java_spaghetti::Local<'env, #throwable>> {
static __CLASS: ::std::sync::OnceLock<::java_spaghetti::Global<#object>> =
::std::sync::OnceLock::new();
static __CLASS: ::std::sync::OnceLock<::java_spaghetti::JClass> = ::std::sync::OnceLock::new();
let __jni_class = __CLASS
.get_or_init(|| unsafe {
::java_spaghetti::Local::from_raw(env, env.require_class(#java_proxy_path),)
.as_global()
})
.as_raw();
let required = env.require_class(#java_proxy_path);
if let Ok(proxy_class) = required {
proxy_class
} else if let Some(proxy_class) = proxy_class {
let bin_name = env.get_class_name(&proxy_class).replace('.', "/");
let expected = #java_proxy_path.to_string_lossy();
if bin_name != expected {
panic!("wrong proxy_class, expected: {}, provided: {}", expected, bin_name)
}
Self::register_proxy_methods(env, &proxy_class);
proxy_class
} else {
panic!("{}", required.unwrap_err())
}
});

let b = ::std::boxed::Box::new(proxy);
let ptr = ::std::boxed::Box::into_raw(b);

static __METHOD: ::std::sync::OnceLock<::java_spaghetti::JMethodID> = ::std::sync::OnceLock::new();
unsafe {
let __jni_args = [::java_spaghetti::sys::jvalue {
let __jni_args = &[::java_spaghetti::sys::jvalue {
j: ptr.expose_provenance() as i64,
}];
let __jni_method = __METHOD
.get_or_init(|| {
::java_spaghetti::JMethodID::from_raw(env.require_method(
__jni_class,
c"<init>",
c"(J)V",
))
})
.as_raw();
env.new_object_a(__jni_class, __jni_method, __jni_args.as_ptr())
let __jni_method = if let Some(&__jni_method) = __METHOD.get() {
__jni_method
} else {
let __jni_method = env.require_method(__jni_class, c"<init>", c"(J)V")?;
*__METHOD.get_or_init(|| __jni_method)
};
env.new_object_a(__jni_class, __jni_method, __jni_args)
}
}
));

let mut register_calls = TokenStream::new();
for (native_method_name, descriptor, extern_name) in native_regs {
register_calls.extend(quote!(
{
let method_name = #native_method_name;
let descriptor = #descriptor;
let fn_ptr = #extern_name as *mut _;
let _ = env.register_native_method(proxy_class, method_name, descriptor, fn_ptr);
}
));
}
contents.extend(quote!(
fn register_proxy_methods<'env>(
env: ::java_spaghetti::Env<'env>,
proxy_class: &::java_spaghetti::JClass,
) {
unsafe {
#register_calls
}
}
));
Expand Down
34 changes: 7 additions & 27 deletions java-spaghetti-gen/src/emit/classes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,11 +103,6 @@ impl Class {

let rust_name = format_ident!("{}", &self.rust.struct_name);

let referencetype_impl = match self.java.is_static() {
true => quote!(),
false => quote!(unsafe impl ::java_spaghetti::ReferenceType for #rust_name {}),
};

let mut out = TokenStream::new();

let java_path = cstring(self.java.path().as_str());
Expand All @@ -117,11 +112,13 @@ impl Class {
#attributes
#visibility enum #rust_name {}

#referencetype_impl

unsafe impl ::java_spaghetti::JniType for #rust_name {
fn static_with_jni_type<R>(callback: impl FnOnce(&::std::ffi::CStr) -> R) -> R {
callback(#java_path)
unsafe impl ::java_spaghetti::ReferenceType for #rust_name {
fn jni_reference_type_name() -> ::std::borrow::Cow<'static, ::std::ffi::CStr> {
::std::borrow::Cow::Borrowed(#java_path)
}
unsafe fn jni_class_cache_once_lock() -> &'static ::std::sync::OnceLock<::java_spaghetti::JClass> {
static __CLASS: ::std::sync::OnceLock<::java_spaghetti::JClass> = ::std::sync::OnceLock::new();
&__CLASS
}
}
));
Expand All @@ -147,23 +144,6 @@ impl Class {

let mut contents = TokenStream::new();

let object = context
.java_to_rust_path(Id("java/lang/Object"), &self.rust.mod_)
.unwrap();

let class = cstring(self.java.path().as_str());

contents.extend(quote!(
fn __class_global_ref(__jni_env: ::java_spaghetti::Env) -> ::java_spaghetti::sys::jobject {
static __CLASS: ::std::sync::OnceLock<::java_spaghetti::Global<#object>> = ::std::sync::OnceLock::new();
__CLASS
.get_or_init(|| unsafe {
::java_spaghetti::Local::from_raw(__jni_env, __jni_env.require_class(#class)).as_global()
})
.as_raw()
}
));

let mut methods: Vec<Method> = self
.java
.methods()
Expand Down
12 changes: 7 additions & 5 deletions java-spaghetti-gen/src/emit/fields.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ impl<'a> Field<'a> {
let set_field = format_ident!("set{static_fragment}_{field_fragment}_field");

let this_or_class = match self.java.is_static() {
false => quote!(self.as_raw()),
false => quote!(self),
true => quote!(__jni_class),
};

Expand All @@ -142,11 +142,12 @@ impl<'a> Field<'a> {
#[doc = #get_docs]
#attributes
pub fn #get<'env>(#env_param) -> #rust_get_type {
use ::java_spaghetti::ReferenceType;
static __FIELD: ::std::sync::OnceLock<::java_spaghetti::JFieldID> = ::std::sync::OnceLock::new();
#env_let
let __jni_class = Self::__class_global_ref(__jni_env);
let __jni_class = Self::jni_get_class(__jni_env).unwrap();
unsafe {
let __jni_field = __FIELD.get_or_init(|| ::java_spaghetti::JFieldID::from_raw(__jni_env.#require_field(__jni_class, #java_name, #descriptor))).as_raw();
let __jni_field = *__FIELD.get_or_init(|| __jni_env.#require_field(__jni_class, #java_name, #descriptor));
__jni_env.#get_field(#this_or_class, __jni_field)
}
}
Expand All @@ -164,11 +165,12 @@ impl<'a> Field<'a> {
#[doc = #set_docs]
#attributes
pub fn #set<#lifetimes>(#env_param, value: #rust_set_type) {
use ::java_spaghetti::ReferenceType;
static __FIELD: ::std::sync::OnceLock<::java_spaghetti::JFieldID> = ::std::sync::OnceLock::new();
#env_let
let __jni_class = Self::__class_global_ref(__jni_env);
let __jni_class = Self::jni_get_class(__jni_env).unwrap();
unsafe {
let __jni_field = __FIELD.get_or_init(|| ::java_spaghetti::JFieldID::from_raw(__jni_env.#require_field(__jni_class, #java_name, #descriptor))).as_raw();
let __jni_field = *__FIELD.get_or_init(|| __jni_env.#require_field(__jni_class, #java_name, #descriptor));
__jni_env.#set_field(#this_or_class, __jni_field, value);
}
}
Expand Down
22 changes: 13 additions & 9 deletions java-spaghetti-gen/src/emit/methods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,28 +141,32 @@ impl<'a> Method<'a> {
let method_name = format_ident!("{method_name}");

let call = if self.java.is_constructor() {
quote!(__jni_env.new_object_a(__jni_class, __jni_method, __jni_args.as_ptr()))
quote!(__jni_env.new_object_a(__jni_class, __jni_method, __jni_args))
} else if self.java.is_static() {
let call = format_ident!("call_static_{ret_method_fragment}_method_a");
quote!( __jni_env.#call(__jni_class, __jni_method, __jni_args.as_ptr()))
quote!( __jni_env.#call(__jni_class, __jni_method, __jni_args))
} else {
let call = format_ident!("call_{ret_method_fragment}_method_a");
quote!( __jni_env.#call(self.as_raw(), __jni_method, __jni_args.as_ptr()))
quote!( __jni_env.#call(self, __jni_method, __jni_args))
};

// XXX: use `OnceLock::get_or_try_init` when it becomes stable.
out.extend(quote!(
#[doc = #docs]
#attributes
pub fn #method_name<'env>(#params_decl) -> ::std::result::Result<#ret_decl, ::java_spaghetti::Local<'env, #throwable>> {
use ::java_spaghetti::ReferenceType;
static __METHOD: ::std::sync::OnceLock<::java_spaghetti::JMethodID> = ::std::sync::OnceLock::new();
unsafe {
let __jni_args = [#params_array];
let __jni_args = &[#params_array];
#env_let
let __jni_class = Self::__class_global_ref(__jni_env);
let __jni_method = __METHOD.get_or_init(||
::java_spaghetti::JMethodID::from_raw(__jni_env.#require_method(__jni_class, #java_name, #descriptor))
).as_raw();

let __jni_class = Self::jni_get_class(__jni_env).unwrap();
let __jni_method = if let Some(&__jni_method) = __METHOD.get() {
__jni_method
} else {
let __jni_method = __jni_env.#require_method(__jni_class, #java_name, #descriptor)?;
*__METHOD.get_or_init(|| __jni_method)
};
#call
}
}
Expand Down
65 changes: 40 additions & 25 deletions java-spaghetti/src/array.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
use std::borrow::Cow;
use std::collections::HashMap;
use std::ffi::{CStr, CString};
use std::marker::PhantomData;
use std::ops::{Bound, RangeBounds};
use std::ptr::null_mut;
use std::sync::{LazyLock, OnceLock, RwLock};

use jni_sys::*;

use crate::{AsArg, Env, JniType, Local, Ref, ReferenceType, ThrowableType};
use crate::{AsArg, Env, JClass, Local, Ref, ReferenceType, ThrowableType};

/// A Java Array of some POD-like type such as `bool`, `jbyte`, `jchar`, `jshort`, `jint`, `jlong`, `jfloat`, or `jdouble`.
///
Expand Down Expand Up @@ -96,10 +99,13 @@ macro_rules! primitive_array {
/// A [PrimitiveArray] implementation.
pub enum $name {}

unsafe impl ReferenceType for $name {}
unsafe impl JniType for $name {
fn static_with_jni_type<R>(callback: impl FnOnce(&CStr) -> R) -> R {
callback($type_str)
unsafe impl ReferenceType for $name {
fn jni_reference_type_name() -> Cow<'static, CStr> {
Cow::Borrowed($type_str)
}
unsafe fn jni_class_cache_once_lock() -> &'static OnceLock<JClass> {
static CLASS_CACHE: OnceLock<JClass> = OnceLock::new();
&CLASS_CACHE
}
}

Expand All @@ -110,8 +116,7 @@ macro_rules! primitive_array {
let jnienv = env.as_raw();
unsafe {
let object = ((**jnienv).v1_2.$new_array)(jnienv, size);
let exception = ((**jnienv).v1_2.ExceptionOccurred)(jnienv);
assert!(exception.is_null()); // Only sane exception here is an OOM exception
env.exception_check_raw().expect("OOM");
Local::from_raw(env, object)
}
}
Expand Down Expand Up @@ -197,26 +202,41 @@ primitive_array! { DoubleArray, c"[D", jdouble { NewDoubleArray SetDoubleArray
/// See also [PrimitiveArray] for arrays of reference types.
pub struct ObjectArray<T: ReferenceType, E: ThrowableType>(core::convert::Infallible, PhantomData<(T, E)>);

unsafe impl<T: ReferenceType, E: ThrowableType> ReferenceType for ObjectArray<T, E> {}

unsafe impl<T: ReferenceType, E: ThrowableType> JniType for ObjectArray<T, E> {
fn static_with_jni_type<R>(callback: impl FnOnce(&CStr) -> R) -> R {
T::static_with_jni_type(|inner| {
let inner = inner.to_bytes();
let mut buf = Vec::with_capacity(inner.len() + 4);
buf.extend_from_slice(b"[L");
buf.extend_from_slice(inner);
buf.extend_from_slice(b";");
callback(&CString::new(buf).unwrap())
})
// NOTE: This is a performance compromise for returning `&'static JClass`, still faster than non-cached `FindClass`.
static OBJ_ARR_CLASSES: LazyLock<RwLock<HashMap<CString, &'static OnceLock<JClass>>>> =
LazyLock::new(|| RwLock::new(HashMap::new()));

unsafe impl<T: ReferenceType, E: ThrowableType> ReferenceType for ObjectArray<T, E> {
fn jni_reference_type_name() -> Cow<'static, CStr> {
let item_type = T::jni_reference_type_name();
let item_type = item_type.to_string_lossy();
let array_type = if !item_type.starts_with('[') {
format!("[L{item_type};")
} else {
format!("[{item_type}")
};
Cow::Owned(CString::new(array_type).unwrap())
}

unsafe fn jni_class_cache_once_lock() -> &'static OnceLock<JClass> {
let t = Self::jni_reference_type_name();
let class_map_reader = OBJ_ARR_CLASSES.read().unwrap();
if let Some(&once_lock) = class_map_reader.get(t.as_ref()) {
once_lock
} else {
drop(class_map_reader);
let once_lock: &'static OnceLock<_> = Box::leak(Box::new(OnceLock::new()));
let _ = OBJ_ARR_CLASSES.write().unwrap().insert(t.into_owned(), once_lock);
once_lock
}
}
}

impl<T: ReferenceType, E: ThrowableType> ObjectArray<T, E> {
/// Uses JNI `NewObjectArray` to create a new Java object array.
pub fn new<'env>(env: Env<'env>, size: usize) -> Local<'env, Self> {
assert!(size <= i32::MAX as usize); // jsize == jint == i32
let class = T::static_with_jni_type(|t| unsafe { env.require_class(t) });
let class = T::jni_get_class(env).unwrap().as_raw();
let size = size as jsize;

let object = unsafe {
Expand All @@ -226,11 +246,6 @@ impl<T: ReferenceType, E: ThrowableType> ObjectArray<T, E> {
};
// Only sane exception here is an OOM exception
env.exception_check::<E>().map_err(|_| "OOM").unwrap();

unsafe {
let env = env.as_raw();
((**env).v1_2.DeleteLocalRef)(env, class);
}
unsafe { Local::from_raw(env, object) }
}

Expand Down
Loading