Skip to content

Commit 2d6510c

Browse files
committed
Add Monitor, add doc comments, add is_same_object, fix ReferenceType generation
1 parent 808e316 commit 2d6510c

File tree

10 files changed

+233
-113
lines changed

10 files changed

+233
-113
lines changed

java-spaghetti-gen/src/emit_rust/structs.rs

+7-13
Original file line numberDiff line numberDiff line change
@@ -118,21 +118,15 @@ impl Struct {
118118

119119
let rust_name = &self.rust.struct_name;
120120
writeln!(out, "{attributes}{visibility} enum {rust_name}{{}}")?;
121-
if !self.java.is_static() {
122-
writeln!(
123-
out,
124-
"unsafe impl ::java_spaghetti::ReferenceType for {rust_name} {{\
125-
\n fn jni_get_class(__jni_env: ::java_spaghetti::Env) -> &'static ::java_spaghetti::JClass {{\
126-
\n Self::__class_global_ref(__jni_env)\
127-
\n }}\
128-
\n}}",
129-
)?;
130-
}
121+
131122
writeln!(
132123
out,
133-
"unsafe impl ::java_spaghetti::JniType for {rust_name} {{\
134-
\n fn static_with_jni_type<R>(callback: impl FnOnce(&str) -> R) -> R {{\
135-
\n callback({})\
124+
"unsafe impl ::java_spaghetti::ReferenceType for {rust_name} {{\
125+
\n fn jni_reference_type_name() -> ::std::borrow::Cow<'static, str> {{\
126+
\n ::std::borrow::Cow::Borrowed({})\
127+
\n }}\
128+
\n fn jni_get_class(__jni_env: ::java_spaghetti::Env) -> &'static ::java_spaghetti::JClass {{\
129+
\n Self::__class_global_ref(__jni_env)\
136130
\n }}\
137131
\n}}",
138132
StrEmitter(self.java.path().as_str()),

java-spaghetti/src/array.rs

+38-42
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use std::borrow::Cow;
12
use std::collections::HashMap;
23
use std::marker::PhantomData;
34
use std::ops::{Bound, RangeBounds};
@@ -6,12 +7,12 @@ use std::sync::{LazyLock, OnceLock, RwLock};
67

78
use jni_sys::*;
89

9-
use crate::{AsArg, Env, JClass, JniType, Local, Ref, ReferenceType, ThrowableType};
10+
use crate::{AsArg, Env, JClass, Local, Ref, ReferenceType, ThrowableType};
1011

1112
/// A Java Array of some POD-like type such as `bool`, `jbyte`, `jchar`, `jshort`, `jint`, `jlong`, `jfloat`, or `jdouble`.
1213
///
1314
/// Thread safety of avoiding [race conditions](https://www.ibm.com/docs/en/sdk-java-technology/8?topic=jni-synchronization)
14-
/// is not guaranteed.
15+
/// is not guaranteed. JNI `GetPrimitiveArrayCritical` cannot ensure exclusive access to the array, so it is not used here.
1516
///
1617
/// See also [ObjectArray] for arrays of reference types.
1718
///
@@ -29,35 +30,35 @@ pub trait PrimitiveArray<T>: Sized + ReferenceType
2930
where
3031
T: Clone + Default,
3132
{
32-
/// Uses JNI `New{Type}Array` to create a new java array containing "size" elements.
33+
/// Uses JNI `New{Type}Array` to create a new Java array containing "size" elements.
3334
fn new<'env>(env: Env<'env>, size: usize) -> Local<'env, Self>;
3435

