jni 0.1.1

Rust bindings to the JNI
Documentation
use std::str;

use std::marker::PhantomData;

use std::iter::IntoIterator;

use errors::*;

use sys::{self, jvalue, jint, jsize, jbyte};

use strings::JNIString;
use strings::JavaStr;

use objects::JMap;
use objects::JValue;
use objects::JClass;
use objects::JObject;
use objects::JString;
use objects::JThrowable;
use objects::JMethodID;
use objects::GlobalRef;

use descriptors::Desc;
use descriptors::ClassDesc;
use descriptors::IntoClassDesc;
use descriptors::MethodDesc;
use descriptors::IntoMethodDesc;

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

/// FFI-compatible JNIEnv struct. You can safely use this as the JNIEnv argument
/// to exported methods that will be called by java. This is where most of the
/// magic happens. All methods on this object are wrappers around JNI functions,
/// so the documentation on their behavior is still pretty applicable.
///
/// Since we're calling into the JVM with this, many methods also have the
/// potential to cause an exception to get thrown. If this is the case, an `Err`
/// result will be returned with the error kind `JavaException`. Note that this
/// will _not_ clear the exception - it's up to the caller to decide whether to
/// do so or to let it continue being thrown.
///
/// Because null pointers are a thing in Java, this also converts them to an
/// `Err` result with the kind `NullPtr`. This may occur when either a null
/// argument is passed to a method or when a null would be returned. Where
/// applicable, the null error is changed to a more applicable error type, such
/// as `MethodNotFound`.
#[repr(C)]
pub struct JNIEnv<'a> {
    pub internal: *mut sys::JNIEnv,
    lifetime: PhantomData<&'a ()>,
}

impl<'a> From<*mut sys::JNIEnv> for JNIEnv<'a> {
    fn from(other: *mut sys::JNIEnv) -> Self {
        JNIEnv {
            internal: other,
            lifetime: PhantomData,
        }
    }
}

impl<'a> JNIEnv<'a> {
    /// Get the java version that we're being executed from. This is encoded and
    /// will need to be checked against constants from the sys module.
    ///
    /// TODO: convert this to something more usable.
    pub fn get_version(&self) -> Result<jint> {
        Ok(unsafe { jni_unchecked!(self.internal, GetVersion) })
    }

    /// Define a new java class. See the JNI docs for more details - I've never
    /// had occasion to use this and haven't researched it fully.
    pub fn define_class<S>(&self,
                           name: S,
                           loader: JObject,
                           buf: &[u8])
                           -> Result<JClass>
        where S: Into<JNIString>
    {
        non_null!(loader, "define_class loader argument");
        let name = name.into();
        let class = jni_call!(self.internal,
                              DefineClass,
                              name.as_ptr(),
                              loader.into_inner(),
                              buf.as_ptr() as *const jbyte,
                              buf.len() as jsize);
        Ok(class)
    }

    /// Look up a class by name. The argument to this will be something like
    /// `java/lang/String`. This can also take a concrete JClass, in which case
    /// it simply returns it after doing a null check. This is so that it can be
    /// generic over both concrete classes and class descriptor strings. Methods
    /// with class arguments should therefore take `IntoClassDesc` instead, and
    /// use ths when an actual class is needed. That way, optimizations such as
    /// reusing a class object to look up multiple methods can be done.
    ///
    /// # Example
    /// ```rust,ignore
    /// let class: JClass<'a> = env.find_class("java/lang/String");
    /// ```
    pub fn find_class<T>(&self, name: T) -> Result<JClass<'a>>
        where T: IntoClassDesc<'a>
    {
        let ClassDesc(name) = name.into_desc();
        match name {
            Desc::Descriptor(name) => {
                let name = name.into();
                let class = jni_call!(self.internal, FindClass, name.as_ptr());
                Ok(class)
            }
            Desc::Value(class) => {
                non_null!(class, "find_class value");
                Ok(class)
            }
        }
    }

    /// Get the superclass for a particular class. As with `find_class`, takes
    /// a descriptor.
    pub fn get_superclass<T>(&self, class: T) -> Result<JClass<'a>>
        where T: IntoClassDesc<'a>
    {
        let class = try!(self.find_class(class));
        Ok(jni_call!(self.internal, GetSuperclass, class.into_inner()))
    }

