use crate::{ errors::*, objects::{AutoLocal, JClass, JMethodID, JObject, JValue}, signature::{Primitive, ReturnType}, JNIEnv, }; use std::marker::PhantomData; /// Wrapper for JObjects that implement `java/util/Map`. Provides methods to get /// and set entries and a way to iterate over key/value pairs. /// /// Looks up the class and method ids on creation rather than for every method /// call. pub struct JMap<'local, 'other_local_1: 'obj_ref, 'obj_ref> { internal: &'obj_ref JObject<'other_local_1>, class: AutoLocal<'local, JClass<'local>>, get: JMethodID, put: JMethodID, remove: JMethodID, } impl<'local, 'other_local_1: 'obj_ref, 'obj_ref> AsRef> for JMap<'local, 'other_local_1, 'obj_ref> { fn as_ref(&self) -> &JMap<'local, 'other_local_1, 'obj_ref> { self } } impl<'local, 'other_local_1: 'obj_ref, 'obj_ref> AsRef> for JMap<'local, 'other_local_1, 'obj_ref> { fn as_ref(&self) -> &JObject<'other_local_1> { self.internal } } impl<'local, 'other_local_1: 'obj_ref, 'obj_ref> JMap<'local, 'other_local_1, 'obj_ref> { /// Create a map from the environment and an object. This looks up the /// necessary class and method ids to call all of the methods on it so that /// exra work doesn't need to be done on every method call. pub fn from_env( env: &mut JNIEnv<'local>, obj: &'obj_ref JObject<'other_local_1>, ) -> Result> { let class = AutoLocal::new(env.find_class("java/util/Map")?, env); let get = env.get_method_id(&class, "get", "(Ljava/lang/Object;)Ljava/lang/Object;")?; let put = env.get_method_id( &class, "put", "(Ljava/lang/Object;Ljava/lang/Object;\ )Ljava/lang/Object;", )?; let remove = env.get_method_id(&class, "remove", "(Ljava/lang/Object;)Ljava/lang/Object;")?; Ok(JMap { internal: obj, class, get, put, remove, }) } /// Look up the value for a key. Returns `Some` if it's found and `None` if /// a null pointer would be returned. pub fn get<'other_local_2>( &self, env: &mut JNIEnv<'other_local_2>, key: &JObject, ) -> Result>> { // SAFETY: We keep the class loaded, and fetched the method ID for this function. // Provided argument is statically known as a JObject/null, rather than another primitive type. let result = unsafe { env.call_method_unchecked( self.internal, self.get, ReturnType::Object, &[JValue::from(key).as_jni()], ) }; match result { Ok(val) => Ok(Some(val.l()?)), Err(e) => match e { Error::NullPtr(_) => Ok(None), _ => Err(e), }, } } /// Look up the value for a key. Returns `Some` with the old value if the /// key already existed and `None` if it's a new key. pub fn put<'other_local_2>( &self, env: &mut JNIEnv<'other_local_2>, key: &JObject, value: &JObject, ) -> Result>> { // SAFETY: We keep the class loaded, and fetched the method ID for this function. // Provided argument is statically known as a JObject/null, rather than another primitive type. let result = unsafe { env.call_method_unchecked( self.internal, self.put, ReturnType::Object, &[JValue::from(key).as_jni(), JValue::from(value).as_jni()], ) }; match result { Ok(val) => Ok(Some(val.l()?)), Err(e) => match e { Error::NullPtr(_) => Ok(None), _ => Err(e), }, } } /// Remove a value from the map. Returns `Some` with the removed value and /// `None` if there was no value for the key. pub fn remove<'other_local_2>( &self, env: &mut JNIEnv<'other_local_2>, key: &JObject, ) -> Result>> { // SAFETY: We keep the class loaded, and fetched the method ID for this function. // Provided argument is statically known as a JObject/null, rather than another primitive type. let result = unsafe { env.call_method_unchecked( self.internal, self.remove, ReturnType::Object, &[JValue::from(key).as_jni()], ) }; match result { Ok(val) => Ok(Some(val.l()?)), Err(e) => match e { Error::NullPtr(_) => Ok(None), _ => Err(e), }, } } /// Get key/value iterator for the map. This is done by getting the /// `EntrySet` from java and iterating over it. /// /// The returned iterator does not implement [`std::iter::Iterator`] and /// cannot be used with a `for` loop. This is because its `next` method /// uses a `&mut JNIEnv` to call the Java iterator. Use a `while let` loop /// instead: /// /// ```rust,no_run /// # use jni::{errors::Result, JNIEnv, objects::{AutoLocal, JMap, JObject}}; /// # /// # fn example(env: &mut JNIEnv, map: JMap) -> Result<()> { /// let mut iterator = map.iter(env)?; /// /// while let Some((key, value)) = iterator.next(env)? { /// let key: AutoLocal = env.auto_local(key); /// let value: AutoLocal = env.auto_local(value); /// /// // Do something with `key` and `value` here. /// } /// # Ok(()) /// # } /// ``` /// /// Each call to `next` creates two new local references. To prevent /// excessive memory usage or overflow error, the local references should /// be deleted using [`JNIEnv::delete_local_ref`] or [`JNIEnv::auto_local`] /// before the next loop iteration. Alternatively, if the map is known to /// have a small, predictable size, the loop could be wrapped in /// [`JNIEnv::with_local_frame`] to delete all of the local references at /// once. pub fn iter<'map, 'iter_local>( &'map self, env: &mut JNIEnv<'iter_local>, ) -> Result> { let iter_class = AutoLocal::new(env.find_class("java/util/Iterator")?, env); let has_next = env.get_method_id(&iter_class, "hasNext", "()Z")?; let next = env.get_method_id(&iter_class, "next", "()Ljava/lang/Object;")?; let entry_class = AutoLocal::new(env.find_class("java/util/Map$Entry")?, env); let get_key = env.get_method_id(&entry_class, "getKey", "()Ljava/lang/Object;")?; let get_value = env.get_method_id(&entry_class, "getValue", "()Ljava/lang/Object;")?; // Get the iterator over Map entries. // SAFETY: We keep the class loaded, and fetched the method ID for this function. Arg list is known empty. let entry_set = AutoLocal::new( unsafe { env.call_method_unchecked( self.internal, (&self.class, "entrySet", "()Ljava/util/Set;"), ReturnType::Object, &[], ) }? .l()?, env, ); // SAFETY: We keep the class loaded, and fetched the method ID for this function. Arg list is known empty. let iter = AutoLocal::new( unsafe { env.call_method_unchecked( entry_set, ("java/util/Set", "iterator", "()Ljava/util/Iterator;"), ReturnType::Object, &[], ) }? .l()?, env, ); Ok(JMapIter { _phantom_map: PhantomData, has_next, next, get_key, get_value, iter, }) } } /// An iterator over the keys and values in a map. See [`JMap::iter`] for more /// information. /// /// TODO: make the iterator implementation for java iterators its own thing /// and generic enough to use elsewhere. pub struct JMapIter<'map, 'local, 'other_local_1: 'obj_ref, 'obj_ref, 'iter_local> { _phantom_map: PhantomData<&'map JMap<'local, 'other_local_1, 'obj_ref>>, has_next: JMethodID, next: JMethodID, get_key: JMethodID, get_value: JMethodID, iter: AutoLocal<'iter_local, JObject<'iter_local>>, } impl<'map, 'local, 'other_local_1: 'obj_ref, 'obj_ref, 'iter_local> JMapIter<'map, 'local, 'other_local_1, 'obj_ref, 'iter_local> { /// Advances the iterator and returns the next key-value pair in the /// `java.util.Map`, or `None` if there are no more objects. /// /// See [`JMap::iter`] for more information. /// /// This method creates two new local references. To prevent excessive /// memory usage or overflow error, the local references should be deleted /// using [`JNIEnv::delete_local_ref`] or [`JNIEnv::auto_local`] before the /// next loop iteration. Alternatively, if the map is known to have a /// small, predictable size, the loop could be wrapped in /// [`JNIEnv::with_local_frame`] to delete all of the local references at /// once. /// /// This method returns: /// /// * `Ok(Some(_))`: if there was another key-value pair in the map. /// * `Ok(None)`: if there are no more key-value pairs in the map. /// * `Err(_)`: if there was an error calling the Java method to /// get the next key-value pair. /// /// This is like [`std::iter::Iterator::next`], but requires a parameter of /// type `&mut JNIEnv` in order to call into Java. pub fn next<'other_local_2>( &mut self, env: &mut JNIEnv<'other_local_2>, ) -> Result, JObject<'other_local_2>)>> { // SAFETY: We keep the class loaded, and fetched the method ID for these functions. We know none expect args. let has_next = unsafe { env.call_method_unchecked( &self.iter, self.has_next, ReturnType::Primitive(Primitive::Boolean), &[], ) }? .z()?; if !has_next { return Ok(None); } let next = unsafe { env.call_method_unchecked(&self.iter, self.next, ReturnType::Object, &[]) }? .l()?; let next = env.auto_local(next); let key = unsafe { env.call_method_unchecked(&next, self.get_key, ReturnType::Object, &[]) }? .l()?; let value = unsafe { env.call_method_unchecked(&next, self.get_value, ReturnType::Object, &[]) }? .l()?; Ok(Some((key, value))) } }