35-
/// Uses JNI `GetArrayLength` to get the length of the java array.
36+
/// Uses JNI `GetArrayLength` to get the length of the Java array.
3637
fn len(self: &Ref<'_, Self>) -> usize;
3738

38-
/// Uses JNI `Get{Type}ArrayRegion` to read the contents of the java array within `[start .. start + elements.len()]`.
39+
/// Uses JNI `Get{Type}ArrayRegion` to read the contents of the Java array within `[start .. start + elements.len()]`.
3940
///
4041
/// Panics if the index is out of bound.
4142
fn get_region(self: &Ref<'_, Self>, start: usize, elements: &mut [T]);
4243

43-
/// Uses JNI `Set{Type}ArrayRegion` to set the contents of the java array within `[start .. start + elements.len()]`.
44+
/// Uses JNI `Set{Type}ArrayRegion` to set the contents of the Java array within `[start .. start + elements.len()]`.
4445
///
4546
/// Panics if the index is out of bound.
4647
fn set_region(self: &Ref<'_, Self>, start: usize, elements: &[T]);
4748

48-
/// Uses JNI `New{Type}Array` + `Set{Type}ArrayRegion` to create a new java array containing a copy of "elements".
49+
/// Uses JNI `New{Type}Array` + `Set{Type}ArrayRegion` to create a new Java array containing a copy of "elements".
4950
fn new_from<'env>(env: Env<'env>, elements: &[T]) -> Local<'env, Self> {
5051
let array = Self::new(env, elements.len());
5152
array.set_region(0, elements);
5253
array
5354
}
5455

55-
/// Uses JNI `GetArrayLength` to get the length of the java array, returns `true` if it is 0.
56+
/// Uses JNI `GetArrayLength` to get the length of the Java array, returns `true` if it is 0.
5657
fn is_empty(self: &Ref<'_, Self>) -> bool {
5758
self.len() == 0
5859
}
5960

60-
/// Uses JNI `GetArrayLength` + `Get{Type}ArrayRegion` to read the contents of the java array within given range
61+
/// Uses JNI `GetArrayLength` + `Get{Type}ArrayRegion` to read the contents of the Java array within given range
6162
/// into a new `Vec`.
6263
///
6364
/// Panics if the index is out of bound.
@@ -86,7 +87,7 @@ where
8687
vec
8788
}
8889

89-
/// Uses JNI `GetArrayLength` + `Get{Type}ArrayRegion` to read the contents of the entire java array into a new `Vec`.
90+
/// Uses JNI `GetArrayLength` + `Get{Type}ArrayRegion` to read the contents of the entire Java array into a new `Vec`.
9091
fn as_vec(self: &Ref<'_, Self>) -> Vec<T> {
9192
self.get_region_as_vec(0..self.len())
9293
}
@@ -98,14 +99,12 @@ macro_rules! primitive_array {
9899
pub enum $name {}
99100

100101
unsafe impl ReferenceType for $name {
102+
fn jni_reference_type_name() -> Cow<'static, str> {
103+
Cow::Borrowed($type_str)
104+
}
101105
fn jni_get_class(env: Env) -> &'static JClass {
102106
static CLASS_CACHE: OnceLock<JClass> = OnceLock::new();
103-
CLASS_CACHE.get_or_init(|| Self::static_with_jni_type(|t| unsafe { env.require_class(t) }))
104-
}
105-
}
106-
unsafe impl JniType for $name {
107-
fn static_with_jni_type<R>(callback: impl FnOnce(&str) -> R) -> R {
108-
callback($type_str)
107+
CLASS_CACHE.get_or_init(|| unsafe { env.require_class(&Self::jni_reference_type_name()) })
109108
}
110109
}
111110

