jni 0.7.1

Rust bindings to the JNI
Documentation
use JNIEnv;

use errors::*;

use objects::JObject;
use objects::JClass;
use objects::JMethodID;

use signature::JavaType;
use signature::Primitive;

/// 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> {
    internal: JObject<'a>,
    class: JClass<'a>,
    get: JMethodID<'a>,
    put: JMethodID<'a>,
    remove: JMethodID<'a>,
    env: &'a JNIEnv<'a>,
}

impl<'a> ::std::ops::Deref for JMap<'a> {
    type Target = JObject<'a>;

    fn deref(&self) -> &Self::Target {
        &self.internal
    }
}

impl<'a> From<JMap<'a>> for JObject<'a> {
    fn from(other: JMap) -> JObject {
        other.internal
    }
}

impl<'a> JMap<'a> {
    /// 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: &'a JNIEnv<'a>, obj: JObject<'a>) -> Result<JMap<'a>> {
        let class = 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: class,
            get: get,
            put: put,
            remove: remove,
            env: 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>> {
        let result = unsafe {
            self.env.call_method_unsafe(
                self.internal,
                self.get,
                JavaType::Object("java/lang/Object".into()),
                &[key.into()],
            )
        };

        match result {
            Ok(val) => Ok(Some(val.l()?)),
            Err(e) => match e.kind() {
                &ErrorKind::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>> {
        let result = unsafe {
            self.env.call_method_unsafe(
                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.kind() {
                &ErrorKind::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 = unsafe {
            self.env.call_method_unsafe(
                self.internal,
                self.remove,
                JavaType::Object("java/lang/Object".into()),
                &[key.into()],
            )
        };

        match result {
            Ok(val) => Ok(Some(val.l()?)),
            Err(e) => match e.kind() {
                &ErrorKind::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(&'a self) -> Result<JMapIter<'a>> {
        let set = unsafe {
            let set = self.env.call_method_unsafe(
                self.internal,
                (self.class, "entrySet", "()Ljava/util/Set;"),
                JavaType::Object("java/util/Set".into()),
                &[],
            )?;
            set.l()?
        };

        let iter = unsafe {
            let iter = self.env.call_method_unsafe(
                set,
                ("java/util/Set", "iterator", "()Ljava/util/Iterator;"),
                JavaType::Object("java/util/Iterator".into()),
                &[],
            )?;
            iter.l()?
        };

        let iter_class = 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.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;")?;

        Ok(JMapIter {
            map: &self,
            has_next: has_next,
            next: next,
            get_key: get_key,
            get_value: get_value,
            iter: 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> {
    map: &'a JMap<'a>,
    has_next: JMethodID<'a>,
    next: JMethodID<'a>,
    get_key: JMethodID<'a>,
    get_value: JMethodID<'a>,
    iter: JObject<'a>,
}

impl<'a> JMapIter<'a> {
    fn get_next(&self) -> Result<Option<(JObject<'a>, JObject<'a>)>> {
        let has_next = unsafe {
            let val = self.map.env.call_method_unsafe(
                self.iter,
                self.has_next,
                JavaType::Primitive(Primitive::Boolean),
                &[],
            )?;
            val.z()?
        };

        if !has_next {
            return Ok(None);
        }
        let next = unsafe {
            let next = self.map.env.call_method_unsafe(
                self.iter,
                self.next,
                JavaType::Object("java/util/Map$Entry".into()),
                &[],
            )?;
            next.l()?
        };

        let key = unsafe {
            let key = self.map.env.call_method_unsafe(
                next,
                self.get_key,
                JavaType::Object("java/lang/Object".into()),
                &[],
            )?;
            key.l()?
        };

        let value = unsafe {
            let value = self.map.env.call_method_unsafe(
                next,
                self.get_value,
                JavaType::Object("java/lang/Object".into()),
                &[],
            )?;
            value.l()?
        };

        Ok(Some((key, value)))
    }
}

impl<'a> Iterator for JMapIter<'a> {
    type Item = (JObject<'a>, JObject<'a>);

    fn next(&mut self) -> Option<Self::Item> {
        match self.get_next() {
            Ok(Some(n)) => Some(n),
            _ => None,
        }
    }
}