java_oxide/
array.rs

1use crate::{AsArg, Env, JniType, Local, Ref, ReferenceType, ThrowableType};
2use jni_sys::*;
3use std::{
4    ffi::{CStr, CString},
5    marker::PhantomData,
6    ops::{Bound, RangeBounds},
7    ptr::null_mut,
8};
9
10/// A Java Array of some POD-like type such as `bool`, `jbyte`, `jchar`, `jshort`, `jint`, `jlong`, `jfloat`, or `jdouble`.
11///
12/// Thread safety of avoiding [race conditions](https://www.ibm.com/docs/en/sdk-java-technology/8?topic=jni-synchronization)
13/// is not guaranteed. JNI `GetPrimitiveArrayCritical` cannot ensure exclusive access to the array, so it is not used here.
14///
15/// See also [ObjectArray] for arrays of reference types.
16///
17/// | JNI Type      | PrimitiveArray Implementation |
18/// | ------------- | ----------------- |
19/// | [bool]\[\]    | [BooleanArray]    |
20/// | [jbyte]\[\]   | [ByteArray]       |
21/// | [jchar]\[\]   | [CharArray]       |
22/// | [jint]\[\]    | [IntArray]        |
23/// | [jlong]\[\]   | [LongArray]       |
24/// | [jfloat]\[\]  | [FloatArray]      |
25/// | [jdouble]\[\] | [DoubleArray]     |
26///
27pub trait PrimitiveArray<T>: Sized + ReferenceType
28where
29    T: Clone + Default,
30{
31    /// Uses JNI `New{Type}Array` to create a new Java array containing "size" elements.
32    fn new<'env>(env: Env<'env>, size: usize) -> Local<'env, Self>;
33
34    /// Uses JNI `GetArrayLength` to get the length of the Java array.
35    fn len(self: &Ref<'_, Self>) -> usize;
36
37    /// Uses JNI `GetArrayLength` to get the length of the Java array, returns `true` if it is 0.
38    fn is_empty(self: &Ref<'_, Self>) -> bool {
39        self.len() == 0
40    }
41
42    /// Uses JNI `Get{Type}ArrayRegion` to read the contents of the Java array within `[start .. start + elements.len()]`.
43    ///
44    /// Panics if the index is out of bound.
45    fn get_region(self: &Ref<'_, Self>, start: usize, elements: &mut [T]);
46
47    /// Uses JNI `Set{Type}ArrayRegion` to set the contents of the Java array within `[start .. start + elements.len()]`.
48    ///
49    /// Panics if the index is out of bound.
50    fn set_region(self: &Ref<'_, Self>, start: usize, elements: &[T]);
51
52    /// Uses JNI `New{Type}Array` + `Set{Type}ArrayRegion` to create a new Java array containing a copy of "elements".
53    fn new_from<'env>(env: Env<'env>, elements: &[T]) -> Local<'env, Self> {
54        let array = Self::new(env, elements.len());
55        array.set_region(0, elements);
56        array
57    }
58
59    /// Uses JNI `GetArrayLength` + `Get{Type}ArrayRegion` to read the contents of the Java array within given range
60    /// into a new `Vec`.
61    ///
62    /// Panics if the index is out of bound.
63    fn get_region_as_vec(self: &Ref<'_, Self>, range: impl RangeBounds<usize>) -> Vec<T> {
64        let len = self.len();
65
66        let start = match range.start_bound() {
67            Bound::Unbounded => 0,
68            Bound::Included(n) => *n,
69            Bound::Excluded(n) => *n + 1,
70        };
71
72        let end = match range.end_bound() {
73            Bound::Unbounded => len,
74            Bound::Included(n) => *n + 1,
75            Bound::Excluded(n) => *n,
76        };
77
78        assert!(start <= end);
79        assert!(end <= len);
80        let vec_len = end - start;
81
82        let mut vec = Vec::new();
83        vec.resize(vec_len, Default::default());
84        self.get_region(start, &mut vec[..]);
85        vec
86    }
87
88    /// Uses JNI `GetArrayLength` + `Get{Type}ArrayRegion` to read the contents of the entire Java array into a new `Vec`.
89    fn as_vec(self: &Ref<'_, Self>) -> Vec<T> {
90        self.get_region_as_vec(0..self.len())
91    }
92}
93
94macro_rules! primitive_array {
95    ($name:ident, $type_str:expr, $type:ident { $new_array:ident $set_region:ident $get_region:ident } ) => {
96        /// A [PrimitiveArray] implementation.
97        pub enum $name {}
98
99        unsafe impl ReferenceType for $name {}
100        unsafe impl JniType for $name {
101            fn static_with_jni_type<R>(callback: impl FnOnce(&CStr) -> R) -> R {
102                callback($type_str)
103            }
104        }
105
106        impl PrimitiveArray<$type> for $name {
107            fn new<'env>(env: Env<'env>, size: usize) -> Local<'env, Self> {
108                assert!(size <= i32::MAX as usize); // jsize == jint == i32
109                let size = size as jsize;
110                let jnienv = env.as_raw();
111                unsafe {
112                    let object = ((**jnienv).v1_2.$new_array)(jnienv, size);
113                    let exception = ((**jnienv).v1_2.ExceptionOccurred)(jnienv);
114                    assert!(exception.is_null()); // Only sane exception here is an OOM exception
115                    Local::from_raw(env, object)
116                }
117            }
118
119            fn new_from<'env>(env: Env<'env>, elements: &[$type]) -> Local<'env, Self> {
120                let array = Self::new(env, elements.len());
121                let size = elements.len() as jsize;
122                let env = array.env().as_raw();
123                unsafe {
124                    ((**env).v1_1.$set_region)(
125                        env,
126                        array.as_raw(),
127                        0,
128                        size,
129                        elements.as_ptr() as *const _,
130                    );
131                }
132                array
133            }
134
135            fn len(self: &Ref<'_, Self>) -> usize {
136                let env = self.env().as_raw();
137                unsafe { ((**env).v1_2.GetArrayLength)(env, self.as_raw()) as usize }
138            }
139
140            fn get_region(self: &Ref<'_, Self>, start: usize, elements: &mut [$type]) {
141                assert!(start <= i32::MAX as usize); // jsize == jint == i32
142                assert!(elements.len() <= i32::MAX as usize); // jsize == jint == i32
143                let self_len = self.len() as jsize;
144                let elements_len = elements.len() as jsize;
145
146                let start = start as jsize;
147                let end = start + elements_len;
148                assert!(start <= end);
149                assert!(end <= self_len);
150
151                let env = self.env().as_raw();
152                unsafe {
153                    ((**env).v1_1.$get_region)(
154                        env,
155                        self.as_raw(),
156                        start,
157                        elements_len,
158                        elements.as_mut_ptr() as *mut _,
159                    )
160                };
161            }
162
163            fn set_region(self: &Ref<'_, Self>, start: usize, elements: &[$type]) {
164                assert!(start <= i32::MAX as usize); // jsize == jint == i32
165                assert!(elements.len() <= i32::MAX as usize); // jsize == jint == i32
166                let self_len = self.len() as jsize;
167                let elements_len = elements.len() as jsize;
168
169                let start = start as jsize;
170                let end = start + elements_len;
171                assert!(start <= end);
172                assert!(end <= self_len);
173
174                let env = self.env().as_raw();
175                unsafe {
176                    ((**env).v1_1.$set_region)(
177                        env,
178                        self.as_raw(),
179                        start,
180                        elements_len,
181                        elements.as_ptr() as *const _,
182                    )
183                };
184            }
185        }
186    };
187}
188
189primitive_array! { BooleanArray, c"[Z", bool    { NewBooleanArray SetBooleanArrayRegion GetBooleanArrayRegion } }
190primitive_array! { ByteArray,    c"[B", jbyte   { NewByteArray    SetByteArrayRegion    GetByteArrayRegion    } }
191primitive_array! { CharArray,    c"[C", jchar   { NewCharArray    SetCharArrayRegion    GetCharArrayRegion    } }
192primitive_array! { ShortArray,   c"[S", jshort  { NewShortArray   SetShortArrayRegion   GetShortArrayRegion   } }
193primitive_array! { IntArray,     c"[I", jint    { NewIntArray     SetIntArrayRegion     GetIntArrayRegion     } }
194primitive_array! { LongArray,    c"[J", jlong   { NewLongArray    SetLongArrayRegion    GetLongArrayRegion    } }
195primitive_array! { FloatArray,   c"[F", jfloat  { NewFloatArray   SetFloatArrayRegion   GetFloatArrayRegion   } }
196primitive_array! { DoubleArray,  c"[D", jdouble { NewDoubleArray  SetDoubleArrayRegion  GetDoubleArrayRegion  } }
197
198/// A Java Array of reference types (classes, interfaces, other arrays, etc.)
199///
200/// Thread safety of avoiding [race conditions](https://www.ibm.com/docs/en/sdk-java-technology/8?topic=jni-synchronization)
201/// is not guaranteed.
202///
203/// See also [PrimitiveArray] for arrays of reference types.
204pub struct ObjectArray<T: ReferenceType, E: ThrowableType>(
205    core::convert::Infallible,
206    PhantomData<(T, E)>,
207);
208
209unsafe impl<T: ReferenceType, E: ThrowableType> ReferenceType for ObjectArray<T, E> {}
210
211unsafe impl<T: ReferenceType, E: ThrowableType> JniType for ObjectArray<T, E> {
212    fn static_with_jni_type<R>(callback: impl FnOnce(&CStr) -> R) -> R {
213        T::static_with_jni_type(|inner| {
214            let inner = inner.to_bytes();
215            let mut buf = Vec::with_capacity(inner.len() + 4);
216            buf.extend_from_slice(b"[L");
217            buf.extend_from_slice(inner);
218            buf.extend_from_slice(b";");
219            callback(&CString::new(buf).unwrap())
220        })
221    }
222}
223
224impl<T: ReferenceType, E: ThrowableType> ObjectArray<T, E> {
225    /// Uses JNI `NewObjectArray` to create a new Java object array.
226    pub fn new<'env>(env: Env<'env>, size: usize) -> Local<'env, Self> {
227        assert!(size <= i32::MAX as usize); // jsize == jint == i32
228        let class = T::static_with_jni_type(|t| unsafe { env.require_class(t) });
229        let size = size as jsize;
230
231        let object = unsafe {
232            let env = env.as_raw();
233            let fill = null_mut();
234            ((**env).v1_2.NewObjectArray)(env, size, class, fill)
235        };
236        // Only sane exception here is an OOM exception
237        env.exception_check::<E>().map_err(|_| "OOM").unwrap();
238
239        unsafe {
240            let env = env.as_raw();
241            ((**env).v1_2.DeleteLocalRef)(env, class);
242        }
243        unsafe { Local::from_raw(env, object) }
244    }
245
246    /// Iterates through object items of the array. See [ObjectArrayIter].
247    pub fn iter<'a, 'env>(self: &'a Ref<'env, Self>) -> ObjectArrayIter<'a, 'env, T, E> {
248        ObjectArrayIter {
249            array: self,
250            index: 0,
251            length: self.len(),
252        }
253    }
254
255    /// Uses JNI `NewObjectArray` to create a new Java object array of the exact size, then sets its items
256    /// with the iterator of JNI (null?) references.
257    pub fn new_from<'env>(
258        env: Env<'env>,
259        elements: impl ExactSizeIterator<Item = impl AsArg<T>>,
260    ) -> Local<'env, Self> {
261        let size = elements.len();
262        let array = Self::new(env, size);
263        let env = array.env().as_raw();
264        for (index, element) in elements.enumerate() {
265            assert!(index < size); // Should only be violated by an invalid ExactSizeIterator implementation.
266            unsafe {
267                ((**env).v1_2.SetObjectArrayElement)(
268                    env,
269                    array.as_raw(),
270                    index as jsize,
271                    element.as_arg(),
272                )
273            };
274        }
275        array
276    }
277
278    /// Uses JNI `GetArrayLength` to get the length of the Java array.
279    pub fn len(self: &Ref<'_, Self>) -> usize {
280        let env = self.env().as_raw();
281        unsafe { ((**env).v1_2.GetArrayLength)(env, self.as_raw()) as usize }
282    }
283
284    /// Gets a local reference of the object item at given `index` in the array.
285    /// Returns `None` if it is null; returns an exception if the index is invalid.
286    ///
287    /// XXX: Expose this via `std::ops::Index`.
288    pub fn get<'env>(
289        self: &Ref<'env, Self>,
290        index: usize,
291    ) -> Result<Option<Local<'env, T>>, Local<'env, E>> {
292        assert!(index <= i32::MAX as usize); // jsize == jint == i32 XXX: Should maybe be treated as an exception?
293        let index = index as jsize;
294        let env = self.env();
295        let result = unsafe {
296            let env = env.as_raw();
297            ((**env).v1_2.GetObjectArrayElement)(env, self.as_raw(), index)
298        };
299        env.exception_check()?;
300        if result.is_null() {
301            Ok(None)
302        } else {
303            Ok(Some(unsafe { Local::from_raw(env, result) }))
304        }
305    }
306
307    /// Sets an element at the given `index` in the array. Returns an exception if the index is invalid.
308    ///
309    /// XXX: I don't think there's a way to expose this via `std::ops::IndexMut` sadly?
310    pub fn set<'env>(
311        self: &Ref<'env, Self>,
312        index: usize,
313        value: impl AsArg<T>,
314    ) -> Result<(), Local<'env, E>> {
315        assert!(index <= i32::MAX as usize); // jsize == jint == i32 XXX: Should maybe be treated as an exception?
316        let index = index as jsize;
317        let env = self.env();
318        unsafe {
319            let env = env.as_raw();
320            ((**env).v1_2.SetObjectArrayElement)(env, self.as_raw(), index, value.as_arg());
321        }
322        env.exception_check()
323    }
324}
325
326/// An iterator over object items of an [ObjectArray]. Local references of object items
327/// will be created automatically.
328pub struct ObjectArrayIter<'a, 'env, T: ReferenceType, E: ThrowableType> {
329    array: &'a Ref<'env, ObjectArray<T, E>>,
330    index: usize,
331    length: usize,
332}
333
334impl<'a, 'env, T: ReferenceType, E: ThrowableType> Iterator for ObjectArrayIter<'a, 'env, T, E> {
335    type Item = Option<Local<'env, T>>;
336    fn next(&mut self) -> Option<Self::Item> {
337        let index = self.index;
338        if index < self.length {
339            self.index = index + 1;
340            Some(self.array.get(index).unwrap_or(None))
341        } else {
342            None
343        }
344    }
345}