blob: 278b91f3b702f9ab489e9c01e07b14d756c85eab [file] [log] [blame]
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<JMap<'local, 'other_local_1, 'obj_ref>>
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<JObject<'other_local_1>>
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<JMap<'local, 'other_local_1, 'obj_ref>> {
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<Option<JObject<'other_local_2>>> {
// 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<Option<JObject<'other_local_2>>> {
// 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<Option<JObject<'other_local_2>>> {
// 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<JObject> = env.auto_local(key);
/// let value: AutoLocal<JObject> = 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<JMapIter<'map, 'local, 'other_local_1, 'obj_ref, 'iter_local>> {
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<Option<(JObject<'other_local_2>, 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)))
}
}