@@ -116,8 +115,8 @@ macro_rules! primitive_array {
116115
let jnienv = env.as_raw();
117116
unsafe {
118117
let object = ((**jnienv).v1_2.$new_array)(jnienv, size);
119-
let exception = ((**jnienv).v1_2.ExceptionOccurred)(jnienv);
120-
assert!(exception.is_null()); // Only sane exception here is an OOM exception
118+
assert!(!object.is_null(), "OOM");
119+
env.exception_check_raw().expect("OOM"); // Only sane exception here is an OOM exception
121120
Local::from_raw(env, object)
122121
}
123122
}
@@ -203,35 +202,32 @@ primitive_array! { DoubleArray, "[D\0", jdouble { NewDoubleArray SetDoubleArra
203202
/// See also [PrimitiveArray] for arrays of reference types.
204203
pub struct ObjectArray<T: ReferenceType, E: ThrowableType>(core::convert::Infallible, PhantomData<(T, E)>);
205204

206-
// NOTE: This is a performance compromise for returning `&'static JClass`,
207-
// still faster than non-cached `require_class`.
205+
// NOTE: This is a performance compromise for returning `&'static JClass`, still faster than non-cached `FindClass`.
208206
static OBJ_ARR_CLASSES: LazyLock<RwLock<HashMap<String, &'static JClass>>> =
209207
LazyLock::new(|| RwLock::new(HashMap::new()));
210208

211209
unsafe impl<T: ReferenceType, E: ThrowableType> ReferenceType for ObjectArray<T, E> {
212-
fn jni_get_class(env: Env) -> &'static JClass {
213-
Self::static_with_jni_type(|t| {
214-
let class_map_reader = OBJ_ARR_CLASSES.read().unwrap();
215-
if let Some(&class) = class_map_reader.get(t) {
216-
class
217-
} else {
218-
drop(class_map_reader);
219-
let class: &'static JClass = Box::leak(Box::new(unsafe { env.require_class(t) }));
220-
let _ = OBJ_ARR_CLASSES.write().unwrap().insert(t.to_string(), class);
221-
class
222-
}
223-
})
210+
fn jni_reference_type_name() -> Cow<'static, str> {
211+
let inner = T::jni_reference_type_name();
212+
Cow::Owned(format!("[L{};\0", inner.trim_end_matches("\0")))
224213
}
225-
}
226-
227-
unsafe impl<T: ReferenceType, E: ThrowableType> JniType for ObjectArray<T, E> {
228-
fn static_with_jni_type<R>(callback: impl FnOnce(&str) -> R) -> R {
229-
T::static_with_jni_type(|inner| callback(format!("[L{};\0", inner.trim_end_matches("\0")).as_str()))
214+
fn jni_get_class(env: Env) -> &'static JClass {
215+
let t = Self::jni_reference_type_name();
216+
let class_map_reader = OBJ_ARR_CLASSES.read().unwrap();
217+
if let Some(&class) = class_map_reader.get(t.as_ref()) {
218+
class
219+
} else {
220+
drop(class_map_reader);
221+
let class = unsafe { env.require_class(&t) };
222+
let class_leaked: &'static JClass = Box::leak(Box::new(class));
223+
let _ = OBJ_ARR_CLASSES.write().unwrap().insert(t.to_string(), class_leaked);
224+
class_leaked
225+
}
230226
}
231227
}
232228

233229
impl<T: ReferenceType, E: ThrowableType> ObjectArray<T, E> {
234-
/// Uses JNI `NewObjectArray` to create a new java object array.
230+
/// Uses JNI `NewObjectArray` to create a new Java object array.
235231
pub fn new<'env>(env: Env<'env>, size: usize) -> Local<'env, Self> {
236232
assert!(size <= i32::MAX as usize); // jsize == jint == i32
237233
let class = T::jni_get_class(env).as_raw();
@@ -242,7 +238,7 @@ impl<T: ReferenceType, E: ThrowableType> ObjectArray<T, E> {
242238
let fill = null_mut();
243239
((**env).v1_2.NewObjectArray)(env, size, class, fill)
244240
};
245-
// Only sane exception here is an OOM exception
241+
assert!(!object.is_null(), "OOM");
246242
env.exception_check::<E>().map_err(|_| "OOM").unwrap();
247243
unsafe { Local::from_raw(env, object) }
248244
}
@@ -256,7 +252,7 @@ impl<T: ReferenceType, E: ThrowableType> ObjectArray<T, E> {
256252
}
257253
}
258254

259-
/// Uses JNI `NewObjectArray` to create a new java object array of the exact size, then sets its items
255+
/// Uses JNI `NewObjectArray` to create a new Java object array of the exact size, then sets its items
260256
/// with the iterator of JNI (null?) references.
261257
pub fn new_from<'env>(env: Env<'env>, elements: impl ExactSizeIterator<Item = impl AsArg<T>>) -> Local<'env, Self> {
262258
let size = elements.len();
@@ -269,13 +265,13 @@ impl<T: ReferenceType, E: ThrowableType> ObjectArray<T, E> {
269265
array
270266
}
271267

272-
/// Uses JNI `GetArrayLength` to get the length of the java array.
268+
/// Uses JNI `GetArrayLength` to get the length of the Java array.
273269
pub fn len(self: &Ref<'_, Self>) -> usize {
274270
let env = self.env().as_raw();
275271
unsafe { ((**env).v1_2.GetArrayLength)(env, self.as_raw()) as usize }
276272
}
277273

278-
/// Uses JNI `GetArrayLength` to get the length of the java array, returns `true` if it is 0.
274+
/// Uses JNI `GetArrayLength` to get the length of the Java array, returns `true` if it is 0.
279275
pub fn is_empty(self: &Ref<'_, Self>) -> bool {
280276
self.len() == 0
281277
}

