use crate::class::Class;
use jni::errors::Result;
use jni::objects::{JClass, JMethodID, JObject, JValue};
use jni::signature::{JavaType, Primitive};
use jni::sys::{_jobject, jsize};
use jni::JNIEnv;
use thiserror::Error;
#[derive(Clone)]
pub struct Object<'a> {
pub inner: JObject<'a>,
pub class: Class<'a>,
pub env: &'a JNIEnv<'a>,
}
#[allow(clippy::from_over_into)]
impl<'a> Into<JValue<'a>> for Object<'a> {
fn into(self) -> JValue<'a> {
JValue::Object(self.inner)
}
}
#[allow(clippy::from_over_into)]
impl<'a> Into<JValue<'a>> for &Object<'a> {
fn into(self) -> JValue<'a> {
JValue::Object(self.inner)
}
}
#[allow(clippy::from_over_into)]
impl<'a> Into<*mut _jobject> for Object<'a> {
fn into(self) -> *mut _jobject {
self.inner.into_inner()
}
}
#[derive(Debug, Error)]
pub enum PrimitiveError<'a> {
#[error("JNI Error: {0}")]
Jni(#[from] jni::errors::Error),
#[error("Expected {0:?}, but found {1:?}")]
ClassMismatch(Class<'a>, Class<'a>),
}
pub type PrimitiveResult<'a, T> = std::result::Result<T, PrimitiveError<'a>>;
#[derive(Debug, Error)]
pub enum GetArrayError<'a> {
#[error("JNI Error: {0}")]
Jni(#[from] jni::errors::Error),
#[error("Expected {0:?} to be an array, it is not")]
NotArray(Class<'a>),
}
macro_rules! assert_same_class {
($a:expr, $b:expr) => {
if $a.get_name()?.ne(&$b.get_name()?) {
return Err(PrimitiveError::ClassMismatch($b, $a.clone()));
}
};
}
impl<'a> Object<'a> {
pub fn new(env: &'a JNIEnv<'a>, obj: JObject<'a>, class: Class<'a>) -> Self {
Self {
inner: obj,
class,
env,
}
}
fn get_constructor(env: &'a JNIEnv<'a>, class: Class<'a>, sig: &str) -> Result<JMethodID<'a>> {
env.get_method_id(class.class, "<init>", sig)
}
pub fn new_string<S: AsRef<str>>(env: &'a JNIEnv<'a>, str: S) -> Result<Self> {
Ok(Self::new(
env,
env.new_string(str.as_ref())?.into(),
Class::String(env)?,
))
}
pub fn new_byte_object(env: &'a JNIEnv<'a>, b: u8) -> Result<Self> {
Ok(Self::new(
env,
env.new_object_unchecked(
Class::Byte(env)?.class,
Self::get_constructor(env, Class::Byte(env)?, "(B)V")?,
&[JValue::Byte(b as i8)],
)?,
Class::Byte(env)?,
))
}
pub fn new_long_object(env: &'a JNIEnv<'a>, l: i64) -> Result<Self> {
Ok(Self::new(
env,
env.new_object_unchecked(
Class::Long(env)?.class,
Self::get_constructor(env, Class::Long(env)?, "(J)V")?,
&[JValue::Long(l)],
)?,
Class::Long(env)?,
))
}
pub fn new_integer_object(env: &'a JNIEnv<'a>, i: i32) -> Result<Self> {
Ok(Self::new(
env,
env.new_object_unchecked(
Class::Integer(env)?.class,
Self::get_constructor(env, Class::Integer(env)?, "(I)V")?,
&[JValue::Int(i)],
)?,
Class::Integer(env)?,
))
}
pub fn new_float_object(env: &'a JNIEnv<'a>, f: f32) -> Result<Self> {
Ok(Self::new(
env,
env.new_object_unchecked(
Class::Float(env)?.class,
Self::get_constructor(env, Class::Float(env)?, "(F)V")?,
&[JValue::Float(f)],
)?,
Class::Float(env)?,
))
}
pub fn new_double_object(env: &'a JNIEnv<'a>, d: f64) -> Result<Self> {
Ok(Self::new(
env,
env.new_object_unchecked(
Class::Double(env)?.class,
Self::get_constructor(env, Class::Double(env)?, "(D)V")?,
&[JValue::Double(d)],
)?,
Class::Double(env)?,
))
}
pub fn new_boolean_object(env: &'a JNIEnv<'a>, b: bool) -> Result<Self> {
let int_val = if b { 1 } else { 0 };
Ok(Self::new(
env,
env.new_object_unchecked(
Class::Boolean(env)?.class,
Self::get_constructor(env, Class::Boolean(env)?, "(Z)V")?,
&[JValue::Bool(int_val)],
)?,
Class::Boolean(env)?,
))
}
pub fn new_character_object(env: &'a JNIEnv<'a>, c: u16) -> Result<Self> {
Ok(Self::new(
env,
env.new_object_unchecked(
Class::Character(env)?.class,
Self::get_constructor(env, Class::Character(env)?, "(C)V")?,
&[JValue::Char(c)],
)?,
Class::Character(env)?,
))
}
pub fn new_short_object(env: &'a JNIEnv<'a>, s: i16) -> Result<Self> {
Ok(Self::new(
env,
env.new_object_unchecked(
Class::Short(env)?.class,
Self::get_constructor(env, Class::Short(env)?, "(S)V")?,
&[JValue::Short(s)],
)?,
Class::Short(env)?,
))
}
pub fn new_array(env: &'a JNIEnv<'a>, class: Class<'a>, data: &'a [Self]) -> Result<Self> {
let arr = env.new_object_array(data.len() as i32, class.class, JObject::null())?;
for i in 0..data.len() {
let elem = data.get(i).unwrap();
env.set_object_array_element(arr, i as i32, elem.inner)?;
}
Ok(Self::new(env, JObject::from(arr), class.array_type(env)?))
}
pub fn get_array(&self) -> std::result::Result<Vec<Self>, GetArrayError<'a>> {
if !self.is_array()? {
return Err(GetArrayError::NotArray(self.class.clone()));
}
let class_name = self.class.get_name()?;
let regular_name = &class_name[2..class_name.len() - 1];
let object_class = Class::for_name(self.env, regular_name)?;
let len = self.env.get_array_length(self.inner.into_inner())?;
let mut buf = Vec::with_capacity(len as usize);
for i in 0..len {
let obj = self
.env
.get_object_array_element(self.inner.into_inner(), i as jsize)?;
let object = Self::new(self.env, obj, object_class.clone());
buf.push(object);
}
Ok(buf)
}
fn is_array(&self) -> Result<bool> {
let class_name = self.class.get_name()?;
Ok(class_name.starts_with("["))
}
pub fn get_class_of_self(&self) -> Result<Class<'a>> {
Self::get_class(self, self.env)
}
pub fn get_byte(&self) -> PrimitiveResult<u8> {
assert_same_class!(self.class, Class::Byte(self.env)?);
let method = self
.env
.get_method_id("java/lang/Byte", "byteValue", "()B")?;
let value = self.env.call_method_unchecked(
self.inner,
method,
JavaType::Primitive(Primitive::Byte),
&[],
)?;
Ok(value.b()? as u8)
}
pub fn get_long(&self) -> PrimitiveResult<i64> {
assert_same_class!(self.class, Class::Long(self.env)?);
let method = self
.env
.get_method_id("java/lang/Long", "longValue", "()J")?;
let value = self.env.call_method_unchecked(
self.inner,
method,
JavaType::Primitive(Primitive::Long),
&[],
)?;
Ok(value.j()?)
}
pub fn get_integer(&self) -> PrimitiveResult<i32> {
assert_same_class!(self.class, Class::Integer(self.env)?);
let method = self
.env
.get_method_id("java/lang/Integer", "intValue", "()I")?;
let value = self.env.call_method_unchecked(
self.inner,
method,
JavaType::Primitive(Primitive::Int),
&[],
)?;
Ok(value.i()?)
}
pub fn get_float(&self) -> PrimitiveResult<f32> {
assert_same_class!(self.class, Class::Float(self.env)?);
let method = self
.env
.get_method_id("java/lang/Float", "floatValue", "()F")?;
let value = self.env.call_method_unchecked(
self.inner,
method,
JavaType::Primitive(Primitive::Float),
&[],
)?;
Ok(value.f()?)
}
pub fn get_double(&self) -> PrimitiveResult<f64> {
assert_same_class!(self.class, Class::Double(self.env)?);
let method = self
.env
.get_method_id("java/lang/Double", "doubleValue", "()D")?;
let value = self.env.call_method_unchecked(
self.inner,
method,
JavaType::Primitive(Primitive::Double),
&[],
)?;
Ok(value.d()?)
}
pub fn get_boolean(&self) -> PrimitiveResult<bool> {
assert_same_class!(self.class, Class::Boolean(self.env)?);
let method = self
.env
.get_method_id("java/lang/Boolean", "booleanValue", "()Z")?;
let value = self.env.call_method_unchecked(
self.inner,
method,
JavaType::Primitive(Primitive::Boolean),
&[],
)?;
Ok(value.z()?)
}
pub fn get_char(&self) -> PrimitiveResult<u16> {
assert_same_class!(self.class, Class::Character(self.env)?);
let method = self
.env
.get_method_id("java/lang/Character", "charValue", "()C")?;
let value = self.env.call_method_unchecked(
self.inner,
method,
JavaType::Primitive(Primitive::Char),
&[],
)?;
Ok(value.c()?)
}
pub fn get_short(&self) -> PrimitiveResult<i16> {
assert_same_class!(self.class, Class::Short(self.env)?);
let method = self
.env
.get_method_id("java/lang/Short", "shortValue", "()S")?;
let value = self.env.call_method_unchecked(
self.inner,
method,
JavaType::Primitive(Primitive::Short),
&[],
)?;
Ok(value.s()?)
}
pub fn get_class(obj: &Object<'a>, env: &'a JNIEnv<'a>) -> Result<Class<'a>> {
let class_object = env
.call_method(obj.inner, "getClass", "()Ljava/lang/Class;", &[])?
.l()?;
let class_name = Class::new(env, JClass::from(class_object)).get_name()?;
Class::for_name(env, &class_name)
}
pub fn instance_of_class(&self, class: &Class) -> Result<bool> {
self.env.is_instance_of(self.inner, class.class)
}
pub fn instance_of_same_object(&self, other: &Self) -> Result<bool> {
self.env.is_instance_of(self.inner, other.class.class)
}
pub fn equals(&self, other: &Object<'a>) -> Result<bool> {
let equals = self.env.call_method(
self.inner,
"equals",
"(Ljava/lang/Object;)Z",
&[other.into()],
)?;
equals.z()
}
}
#[cfg(test)]
mod test {
#![allow(non_snake_case)]
use super::*;
use crate::class::Class;
use crate::test::JVM;
use jni::objects::JString;
#[test]
fn new_string() {
let jvm = JVM.lock().unwrap();
let env = jvm.attach_current_thread().unwrap();
let jstring = Object::new_string(&env, "Foo").unwrap();
let rstring: String = env.get_string(JString::from(jstring.inner)).unwrap().into();
assert_eq!("Foo", rstring.as_str());
}
#[test]
fn new_byte() {
let jvm = JVM.lock().unwrap();
let env = jvm.attach_current_thread().unwrap();
let jByte = Object::new_byte_object(&env, 0x1).unwrap();
let jbyte = env
.call_method(jByte.inner, "byteValue", "()B", &[])
.unwrap()
.b()
.unwrap();
assert_eq!(0x1, jbyte);
}
#[test]
fn new_long() {
let jvm = JVM.lock().unwrap();
let env = jvm.attach_current_thread().unwrap();
let jLong = Object::new_long_object(&env, 10).unwrap();
let jlong = env
.call_method(jLong.inner, "longValue", "()J", &[])
.unwrap()
.j()
.unwrap();
assert_eq!(10, jlong);
}
#[test]
fn new_integer() {
let jvm = JVM.lock().unwrap();
let env = jvm.attach_current_thread().unwrap();
let jInteger = Object::new_integer_object(&env, 10).unwrap();
let jint = env
.call_method(jInteger.inner, "intValue", "()I", &[])
.unwrap()
.i()
.unwrap();
assert_eq!(10, jint);
}
#[test]
fn new_float() {
let jvm = JVM.lock().unwrap();
let env = jvm.attach_current_thread().unwrap();
let jFloat = Object::new_float_object(&env, 10.5).unwrap();
let jfloat = env
.call_method(jFloat.inner, "floatValue", "()F", &[])
.unwrap()
.f()
.unwrap();
assert_eq!(10.5, jfloat);
}
#[test]
fn new_double() {
let jvm = JVM.lock().unwrap();
let env = jvm.attach_current_thread().unwrap();
let jDouble = Object::new_double_object(&env, 10.5).unwrap();
let jdouble = env
.call_method(jDouble.inner, "doubleValue", "()D", &[])
.unwrap()
.d()
.unwrap();
assert_eq!(10.5, jdouble);
}
#[test]
fn new_boolean() {
let jvm = JVM.lock().unwrap();
let env = jvm.attach_current_thread().unwrap();
let jBoolean = Object::new_boolean_object(&env, true).unwrap();
let jboolean = env
.call_method(jBoolean.inner, "booleanValue", "()Z", &[])
.unwrap()
.z()
.unwrap();
assert_eq!(true, jboolean);
let jBoolean = Object::new_boolean_object(&env, false).unwrap();
let jboolean = env
.call_method(jBoolean.inner, "booleanValue", "()Z", &[])
.unwrap()
.z()
.unwrap();
assert_eq!(false, jboolean);
}
#[test]
fn new_character() {
let jvm = JVM.lock().unwrap();
let env = jvm.attach_current_thread().unwrap();
let character = Object::new_character_object(&env, 123).unwrap();
let jchar = env
.call_method(character.inner, "charValue", "()C", &[])
.unwrap()
.c()
.unwrap();
assert_eq!(123, jchar);
}
#[test]
fn new_short() {
let jvm = JVM.lock().unwrap();
let env = jvm.attach_current_thread().unwrap();
let short = Object::new_short_object(&env, 123).unwrap();
let jshort = env
.call_method(short.inner, "shortValue", "()S", &[])
.unwrap()
.s()
.unwrap();
assert_eq!(123, jshort);
}
#[test]
fn new_array() {
let jvm = JVM.lock().unwrap();
let env = jvm.attach_current_thread().unwrap();
let boolean = &[
Object::new_boolean_object(&env, true).unwrap(),
Object::new_boolean_object(&env, false).unwrap(),
];
let boolean_array =
Object::new_array(&env, Class::Boolean(&env).unwrap(), boolean).unwrap();
let size = env
.get_array_length(boolean_array.inner.into_inner())
.unwrap();
assert_eq!(2, size);
let zeroth_element = env
.get_object_array_element(boolean_array.inner.into_inner(), 0i32)
.unwrap();
let bool_value = env
.call_method(zeroth_element, "booleanValue", "()Z", &[])
.unwrap()
.z()
.unwrap();
assert_eq!(true, bool_value);
}
#[test]
fn get_array() {
let jvm = JVM.lock().unwrap();
let env = jvm.attach_current_thread().unwrap();
let boolean = &[
Object::new_boolean_object(&env, true).unwrap(),
Object::new_boolean_object(&env, false).unwrap(),
];
let boolean_array =
Object::new_array(&env, Class::Boolean(&env).unwrap(), boolean).unwrap();
let array = boolean_array.get_array().unwrap();
let booleans: Vec<_> = array
.into_iter()
.map(|f| f.get_boolean().unwrap())
.collect();
assert_eq!(&[true, false], booleans.as_slice());
}
#[test]
fn get_array_not_array() {
let jvm = JVM.lock().unwrap();
let env = jvm.attach_current_thread().unwrap();
let object = Object::new_boolean_object(&env, false).unwrap();
let array = object.get_array();
assert!(array.is_err());
let err = array.err().unwrap();
let is_correct_err = match err {
GetArrayError::Jni(_) => false,
GetArrayError::NotArray(_) => true,
};
assert!(is_correct_err);
}
#[test]
fn is_array_true() {
let jvm = JVM.lock().unwrap();
let env = jvm.attach_current_thread().unwrap();
let boolean = &[
Object::new_boolean_object(&env, true).unwrap(),
Object::new_boolean_object(&env, false).unwrap(),
];
let boolean_array =
Object::new_array(&env, Class::Boolean(&env).unwrap(), boolean).unwrap();
let is_array = boolean_array.is_array();
assert!(is_array.is_ok());
assert!(is_array.unwrap());
}
#[test]
fn is_array_false() {
let jvm = JVM.lock().unwrap();
let env = jvm.attach_current_thread().unwrap();
let object = Object::new_boolean_object(&env, false).unwrap();
let is_array = object.is_array();
assert!(is_array.is_ok());
assert!(!is_array.unwrap());
}
#[test]
fn get_class() {
let jvm = JVM.lock().unwrap();
let env = jvm.attach_current_thread().unwrap();
let string = Object::new_string(&env, "Foo").unwrap();
assert!(string
.instance_of_class(&string.get_class_of_self().unwrap())
.unwrap());
}
#[test]
fn instance_of() {
let jvm = JVM.lock().unwrap();
let env = jvm.attach_current_thread().unwrap();
let string = Object::new_string(&env, "Foo").unwrap();
assert!(string
.instance_of_class(&Class::String(&env).unwrap())
.unwrap());
assert!(string.instance_of_same_object(&string).unwrap());
}
#[test]
fn get_byte() {
let jvm = JVM.lock().unwrap();
let env = jvm.attach_current_thread().unwrap();
let byte = Object::new_byte_object(&env, 10).unwrap();
let value = byte.get_byte().unwrap();
assert_eq!(10, value);
}
#[test]
fn get_long() {
let jvm = JVM.lock().unwrap();
let env = jvm.attach_current_thread().unwrap();
let long = Object::new_long_object(&env, 10).unwrap();
let value = long.get_long().unwrap();
assert_eq!(10, value);
}
#[test]
fn get_integer() {
let jvm = JVM.lock().unwrap();
let env = jvm.attach_current_thread().unwrap();
let integer = Object::new_integer_object(&env, 10).unwrap();
let value = integer.get_integer().unwrap();
assert_eq!(10, value);
}
#[test]
fn get_float() {
let jvm = JVM.lock().unwrap();
let env = jvm.attach_current_thread().unwrap();
let float = Object::new_float_object(&env, 10.0).unwrap();
let value = float.get_float().unwrap();
assert_eq!(10.0, value);
}
#[test]
fn get_double() {
let jvm = JVM.lock().unwrap();
let env = jvm.attach_current_thread().unwrap();
let double = Object::new_double_object(&env, 10.0).unwrap();
let value = double.get_double().unwrap();
assert_eq!(10.0, value);
}
#[test]
fn get_boolean() {
let jvm = JVM.lock().unwrap();
let env = jvm.attach_current_thread().unwrap();
let boolean = Object::new_boolean_object(&env, true).unwrap();
let value = boolean.get_boolean().unwrap();
assert_eq!(true, value);
}
#[test]
fn get_char() {
let jvm = JVM.lock().unwrap();
let env = jvm.attach_current_thread().unwrap();
let character = Object::new_character_object(&env, 123).unwrap();
let value = character.get_char().unwrap();
assert_eq!(123, value);
}
#[test]
fn get_short() {
let jvm = JVM.lock().unwrap();
let env = jvm.attach_current_thread().unwrap();
let short = Object::new_short_object(&env, 123).unwrap();
let value = short.get_short().unwrap();
assert_eq!(123, value);
}
#[test]
fn get_wrong_class() {
let jvm = JVM.lock().unwrap();
let env = jvm.attach_current_thread().unwrap();
let integer = Object::new_integer_object(&env, 10).unwrap();
let value = integer.get_float();
assert!(value.is_err());
}
}