diff options
Diffstat (limited to 'src/wrapper/objects/jmap.rs')
-rw-r--r-- | src/wrapper/objects/jmap.rs | 261 |
1 files changed, 261 insertions, 0 deletions
diff --git a/src/wrapper/objects/jmap.rs b/src/wrapper/objects/jmap.rs new file mode 100644 index 0000000..23ac892 --- /dev/null +++ b/src/wrapper/objects/jmap.rs @@ -0,0 +1,261 @@ +use crate::{ + errors::*, + objects::{AutoLocal, JMethodID, JObject}, + signature::{JavaType, Primitive}, + JNIEnv, +}; + +/// 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<'a: 'b, 'b> { + internal: JObject<'a>, + class: AutoLocal<'a, 'b>, + get: JMethodID<'a>, + put: JMethodID<'a>, + remove: JMethodID<'a>, + env: &'b JNIEnv<'a>, +} + +impl<'a: 'b, 'b> ::std::ops::Deref for JMap<'a, 'b> { + type Target = JObject<'a>; + + fn deref(&self) -> &Self::Target { + &self.internal + } +} + +impl<'a: 'b, 'b> From<JMap<'a, 'b>> for JObject<'a> { + fn from(other: JMap<'a, 'b>) -> JObject<'a> { + other.internal + } +} + +impl<'a: 'b, 'b> JMap<'a, 'b> { + /// 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: &'b JNIEnv<'a>, obj: JObject<'a>) -> Result<JMap<'a, 'b>> { + let class = env.auto_local(env.find_class("java/util/Map")?); + + 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, + env, + }) + } + + /// 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(&self, key: JObject<'a>) -> Result<Option<JObject<'a>>> { + let result = self.env.call_method_unchecked( + self.internal, + self.get, + JavaType::Object("java/lang/Object".into()), + &[key.into()], + ); + + 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(&self, key: JObject<'a>, value: JObject<'a>) -> Result<Option<JObject<'a>>> { + let result = self.env.call_method_unchecked( + self.internal, + self.put, + JavaType::Object("java/lang/Object".into()), + &[key.into(), value.into()], + ); + + 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(&self, key: JObject<'a>) -> Result<Option<JObject<'a>>> { + let result = self.env.call_method_unchecked( + self.internal, + self.remove, + JavaType::Object("java/lang/Object".into()), + &[key.into()], + ); + + 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. + pub fn iter(&self) -> Result<JMapIter<'a, 'b, '_>> { + let iter_class = self + .env + .auto_local(self.env.find_class("java/util/Iterator")?); + + let has_next = self.env.get_method_id(&iter_class, "hasNext", "()Z")?; + + let next = self + .env + .get_method_id(&iter_class, "next", "()Ljava/lang/Object;")?; + + let entry_class = self + .env + .auto_local(self.env.find_class("java/util/Map$Entry")?); + + let get_key = self + .env + .get_method_id(&entry_class, "getKey", "()Ljava/lang/Object;")?; + + let get_value = self + .env + .get_method_id(&entry_class, "getValue", "()Ljava/lang/Object;")?; + + // Get the iterator over Map entries. + // Use the local frame till #109 is resolved, so that implicitly looked-up + // classes are freed promptly. + let iter = self.env.with_local_frame(16, || { + let entry_set = self + .env + .call_method_unchecked( + self.internal, + (&self.class, "entrySet", "()Ljava/util/Set;"), + JavaType::Object("java/util/Set".into()), + &[], + )? + .l()?; + + let iter = self + .env + .call_method_unchecked( + entry_set, + ("java/util/Set", "iterator", "()Ljava/util/Iterator;"), + JavaType::Object("java/util/Iterator".into()), + &[], + )? + .l()?; + + Ok(iter) + })?; + let iter = self.env.auto_local(iter); + + Ok(JMapIter { + map: &self, + has_next, + next, + get_key, + get_value, + iter, + }) + } +} + +/// An iterator over the keys and values in a map. +/// +/// TODO: make the iterator implementation for java iterators its own thing +/// and generic enough to use elsewhere. +pub struct JMapIter<'a, 'b, 'c> { + map: &'c JMap<'a, 'b>, + has_next: JMethodID<'a>, + next: JMethodID<'a>, + get_key: JMethodID<'a>, + get_value: JMethodID<'a>, + iter: AutoLocal<'a, 'b>, +} + +impl<'a: 'b, 'b: 'c, 'c> JMapIter<'a, 'b, 'c> { + fn get_next(&self) -> Result<Option<(JObject<'a>, JObject<'a>)>> { + let iter = self.iter.as_obj(); + let has_next = self + .map + .env + .call_method_unchecked( + iter, + self.has_next, + JavaType::Primitive(Primitive::Boolean), + &[], + )? + .z()?; + + if !has_next { + return Ok(None); + } + let next = self + .map + .env + .call_method_unchecked( + iter, + self.next, + JavaType::Object("java/util/Map$Entry".into()), + &[], + )? + .l()?; + + let key = self + .map + .env + .call_method_unchecked( + next, + self.get_key, + JavaType::Object("java/lang/Object".into()), + &[], + )? + .l()?; + + let value = self + .map + .env + .call_method_unchecked( + next, + self.get_value, + JavaType::Object("java/lang/Object".into()), + &[], + )? + .l()?; + + Ok(Some((key, value))) + } +} + +impl<'a: 'b, 'b: 'c, 'c> Iterator for JMapIter<'a, 'b, 'c> { + type Item = (JObject<'a>, JObject<'a>); + + fn next(&mut self) -> Option<Self::Item> { + match self.get_next() { + Ok(Some(n)) => Some(n), + _ => None, + } + } +} |