    /// Tests whether class1 is assignable from class2.
    pub fn is_assignable_from<T, U>(&self,
                                    class1: T,
                                    class2: U)
                                    -> Result<bool>
        where T: IntoClassDesc<'a>,
              U: IntoClassDesc<'a>
    {
        let class1 = try!(self.find_class(class1));
        let class2 = try!(self.find_class(class2));
        Ok(unsafe {
            jni_unchecked!(self.internal,
                           IsAssignableFrom,
                           class1.into_inner(),
                           class2.into_inner())
        } == sys::JNI_TRUE)
    }

    /// Raise an exception from an existing object. This will continue being
    /// thrown in java unless `exception_clear` is called.
    pub fn throw(&self, obj: JThrowable) -> Result<()> {
        let obj = non_null!(obj, "throw obj argument");
        let res: i32 =
            unsafe { jni_unchecked!(self.internal, Throw, obj.into_inner()) };
        if res < 0 {
            Err(format!("throw failed with code {}", res).into())
        } else {
            Ok(())
        }
    }

    /// Create and throw a new exception from a class descriptor and an error
    /// message.
    pub fn throw_new<S, T>(&self, class: T, msg: S) -> Result<()>
        where S: Into<JNIString>,
              T: IntoClassDesc<'a>
    {
        let class = try!(self.find_class(class));
        let msg = msg.into();
        let res: i32 = unsafe {
            jni_unchecked!(self.internal,
                           ThrowNew,
                           class.into_inner(),
                           msg.as_ptr())
        };
        if res < 0 {
            Err(format!("throw failed with code {}", res).into())
        } else {
            Ok(())
        }
    }

    /// Check whether or not an exception is currently in the process of being
    /// thrown. An exception is in this state from the time it gets thrown and
    /// not caught in a java function until `exception_clear` is called.
    pub fn exception_occurred(&self) -> Result<JThrowable> {
        let throwable = jni_call!(self.internal, ExceptionOccurred);
        Ok(throwable)
    }

    /// Print exception information to the console.
    pub fn exception_describe(&self) -> Result<()> {
        unsafe { jni_unchecked!(self.internal, ExceptionDescribe) };
        Ok(())
    }

    /// Clear an exception in the process of being thrown. If this is never
    /// called, the exception will continue being thrown when control is
    /// returned to java.
    pub fn exception_clear(&self) -> Result<()> {
        unsafe { jni_unchecked!(self.internal, ExceptionClear) };
        Ok(())
    }

    /// Abort the JVM with an error message.
    pub fn fatal_error<S: Into<JNIString>>(&self, msg: S) -> Result<()> {
        let msg = msg.into();
        unsafe { jni_unchecked!(self.internal, FatalError, msg.as_ptr()) };
        Ok(())
    }

    /// Check to see if an exception is being thrown. This only differs from
    /// `exception_occurred` in that it doesn't return the actual thrown
    /// exception.
    pub fn exception_check(&self) -> Result<bool> {
        let check = unsafe { jni_unchecked!(self.internal, ExceptionCheck) } ==
            sys::JNI_TRUE;
        Ok(check)
    }

    /// Turns an object into a global ref. This has the benefit of removing the
    /// lifetime bounds since it's guaranteed to not get GC'd by java. It
    /// releases the GC pin upon being dropped.
    pub fn new_global_ref(&self, obj: JObject) -> Result<GlobalRef> {
        non_null!(obj, "new_global_ref obj argument");
        let new_ref: JObject =
            jni_call!(self.internal, NewGlobalRef, obj.into_inner());
        let global =
            unsafe { GlobalRef::new(self.internal, new_ref.into_inner()) };
        Ok(global)
    }

    // Not public yet - not sure what the GC behavior is. Needs more research
    #[allow(dead_code)]
    fn new_local_ref(&self, obj: JObject) -> Result<JObject> {
        non_null!(obj, "new_local_ref obj argument");
        Ok(jni_call!(self.internal, NewLocalRef, obj.into_inner()))
    }

    #[allow(dead_code)]
    fn delete_local_ref(&self, obj: JObject) -> Result<()> {
        non_null!(obj, "delete_local_ref obj argument");
        Ok(unsafe {
            jni_unchecked!(self.internal, DeleteLocalRef, obj.into_inner());
            check_exception!(self.internal);
        })
    }

    /// Allocates a new object from a class descriptor without running a
    /// constructor.
    pub fn alloc_object<T>(&self, class: T) -> Result<JObject<'a>>
        where T: IntoClassDesc<'a>
    {
        let class = try!(self.find_class(class));
        Ok(jni_call!(self.internal, AllocObject, class.into_inner()))
    }

