use std::path::{Path, PathBuf};
use std::os::raw::c_void;
use jni::JNIEnv;
use jni::objects::JString;
use yog_abi::{YogBytes, YogReply, YogStatus, status};
use yog_data::Value;
use yog_event::Message;
use yog_event::nanite::Injected;
use crate::bus::{Handler, ROUTER};
use yog_event::nanite::{ClassMap, Mappings, MemberMap};
const PROBE: &str = "dev/yog/nanite/InjectProbe";
const POINT: &str = "test:point";
const ANSWER: i64 = 99;
unsafe extern "C" fn answer(
_: *mut c_void,
payload: YogBytes,
reply: *const YogReply,
) -> YogStatus {
let Some(mut e) = Injected::from_bytes(unsafe { payload.as_slice() }) else {
return status::BAD_PAYLOAD;
};
assert_eq!(e.args, vec![Value::Str("x".into()), Value::Int(3)]);
e.value = Value::Int(ANSWER);
e.cancelled = true;
e.returned = true;
unsafe { YogReply::send(reply, &e.to_bytes()) };
status::OK
}
unsafe extern "C" fn add_hundred(
_: *mut c_void,
payload: YogBytes,
reply: *const YogReply,
) -> YogStatus {
let Some(mut e) = Injected::from_bytes(unsafe { payload.as_slice() }) else {
return status::BAD_PAYLOAD;
};
if let Value::Int(v) = e.value {
e.value = Value::Int(v + 100);
e.returned = true;
}
unsafe { YogReply::send(reply, &e.to_bytes()) };
status::OK
}
unsafe extern "C" fn answer_seven(
_: *mut c_void,
payload: YogBytes,
reply: *const YogReply,
) -> YogStatus {
let Some(mut e) = Injected::from_bytes(unsafe { payload.as_slice() }) else {
return status::BAD_PAYLOAD;
};
e.value = Value::Int(7);
e.returned = true;
unsafe { YogReply::send(reply, &e.to_bytes()) };
status::OK
}
unsafe extern "C" fn double_local(
_: *mut c_void,
payload: YogBytes,
reply: *const YogReply,
) -> YogStatus {
let Some(mut e) = Injected::from_bytes(unsafe { payload.as_slice() }) else {
return status::BAD_PAYLOAD;
};
if let Some(Value::Int(v)) = e.locals.first().cloned() {
e.locals[0] = Value::Int(v * 2);
}
unsafe { YogReply::send(reply, &e.to_bytes()) };
status::OK
}
fn string(env: &mut JNIEnv, method: &str) -> String {
let out = env
.call_static_method(PROBE, method, "()Ljava/lang/String;", &[])
.and_then(|v| v.l());
if env.exception_check().unwrap_or(false) {
env.exception_describe().ok();
}
env.get_string(&JString::from(out.unwrap())).unwrap().into()
}
#[test]
#[ignore = "needs a JDK; run with --ignored"]
fn nanites_reflect_and_inject() {
let Some(jvm) = crate::testing::jvm_shared::shared() else {
return;
};
let root = Path::new(env!("CARGO_MANIFEST_DIR"));
let common = root.join("../../../java-common/src/main/java/dev/yog");
let sources: Vec<PathBuf> = [
"nanite/YogReflect.java",
"nanite/YogObjects.java",
"nanite/YogMappings.java",
"data/YogJava.java",
"data/YogData.java",
"data/YogFrame.java",
"YogNames.java",
"YogBus.java",
"YogMessage.java",
"YogAtoms.java",
"bus/YogTypes.java",
"bus/YogEvents.java",
"nanite/YogNaniteGen.java",
"nanite/YogNaniteBodies.java",
"nanite/YogNaniteAnnotations.java",
"nanite/YogNaniteLocals.java",
"nanite/YogNanites.java",
"nanite/YogEarly.java",
"util/YogNativeRuntime.java",
"nanite/YogInjections.java",
"api/YogBus.java",
"api/YogRecordFrames.java",
]
.map(|f| common.join(f))
.into_iter()
.chain([
root.join("tests/java/dev/yog/YogLog.java"),
root.join("tests/java/dev/yog/YogPlatform.java"),
root.join("tests/java/dev/yog/nanite/ReflectProbe.java"),
root.join("tests/java/dev/yog/nanite/InjectProbe.java"),
])
.collect();
assert!(jvm.compile(&sources), "javac failed");
write_table(&jvm.classes);
let vm = &jvm.vm;
let mut env = vm.attach_current_thread().unwrap();
let out = env
.call_static_method(
"dev/yog/nanite/ReflectProbe",
"run",
"()Ljava/lang/String;",
&[],
)
.and_then(|v| v.l());
if env.exception_check().unwrap_or(false) {
env.exception_describe().ok();
}
let out: String = env.get_string(&JString::from(out.unwrap())).unwrap().into();
assert_eq!(out, "ababab,7,42,true,1,0,true");
let mapped = env
.call_static_method(
"dev/yog/nanite/ReflectProbe",
"runMapped",
"()Ljava/lang/String;",
&[],
)
.and_then(|v| v.l())
.unwrap();
let mapped: String = env.get_string(&JString::from(mapped)).unwrap().into();
assert_eq!(
mapped, "xx,7",
"named members found by their run-time names"
);
if jvm.mixin.is_none() {
return;
}
let _ = crate::globals::JAVA_VM
.set(unsafe { jni::JavaVM::from_raw(jvm.vm.get_java_vm_pointer()) }.unwrap());
crate::bus::jvm_support::register_natives(&mut env).unwrap();
let made = string(&mut env, "generate");
assert_eq!(
made,
"dev/yog/nanite/gen/Nanite0|Lorg/spongepowered/asm/mixin/Mixin;|[targets, [dev.yog.nanite.InjectProbe], remap, false]\
|yog$nanite$0|(Ljava/lang/String;ILorg/spongepowered/asm/mixin/injection/callback/CallbackInfoReturnable;)V\
|Lorg/spongepowered/asm/mixin/injection/Inject;|[counted(Ljava/lang/String;I)I]"
);
let at = ROUTER.resolve(&yog_event::keyed(yog_ids::event_nanite::INJECT, POINT));
ROUTER.on(
at,
0,
Handler::Native {
owner: None,
ud: 0,
f: answer,
},
);
assert_eq!(string(&mut env, "fire"), format!("true,{ANSWER}"));
let on = |point: &str, f: yog_abi::YogHandlerFn| {
let at = ROUTER.resolve(&yog_event::keyed(yog_ids::event_nanite::INJECT, point));
ROUTER.on(
at,
0,
Handler::Native {
owner: None,
ud: 0,
f,
},
);
};
on("test:modify", add_hundred);
on("test:redirect", answer_seven);
assert_eq!(string(&mut env, "modifyAndRedirect"), "105,7");
assert_eq!(
string(&mut env, "redirectUnanswered"),
"50",
"unanswered: the call as it was"
);
on("test:added", answer_seven);
assert_eq!(
string(&mut env, "added"),
"7,yog$probe$chargeI,[java/lang/Runnable]"
);
on("test:read", answer_seven);
on("test:write", answer_seven);
assert_eq!(string(&mut env, "fields"), "7,5,5,11");
on("test:locals", double_local);
assert_eq!(string(&mut env, "locals"), "42");
}
fn write_table(classes: &Path) {
let member = |named: &str, desc: &str, runtime: &str| MemberMap {
named: named.into(),
desc: desc.into(),
runtime: runtime.into(),
};
let table = Mappings {
runtime: yog_ids::mapping_ns::INTERMEDIARY.into(),
classes: vec![ClassMap {
named: "dev/yog/nanite/NamedProbe".into(),
runtime: "dev/yog/nanite/ReflectProbe".into(),
fields: vec![member("hidden", "", "secret")],
methods: vec![member(
"double",
"(Ljava/lang/String;I)Ljava/lang/String;",
"twice",
)],
}],
};
std::fs::write(
classes.join(yog_ids::mapping_file::NAME),
yog_data::to_bytes(&table),
)
.unwrap();
}