use jni::{
objects::{GlobalRef, JList, JMethodID, JObject, JValue},
sys::jvalue,
JNIEnv,
};
use crate::{get_jvm, to_jvalue, to_string, to_value};
#[derive(Clone)]
pub struct JavaFunction {
pub obj: GlobalRef,
pub method_id: JMethodID,
pub arity: Option,
}
impl JavaFunction {
pub fn new(env: &JNIEnv, obj: JObject) -> Result {
let obj = env.new_global_ref(obj).unwrap();
// Check from Function0 to Function'MAX_ARITY'
for a in 0..get_jvm().max_arity {
let cls = env
.find_class(format!("com/linkedin/feathr/online/Function{a}"))
.map_err(|e| piper::PiperError::ExternalError(e.to_string()))?;
if env
.is_instance_of(&obj, cls)
.map_err(|e| piper::PiperError::ExternalError(e.to_string()))?
{
let method_id = env
.get_method_id(
cls,
format!("apply{a}"),
format!("({})Ljava/lang/Object;", "Ljava/lang/Object;".repeat(a)),
)
.map_err(|e| piper::PiperError::ExternalError(e.to_string()))?;
return Ok(Self {
obj,
method_id,
arity: Some(a),
});
}
}
// Assume VarFunction
let cls = env
.find_class("com/azure/feathr/piper/VarFunction")
.map_err(|e| piper::PiperError::ExternalError(e.to_string()))?;
let method_id = env
.get_method_id(cls, "applyVar", "([Ljava/lang/Object;)Ljava/lang/Object;")
.map_err(|e| piper::PiperError::ExternalError(e.to_string()))?;
Ok(Self {
obj,
method_id,
arity: None,
})
}
}
impl piper::Function for JavaFunction {
fn get_output_type(
&self,
_argument_types: &[piper::ValueType],
) -> Result {
Ok(piper::ValueType::Dynamic)
}
fn eval(&self, arguments: Vec) -> piper::Value {
let env = match get_jvm().jvm.attach_current_thread_as_daemon() {
Ok(env) => env,
Err(e) => return piper::Value::Error(piper::PiperError::ExternalError(e.to_string())),
};
let args: Vec = match self.arity {
None => {
// call applyVar
let array_list_cls = &get_jvm().array_list_cls;
let new_array_list = get_jvm().new_array_list;
let l = env
.new_object_unchecked(array_list_cls, new_array_list, &[])
.unwrap();
let j = JList::from_env(&env, l).unwrap();
for arg in arguments {
j.add(to_jvalue(arg, &env)).unwrap();
}
let o: JObject = j.into();
vec![JValue::Object(o).to_jni()]
}
Some(arity) => arguments
.into_iter()
.take(arity)
.map(|a| JValue::Object(to_jvalue(a, &env)).to_jni())
.collect(),
};
let ret = env
.call_method_unchecked(
self.obj.as_obj(),
self.method_id,
jni::signature::ReturnType::Object,
&args,
)
.map_err(|e| piper::PiperError::ExternalError(e.to_string()))
.and_then(|v| {
to_value(v, &env).map_err(|e| piper::PiperError::ExternalError(e.to_string()))
});
if ret.is_err() {
match env.exception_occurred() {
Ok(ex) => {
if !ex.is_null() {
env.exception_clear().unwrap();
return piper::PiperError::ExternalError(to_string(&env, ex.into())).into();
}
}
Err(e) => return piper::PiperError::ExternalError(e.to_string()).into(),
};
}
ret.into()
}
}