java-spaghetti/src/env.rs

+16-17
Original file line numberDiff line numberDiff line change
@@ -140,22 +140,31 @@ impl<'env> Env<'env> {
140140
CLASS_LOADER.store(classloader, Ordering::Relaxed);
141141
}
142142

143+
/// Checks if an exception has occurred; if occurred, it clears the exception to make the next
144+
/// JNI call possible, then it returns the exception as an `Err`.
145+
///
143146
/// XXX: Make this method public after making sure that it has a proper name.
144147
/// Note that there is `ExceptionCheck` in JNI functions, which does not create a
145148
/// local reference to the exception object.
146149
pub(crate) fn exception_check<E: ThrowableType>(self) -> Result<(), Local<'env, E>> {
150+
self.exception_check_raw()
151+
.map_err(|throwable| unsafe { Local::from_raw(self, throwable) })
152+
}
153+
154+
/// The same as `exception_check`, except that it may return a raw local reference of the exception.
155+
pub(crate) fn exception_check_raw(self) -> Result<(), jthrowable> {
147156
unsafe {
148157
let exception = ((**self.env).v1_2.ExceptionOccurred)(self.env);
149158
if exception.is_null() {
150159
Ok(())
151160
} else {
152161
((**self.env).v1_2.ExceptionClear)(self.env);
153-
Err(Local::from_raw(self, exception))
162+
Err(exception as jthrowable)
154163
}
155164
}
156165
}
157166

158-
unsafe fn exception_to_string(self, exception: jobject) -> String {
167+
pub(crate) unsafe fn raw_exception_to_string(self, exception: jthrowable) -> String {
159168
static METHOD_GET_MESSAGE: OnceLock<JMethodID> = OnceLock::new();
160169
let throwable_get_message = *METHOD_GET_MESSAGE.get_or_init(|| {
161170
// use JNI FindClass to avoid infinte recursion.
@@ -165,12 +174,8 @@ impl<'env> Env<'env> {
165174

166175
let message =
167176
((**self.env).v1_2.CallObjectMethodA)(self.env, exception, throwable_get_message.as_raw(), ptr::null_mut());
168-
let e2: *mut _jobject = ((**self.env).v1_2.ExceptionOccurred)(self.env);
169-
if !e2.is_null() {
170-
((**self.env).v1_2.ExceptionClear)(self.env);
171-
panic!("exception happened calling Throwable.getMessage()");
172-
}
173-
177+
self.exception_check_raw()
178+
.expect("exception happened calling Throwable.getMessage()");
174179
StringChars::from_env_jstring(self, message).to_string_lossy()
175180
}
176181

@@ -201,12 +206,10 @@ impl<'env> Env<'env> {
201206
let args = [jvalue { l: string }];
202207
let result: *mut _jobject =
203208
((**self.env).v1_2.CallObjectMethodA)(self.env, classloader, cl_method.as_raw(), args.as_ptr());
204-
let exception: *mut _jobject = ((**self.env).v1_2.ExceptionOccurred)(self.env);
205-
if !exception.is_null() {
206-
((**self.env).v1_2.ExceptionClear)(self.env);
209+
if let Err(exception) = self.exception_check_raw() {
207210
panic!(
208211
"exception happened calling loadClass(): {}",
209-
self.exception_to_string(exception)
212+
self.raw_exception_to_string(exception)
210213
);
211214
} else if result.is_null() {
212215
panic!("loadClass() returned null");
@@ -224,11 +227,7 @@ impl<'env> Env<'env> {
224227
unsafe fn require_class_jni(self, class: &str) -> Option<JClass> {
225228
assert!(class.ends_with('\0'));
226229
let cls = ((**self.env).v1_2.FindClass)(self.env, class.as_ptr() as *const c_char);
227-
let exception: *mut _jobject = ((**self.env).v1_2.ExceptionOccurred)(self.env);
228-
if !exception.is_null() {
229-
((**self.env).v1_2.ExceptionClear)(self.env);
230-
return None;
231-
}
230+
self.exception_check_raw().ok()?;
232231
if cls.is_null() {
233232
return None;
234233
}

0 commit comments

Comments
 (0)