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}