1 use crate::{
2     errors::*,
3     objects::{AutoLocal, JClass, JMethodID, JObject, JValue},
4     signature::{Primitive, ReturnType},
5     JNIEnv,
6 };
7 
8 use 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.
15 pub 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 
23 impl<'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 {
as_ref(&self) -> &JMap<'local, 'other_local_1, 'obj_ref>26     fn as_ref(&self) -> &JMap<'local, 'other_local_1, 'obj_ref> {
27         self
28     }
29 }
30 
31 impl<'local, 'other_local_1: 'obj_ref, 'obj_ref> AsRef<JObject<'other_local_1>>
32     for JMap<'local, 'other_local_1, 'obj_ref>
33 {
as_ref(&self) -> &JObject<'other_local_1>34     fn as_ref(&self) -> &JObject<'other_local_1> {
35         self.internal
36     }
37 }
38 
39 impl<'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.
from_env( env: &mut JNIEnv<'local>, obj: &'obj_ref JObject<'other_local_1>, ) -> Result<JMap<'local, 'other_local_1, 'obj_ref>>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.
get<'other_local_2>( &self, env: &mut JNIEnv<'other_local_2>, key: &JObject, ) -> Result<Option<JObject<'other_local_2>>>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.
put<'other_local_2>( &self, env: &mut JNIEnv<'other_local_2>, key: &JObject, value: &JObject, ) -> Result<Option<JObject<'other_local_2>>>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.
remove<'other_local_2>( &self, env: &mut JNIEnv<'other_local_2>, key: &JObject, ) -> Result<Option<JObject<'other_local_2>>>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.
iter<'map, 'iter_local>( &'map self, env: &mut JNIEnv<'iter_local>, ) -> Result<JMapIter<'map, 'local, 'other_local_1, 'obj_ref, 'iter_local>>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.
244 pub 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 
253 impl<'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.
next<'other_local_2>( &mut self, env: &mut JNIEnv<'other_local_2>, ) -> Result<Option<(JObject<'other_local_2>, JObject<'other_local_2>)>>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 }
313