    /// Look up a method by class descriptor (or concrete class), name, and
    /// signature. Like `find_class`, this is generic over descriptors and
    /// concrete JMethodID objects and can take both. If given a concrete
    /// object, it simply returns it.
    ///
    /// # Example
    /// ```rust,ignore
    /// let method_id: JMethodID = env.get_method_id(
    ///     ("java/lang/String", "getString", "()Ljava/lang/String;"),
    /// );
    /// ```
    pub fn get_method_id<T>(&self, desc: T) -> Result<JMethodID<'a>>
        where T: IntoMethodDesc<'a>
    {
        let MethodDesc(desc) = desc.into_desc();
        match desc {
            Desc::Descriptor((class, name, sig)) => {
                // TODO this block is ugly and does an extra copy on errors.
                // Fix?
                let class = try!(self.find_class(class));
                let ffi_name = name.into();
                let sig = sig.into();

                let res = (|| -> Result<JMethodID> {
                    Ok(jni_call!(self.internal,
                                 GetMethodID,
                                 class.into_inner(),
                                 ffi_name.as_ptr(),
                                 sig.as_ptr()))
                })();

                match res {
                    Ok(m) => Ok(m),
                    Err(e) => {
                        match e.kind() {
                            &ErrorKind::NullPtr(_) => {
                                let name: String = ffi_name.into();
                                return Err(ErrorKind::MethodNotFound(name)
                                           .into());
                            }
                            _ => return Err(e),
                        }
                    }
                }
            }
            Desc::Value(id) => Ok(id),
        }
    }

    /// Get the class for an object.
    pub fn get_object_class(&self, obj: JObject) -> Result<JClass> {
        Ok(jni_call!(self.internal, GetObjectClass, obj.into_inner()))
    }

