jni/wrapper/objects/
jmap.rs

1use crate::{
2    errors::*,
3    objects::{AutoLocal, JClass, JMethodID, JObject, JValue},
4    signature::{Primitive, ReturnType},
5    JNIEnv,
6};
7
8use std::marker::PhantomData;
9
10/// Wrapper for JObjects that implement `java/util/Map`. Provides methods to get
11/// and set entries and a way to iterate over key/value pairs.
12///
13/// Looks up the class and method ids on creation rather than for every method
14/// call.
15pub struct JMap<'local, 'other_local_1: 'obj_ref, 'obj_ref> {
16    internal: &'obj_ref JObject<'other_local_1>,
17    class: AutoLocal<'local, JClass<'local>>,
18    get: JMethodID,
19    put: JMethodID,
20    remove: JMethodID,
21}
22
23impl<'local, 'other_local_1: 'obj_ref, 'obj_ref> AsRef<JMap<'local, 'other_local_1, 'obj_ref>>
24    for JMap<'local, 'other_local_1, 'obj_ref>
25{
26    fn as_ref(&self) -> &JMap<'local, 'other_local_1, 'obj_ref> {
27        self
28    }
29}
30
31impl<'local, 'other_local_1: 'obj_ref, 'obj_ref> AsRef<JObject<'other_local_1>>
32    for JMap<'local, 'other_local_1, 'obj_ref>
33{
34    fn as_ref(&self) -> &JObject<'other_local_1> {
35        self.internal
36    }
37}
38
39impl<'local, 'other_local_1: 'obj_ref, 'obj_ref> JMap<'local, 'other_local_1, 'obj_ref> {
40    /// Create a map from the environment and an object. This looks up the
41    /// necessary class and method ids to call all of the methods on it so that
42    /// exra work doesn't need to be done on every method call.
43    pub fn from_env(
44        env: &mut JNIEnv<'local>,
45        obj: &'obj_ref JObject<'other_local_1>,
46    ) -> Result<JMap<'local, 'other_local_1, 'obj_ref>> {
47        let class = AutoLocal::new(env.find_class("java/util/Map")?, env);
48
49        let get = env.get_method_id(&class, "get", "(Ljava/lang/Object;)Ljava/lang/Object;")?;
50        let put = env.get_method_id(
51            &class,
52            "put",
53            "(Ljava/lang/Object;Ljava/lang/Object;\
54             )Ljava/lang/Object;",
55        )?;
56
57        let remove =
58            env.get_method_id(&class, "remove", "(Ljava/lang/Object;)Ljava/lang/Object;")?;
59
60        Ok(JMap {
61            internal: obj,
62            class,
63            get,
64            put,
65            remove,
66        })
67    }
68
69    /// Look up the value for a key. Returns `Some` if it's found and `None` if
70    /// a null pointer would be returned.
71    pub fn get<'other_local_2>(
72        &self,
73        env: &mut JNIEnv<'other_local_2>,
74        key: &JObject,
75    ) -> Result<Option<JObject<'other_local_2>>> {
76        // SAFETY: We keep the class loaded, and fetched the method ID for this function.
77        // Provided argument is statically known as a JObject/null, rather than another primitive type.
78        let result = unsafe {
79            env.call_method_unchecked(
80                self.internal,
81                self.get,
82                ReturnType::Object,
83                &[JValue::from(key).as_jni()],
84            )
85        };
86
87        match result {
88            Ok(val) => Ok(Some(val.l()?)),
89            Err(e) => match e {
90                Error::NullPtr(_) => Ok(None),
91                _ => Err(e),
92            },
93        }
94    }
95
96    /// Look up the value for a key. Returns `Some` with the old value if the
97    /// key already existed and `None` if it's a new key.
98    pub fn put<'other_local_2>(
99        &self,
100        env: &mut JNIEnv<'other_local_2>,
101        key: &JObject,
102        value: &JObject,
103    ) -> Result<Option<JObject<'other_local_2>>> {
104        // SAFETY: We keep the class loaded, and fetched the method ID for this function.
105        // Provided argument is statically known as a JObject/null, rather than another primitive type.
106        let result = unsafe {
107            env.call_method_unchecked(
108                self.internal,
109                self.put,
110                ReturnType::Object,
111                &[JValue::from(key).as_jni(), JValue::from(value).as_jni()],
112            )
113        };
114
115        match result {
116            Ok(val) => Ok(Some(val.l()?)),
117            Err(e) => match e {
118                Error::NullPtr(_) => Ok(None),
119                _ => Err(e),
120            },
121        }
122    }
123
124    /// Remove a value from the map. Returns `Some` with the removed value and
125    /// `None` if there was no value for the key.
126    pub fn remove<'other_local_2>(
127        &self,
128        env: &mut JNIEnv<'other_local_2>,
129        key: &JObject,
130    ) -> Result<Option<JObject<'other_local_2>>> {
131        // SAFETY: We keep the class loaded, and fetched the method ID for this function.
132        // Provided argument is statically known as a JObject/null, rather than another primitive type.
133        let result = unsafe {
134            env.call_method_unchecked(
135                self.internal,
136                self.remove,
137                ReturnType::Object,
138                &[JValue::from(key).as_jni()],
139            )
140        };
141
142        match result {
143            Ok(val) => Ok(Some(val.l()?)),
144            Err(e) => match e {
145                Error::NullPtr(_) => Ok(None),
146                _ => Err(e),
147            },
148        }
149    }
150
151    /// Get key/value iterator for the map. This is done by getting the
152    /// `EntrySet` from java and iterating over it.
153    ///
154    /// The returned iterator does not implement [`std::iter::Iterator`] and
155    /// cannot be used with a `for` loop. This is because its `next` method
156    /// uses a `&mut JNIEnv` to call the Java iterator. Use a `while let` loop
157    /// instead:
158    ///
159    /// ```rust,no_run
160    /// # use jni::{errors::Result, JNIEnv, objects::{AutoLocal, JMap, JObject}};
161    /// #
162    /// # fn example(env: &mut JNIEnv, map: JMap) -> Result<()> {
163    /// let mut iterator = map.iter(env)?;
164    ///
165    /// while let Some((key, value)) = iterator.next(env)? {
166    ///     let key: AutoLocal<JObject> = env.auto_local(key);
167    ///     let value: AutoLocal<JObject> = env.auto_local(value);
168    ///
169    ///     // Do something with `key` and `value` here.
170    /// }
171    /// # Ok(())
172    /// # }
173    /// ```
174    ///
175    /// Each call to `next` creates two new local references. To prevent
176    /// excessive memory usage or overflow error, the local references should
177    /// be deleted using [`JNIEnv::delete_local_ref`] or [`JNIEnv::auto_local`]
178    /// before the next loop iteration. Alternatively, if the map is known to
179    /// have a small, predictable size, the loop could be wrapped in
180    /// [`JNIEnv::with_local_frame`] to delete all of the local references at
181    /// once.
182    pub fn iter<'map, 'iter_local>(
183        &'map self,
184        env: &mut JNIEnv<'iter_local>,
185    ) -> Result<JMapIter<'map, 'local, 'other_local_1, 'obj_ref, 'iter_local>> {
186        let iter_class = AutoLocal::new(env.find_class("java/util/Iterator")?, env);
187
188        let has_next = env.get_method_id(&iter_class, "hasNext", "()Z")?;
189
190        let next = env.get_method_id(&iter_class, "next", "()Ljava/lang/Object;")?;
191
192        let entry_class = AutoLocal::new(env.find_class("java/util/Map$Entry")?, env);
193
194        let get_key = env.get_method_id(&entry_class, "getKey", "()Ljava/lang/Object;")?;
195
196        let get_value = env.get_method_id(&entry_class, "getValue", "()Ljava/lang/Object;")?;
197
198        // Get the iterator over Map entries.
199
200        // SAFETY: We keep the class loaded, and fetched the method ID for this function. Arg list is known empty.
201        let entry_set = AutoLocal::new(
202            unsafe {
203                env.call_method_unchecked(
204                    self.internal,
205                    (&self.class, "entrySet", "()Ljava/util/Set;"),
206                    ReturnType::Object,
207                    &[],
208                )
209            }?
210            .l()?,
211            env,
212        );
213
214        // SAFETY: We keep the class loaded, and fetched the method ID for this function. Arg list is known empty.
215        let iter = AutoLocal::new(
216            unsafe {
217                env.call_method_unchecked(
218                    entry_set,
219                    ("java/util/Set", "iterator", "()Ljava/util/Iterator;"),
220                    ReturnType::Object,
221                    &[],
222                )
223            }?
224            .l()?,
225            env,
226        );
227
228        Ok(JMapIter {
229            _phantom_map: PhantomData,
230            has_next,
231            next,
232            get_key,
233            get_value,
234            iter,
235        })
236    }
237}
238
239/// An iterator over the keys and values in a map. See [`JMap::iter`] for more
240/// information.
241///
242/// TODO: make the iterator implementation for java iterators its own thing
243/// and generic enough to use elsewhere.
244pub struct JMapIter<'map, 'local, 'other_local_1: 'obj_ref, 'obj_ref, 'iter_local> {
245    _phantom_map: PhantomData<&'map JMap<'local, 'other_local_1, 'obj_ref>>,
246    has_next: JMethodID,
247    next: JMethodID,
248    get_key: JMethodID,
249    get_value: JMethodID,
250    iter: AutoLocal<'iter_local, JObject<'iter_local>>,
251}
252
253impl<'map, 'local, 'other_local_1: 'obj_ref, 'obj_ref, 'iter_local>
254    JMapIter<'map, 'local, 'other_local_1, 'obj_ref, 'iter_local>
255{
256    /// Advances the iterator and returns the next key-value pair in the
257    /// `java.util.Map`, or `None` if there are no more objects.
258    ///
259    /// See [`JMap::iter`] for more information.
260    ///
261    /// This method creates two new local references. To prevent excessive
262    /// memory usage or overflow error, the local references should be deleted
263    /// using [`JNIEnv::delete_local_ref`] or [`JNIEnv::auto_local`] before the
264    /// next loop iteration. Alternatively, if the map is known to have a
265    /// small, predictable size, the loop could be wrapped in
266    /// [`JNIEnv::with_local_frame`] to delete all of the local references at
267    /// once.
268    ///
269    /// This method returns:
270    ///
271    /// * `Ok(Some(_))`: if there was another key-value pair in the map.
272    /// * `Ok(None)`: if there are no more key-value pairs in the map.
273    /// * `Err(_)`: if there was an error calling the Java method to
274    ///   get the next key-value pair.
275    ///
276    /// This is like [`std::iter::Iterator::next`], but requires a parameter of
277    /// type `&mut JNIEnv` in order to call into Java.
278    pub fn next<'other_local_2>(
279        &mut self,
280        env: &mut JNIEnv<'other_local_2>,
281    ) -> Result<Option<(JObject<'other_local_2>, JObject<'other_local_2>)>> {
282        // SAFETY: We keep the class loaded, and fetched the method ID for these functions. We know none expect args.
283
284        let has_next = unsafe {
285            env.call_method_unchecked(
286                &self.iter,
287                self.has_next,
288                ReturnType::Primitive(Primitive::Boolean),
289                &[],
290            )
291        }?
292        .z()?;
293
294        if !has_next {
295            return Ok(None);
296        }
297        let next =
298            unsafe { env.call_method_unchecked(&self.iter, self.next, ReturnType::Object, &[]) }?
299                .l()?;
300        let next = env.auto_local(next);
301
302        let key =
303            unsafe { env.call_method_unchecked(&next, self.get_key, ReturnType::Object, &[]) }?
304                .l()?;
305
306        let value =
307            unsafe { env.call_method_unchecked(&next, self.get_value, ReturnType::Object, &[]) }?
308                .l()?;
309
310        Ok(Some((key, value)))
311    }
312}