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