    /// Call a static method in an unsafe manner. This does nothing to check
    /// whether the method is valid to call on the class, whether the return type
    /// is correct, or whether the number of args is valid for the method.
    ///
    /// Under the hood, this simply calls the `CallStatic<Type>MethodA` method
    /// with the provided arguments.
    #[allow(unused_unsafe)]
    pub unsafe fn call_static_method_unsafe<T, U> (&self,
                                                   class: T,
                                                   method_id: U,
                                                   ret: JavaType,
                                                   args: &[JValue<'a>])
                                                   -> Result<JValue<'a>>
        where T: IntoClassDesc<'a>,
              U: IntoMethodDesc<'a>
    {
        let class = try!(self.find_class(class));

        let MethodDesc(method_desc) = method_id.into_desc();
        let method_desc = match method_desc {
            Desc::Descriptor((_, name, sig)) => {
                Desc::Descriptor((class, name, sig))
            }
            Desc::Value(v) => Desc::Value(v),
        };

        let method_id = try!(self.get_method_id(method_desc)).into_inner();

        let class = class.into_inner();
        let args: Vec<jvalue> = args.into_iter().map(|v| v.to_jni()).collect();
        let jni_args = args.as_ptr();

        // TODO clean this up
        Ok(match ret {
            JavaType::Object(_) |
            JavaType::Array(_) => {
                let obj: JObject = jni_call!(self.internal,
                                             CallStaticObjectMethodA,
                                             class,
                                             method_id,
                                             jni_args);
                obj.into()
            } // JavaType::Object
            JavaType::Method(_) => unimplemented!(),
            JavaType::Primitive(p) => {
                let v: JValue = match p {
                    Primitive::Boolean => {
                        (jni_unchecked!(self.internal,
                                        CallStaticBooleanMethodA,
                                        class,
                                        method_id,
                                        jni_args) ==
                         sys::JNI_TRUE)
                            .into()
                    }
                    Primitive::Char => {
                        jni_unchecked!(self.internal,
                                       CallStaticCharMethodA,
                                       class,
                                       method_id,
                                       jni_args)
                            .into()
                    }
                    Primitive::Short => {
                        jni_unchecked!(self.internal,
                                       CallStaticShortMethodA,
                                       class,
                                       method_id,
                                       jni_args)
                            .into()
                    }
                    Primitive::Int => {
                        jni_unchecked!(self.internal,
                                       CallStaticIntMethodA,
                                       class,
                                       method_id,
                                       jni_args)
                            .into()
                    }
                    Primitive::Long => {
                        jni_unchecked!(self.internal,
                                       CallStaticLongMethodA,
                                       class,
                                       method_id,
                                       jni_args)
                            .into()
                    }
                    Primitive::Float => {
                        jni_unchecked!(self.internal,
                                       CallStaticFloatMethodA,
                                       class,
                                       method_id,
                                       jni_args)
                            .into()
                    }
                    Primitive::Double => {
                        jni_unchecked!(self.internal,
                                       CallStaticDoubleMethodA,
                                       class,
                                       method_id,
                                       jni_args)
                            .into()
                    }
                    Primitive::Byte => {
                        jni_unchecked!(self.internal,
                                       CallStaticByteMethodA,
                                       class,
                                       method_id,
                                       jni_args)
                            .into()
                    }
                    Primitive::Void => {
                        jni_unchecked!(self.internal,
                                       CallStaticVoidMethodA,
                                       class,
                                       method_id,
                                       jni_args)
                            .into()
                    }
                };
                v.into()
            } // JavaType::Primitive
        }) // match parsed.ret
    }

    /// Call an object method in an unsafe manner. This does nothing to check
    /// whether the method is valid to call on the object, whether the return type
    /// is correct, or whether the number of args is valid for the method.
    ///
    /// Under the hood, this simply calls the `Call<Type>MethodA` method
    /// with the provided arguments.
    #[allow(unused_unsafe)]
        pub unsafe fn call_method_unsafe<T>(&self,
                                            obj: JObject,
                                            method_id: T,
                                            ret: JavaType,
                                            args: &[JValue<'a>])
                                            -> Result<JValue<'a>>
        where T: IntoMethodDesc<'a>
    {
        let method_id = try!(self.get_method_id(method_id)).into_inner();

        let obj = obj.into_inner();

        let args: Vec<jvalue> = args.into_iter().map(|v| v.to_jni()).collect();
        let jni_args = args.as_ptr();

        // TODO clean this up
        Ok(match ret {
            JavaType::Object(_) |
            JavaType::Array(_) => {
                let obj: JObject = jni_call!(self.internal,
                                             CallObjectMethodA,
                                             obj,
                                             method_id,
                                             jni_args);
                obj.into()
            } // JavaType::Object
            JavaType::Method(_) => unimplemented!(),
            JavaType::Primitive(p) => {
                let v: JValue = match p {
                    Primitive::Boolean => {
                        (jni_unchecked!(self.internal,
                                        CallBooleanMethodA,
                                        obj,
                                        method_id,
                                        jni_args) ==
                         sys::JNI_TRUE)
                            .into()
                    }
                    Primitive::Char => {
                        jni_unchecked!(self.internal,
                                       CallCharMethodA,
                                       obj,
                                       method_id,
                                       jni_args)
                            .into()
                    }
                    Primitive::Short => {
                        jni_unchecked!(self.internal,
                                       CallShortMethodA,
                                       obj,
                                       method_id,
                                       jni_args)
                            .into()
                    }
                    Primitive::Int => {
                        jni_unchecked!(self.internal,
                                       CallIntMethodA,
                                       obj,
                                       method_id,
                                       jni_args)
                            .into()
                    }
                    Primitive::Long => {
                        jni_unchecked!(self.internal,
                                       CallLongMethodA,
                                       obj,
                                       method_id,
                                       jni_args)
                            .into()
                    }
                    Primitive::Float => {
                        jni_unchecked!(self.internal,
                                       CallFloatMethodA,
                                       obj,
                                       method_id,
                                       jni_args)
                            .into()
                    }
                    Primitive::Double => {
                        jni_unchecked!(self.internal,
                                       CallDoubleMethodA,
                                       obj,
                                       method_id,
                                       jni_args)
                            .into()
                    }
                    Primitive::Byte => {
                        jni_unchecked!(self.internal,
                                       CallByteMethodA,
                                       obj,
                                       method_id,
                                       jni_args)
                            .into()
                    }
                    Primitive::Void => {
                        jni_unchecked!(self.internal,
                                       CallVoidMethodA,
                                       obj,
                                       method_id,
                                       jni_args);
                        return Ok(JValue::Void);
                    }
                };
                v.into()
            } // JavaType::Primitive
        }) // match parsed.ret
    }

    /// Calls an object method safely. This comes with a number of
    /// lookups/checks. It
    ///
    /// * Parses the type signature to find the number of arguments and return
    ///   type
    /// * Looks up the JClass for the given object.
    /// * Looks up the JMethodID for the class/name/signature combination
    /// * Ensures that the number of args matches the signature
    /// * Calls `call_method_unsafe` with the verified safe arguments.
    ///
    /// Note: this may cause a java exception if the arguments are the wrong
    /// type, in addition to if the method itself throws.
    pub fn call_method<S, T>(&'a self,
                             obj: JObject<'a>,
                             name: S,
                             sig: T,
                             args: &[JValue<'a>])
                             -> Result<JValue<'a>>
        where S: Into<JNIString>,
              T: Into<JNIString> + AsRef<str>
    {
        non_null!(obj, "call_method obj argument");

        // parse the signature
        let parsed = try!(TypeSignature::from_str(sig.as_ref()));
        if parsed.args.len() != args.len() {
            return Err(ErrorKind::InvalidArgList.into());
        }

        let class = try!(self.get_object_class(obj));

        unsafe {
            self.call_method_unsafe(obj, (class, name, sig), parsed.ret, args)
        }
    }

    /// Calls a static method safely. This comes with a number of
    /// lookups/checks. It
    ///
    /// * Parses the type signature to find the number of arguments and return
    ///   type
    /// * Looks up the JMethodID for the class/name/signature combination
    /// * Ensures that the number of args matches the signature
    /// * Calls `call_method_unsafe` with the verified safe arguments.
    ///
    /// Note: this may cause a java exception if the arguments are the wrong
    /// type, in addition to if the method itself throws.
    pub fn call_static_method<T, U, V>(&self,
                                       class: T,
                                       name: U,
                                       sig: V,
                                       args: &[JValue<'a>])
                                       -> Result<JValue<'a>>
        where T: IntoClassDesc<'a>,
              U: Into<JNIString>,
              V: Into<JNIString> + AsRef<str>
    {
        let parsed = try!(TypeSignature::from_str(&sig));
        if parsed.args.len() != args.len() {
            return Err(ErrorKind::InvalidArgList.into());
        }

        // go ahead and look up the class since it's already Copy,
        // and we'll need that for the next call.
        let class = try!(self.find_class(class));

        unsafe {
            self.call_static_method_unsafe(class,
                                           (class, name, sig),
                                           parsed.ret,
                                           args)
        }
    }

    /// Create a new object using a constructor. This is done safely using
    /// checks similar to those in `call_static_method`.
    pub fn new_object<T, U>(&self,
                            class: T,
                            ctor_sig: U,
                            ctor_args: &[JValue<'a>])
                            -> Result<JObject<'a>>
        where T: IntoClassDesc<'a>,
              U: Into<JNIString> + AsRef<str>
    {
        // parse the signature
        let parsed = try!(TypeSignature::from_str(&ctor_sig));

        if parsed.args.len() != ctor_args.len() {
            return Err(ErrorKind::InvalidArgList.into());
        }

        if parsed.ret != JavaType::Primitive(Primitive::Void) {
            return Err(ErrorKind::InvalidCtorReturn.into());
        }

        let jni_args: Vec<jvalue> =
            ctor_args.into_iter().map(|v| v.to_jni()).collect();

        // build strings
        let name = "<init>";

        let class = try!(self.find_class(class));

        let method_id = try!(self.get_method_id((class, name, ctor_sig)));

        let jni_args = jni_args.as_ptr();

        Ok(jni_call!(self.internal,
                     NewObjectA,
                     class.into_inner(),
                     method_id.into_inner(),
                     jni_args))
    }

    /// Cast a JObject to a JMap. This won't throw exceptions or return errors
    /// in the event that the object isn't actually a map, but the methods on
    /// the resulting map object will.
    pub fn get_map(&'a self, obj: JObject<'a>) -> Result<JMap<'a>> {
        non_null!(obj, "get_map obj argument");
        JMap::from_env(self, obj)
    }

    /// Get a JavaStr from a JString. This allows conversions from java string
    /// objects to rust strings.
    ///
    /// This entails a call to `GetStringUTFChars` and only decodes java's
    /// modified UTF-8 format on conversion to a rust-compatible string.
    pub fn get_string(&self, obj: JString) -> Result<JavaStr> {
        non_null!(obj, "get_string obj argument");
        JavaStr::from_env(self, obj.into_inner())
    }

    /// Create a new java string object from a rust string. This requires a
    /// re-encoding of rusts *real* UTF-8 strings to java's modified UTF-8
    /// format.
    pub fn new_string<S: Into<JNIString>>(&self, from: S) -> Result<JString> {
        let ffi_str = from.into();
        Ok(jni_call!(self.internal, NewStringUTF, ffi_str.as_ptr()))
    }
}