use crate::backends::java::type_map::{java_boxed_type, java_return_type, java_type};
use crate::codegen::naming::to_java_name;
use crate::core::ir::{FunctionDef, TypeRef};
use ahash::AHashSet;
use heck::ToSnakeCase;
use std::collections::HashSet;
use super::super::helpers::{is_bridge_param_java, render_nullable_type};
use super::super::marshal::{ffi_param_args, marshal_param_to_ffi, opaque_lease_resource};
use super::params_returns::return_type_name;
use super::visitor_bridge::VisitorFunctionBridge;
fn effective_param_type(param: &crate::core::ir::ParamDef) -> TypeRef {
if param.optional && !matches!(param.ty, TypeRef::Optional(_)) {
TypeRef::Optional(Box::new(param.ty.clone()))
} else {
param.ty.clone()
}
}
fn visitor_method_params(
func: &FunctionDef,
bridge_param_names: &HashSet<String>,
bridge_type_aliases: &HashSet<String>,
) -> String {
func.params
.iter()
.filter(|param| !is_bridge_param_java(param, bridge_param_names, bridge_type_aliases))
.map(|param| {
let param_type = if param.optional {
java_boxed_type(¶m.ty)
} else {
java_type(¶m.ty)
};
format!(
"final {} {}",
render_nullable_type(¶m_type, param.optional),
to_java_name(¶m.name)
)
})
.collect::<Vec<_>>()
.join(", ")
}
fn emit_visitor_resources(
out: &mut String,
func: &FunctionDef,
opaque_types: &AHashSet<String>,
bridge_param_names: &HashSet<String>,
bridge_type_aliases: &HashSet<String>,
) {
for param in &func.params {
if is_bridge_param_java(param, bridge_param_names, bridge_type_aliases) {
continue;
}
if let Some(resource) =
opaque_lease_resource(&to_java_name(¶m.name), &effective_param_type(param), opaque_types)
{
out.push_str(&resource);
out.push_str(";\n");
}
}
}
fn emit_visitor_param_marshalling(
out: &mut String,
func: &FunctionDef,
prefix: &str,
opaque_types: &AHashSet<String>,
bridge_param_names: &HashSet<String>,
bridge_type_aliases: &HashSet<String>,
) {
for param in &func.params {
if is_bridge_param_java(param, bridge_param_names, bridge_type_aliases) {
continue;
}
marshal_param_to_ffi(
out,
&to_java_name(¶m.name),
&effective_param_type(param),
opaque_types,
prefix,
"nativeResources",
);
}
}
fn visitor_call_args(
func: &FunctionDef,
opaque_types: &AHashSet<String>,
bridge_param_names: &HashSet<String>,
bridge_type_aliases: &HashSet<String>,
) -> String {
func.params
.iter()
.flat_map(|param| {
if is_bridge_param_java(param, bridge_param_names, bridge_type_aliases) {
vec!["MemorySegment.NULL".to_owned()]
} else {
ffi_param_args(&to_java_name(¶m.name), &effective_param_type(param), opaque_types)
}
})
.collect::<Vec<_>>()
.join(", ")
}
fn emit_visitor_json_return(out: &mut String, func: &FunctionDef) {
out.push_str(" String json = jsonPtr.reinterpret(Long.MAX_VALUE).getString(0);\n");
if let Some(type_name) = return_type_name(&func.return_type) {
if matches!(func.return_type, TypeRef::Optional(_)) {
out.push_str(" return Optional.ofNullable(MAPPER.readValue(json, ");
} else {
out.push_str(" return MAPPER.readValue(json, ");
}
out.push_str(type_name);
out.push_str(if matches!(func.return_type, TypeRef::Optional(_)) {
".class));\n"
} else {
".class);\n"
});
} else {
out.push_str(" return MAPPER.readValue(json, Object.class);\n");
}
}
fn emit_visitor_handle_setup(
out: &mut String,
prefix_upper: &str,
exception_class: &str,
bridge: &VisitorFunctionBridge,
) {
out.push_str(&crate::backends::java::template_env::render(
"ffi_visitor_create.jinja",
minijinja::context! { pu => prefix_upper },
));
out.push_str(" if (visitorHandle.equals(MemorySegment.NULL)) {\n if (!");
out.push_str(&bridge.options_param_c);
out.push_str(".equals(MemorySegment.NULL)) {\n");
out.push_str(&crate::backends::java::template_env::render(
"ffi_options_free.jinja",
minijinja::context! { pu => prefix_upper, options_ptr => &bridge.options_param_c, options_type_handle => &bridge.options_type_handle },
));
out.push_str(" }\n");
out.push_str(&crate::backends::java::template_env::render(
"ffi_throw_on_null.jinja",
minijinja::context! { exception_class },
));
out.push_str(" }\n\n");
}
fn emit_visitor_result_conversion(out: &mut String, func: &FunctionDef, prefix_upper: &str) {
out.push_str(" if (resultPtr.equals(MemorySegment.NULL)) {\n");
out.push_str(" checkLastError();\n return null;\n }\n");
let result_type_handle = return_type_name(&func.return_type)
.map(|name| name.to_snake_case().to_uppercase())
.unwrap_or_else(|| "OBJECT".to_owned());
out.push_str(" nativeResources.register(resultPtr, handle -> NativeLib.");
out.push_str(prefix_upper);
out.push('_');
out.push_str(&result_type_handle);
out.push_str("_FREE.invoke(handle));\n");
out.push_str(&crate::backends::java::template_env::render(
"ffi_result_to_json.jinja",
minijinja::context! { pu => prefix_upper, result_type_handle },
));
out.push_str(" // CPD-OFF\n");
out.push_str(" if (jsonPtr.equals(MemorySegment.NULL)) {\n");
out.push_str(" checkLastError();\n return null;\n }\n");
out.push_str(" nativeResources.register(jsonPtr, handle -> NativeLib.");
out.push_str(prefix_upper);
out.push_str("_FREE_STRING.invoke(handle));\n");
emit_visitor_json_return(out, func);
out.push_str(" // CPD-ON\n");
}
fn emit_visitor_cleanup(out: &mut String, prefix_upper: &str, exception_class: &str) {
super::error_catch::emit_visitor_operation_catch_chain(out, exception_class);
out.push_str(&crate::backends::java::template_env::render(
"ffi_visitor_cleanup.jinja",
minijinja::context! { pu => prefix_upper },
));
super::error_catch::emit_method_catch_chain(out, exception_class);
out.push_str(" }\n");
}
#[allow(clippy::too_many_arguments)]
fn emit_visitor_method_open(
out: &mut String,
func: &FunctionDef,
prefix: &str,
exception_class: &str,
opaque_types: &AHashSet<String>,
bridge_param_names: &HashSet<String>,
bridge_type_aliases: &HashSet<String>,
bridge: &VisitorFunctionBridge,
) {
out.push_str(&crate::backends::java::template_env::render(
"convert_with_visitor_signature.jinja",
minijinja::context! {
return_type => java_return_type(&func.return_type),
method_name => &bridge.internal_method_name,
params => visitor_method_params(func, bridge_param_names, bridge_type_aliases),
exception_class,
},
));
out.push_str(" try (var arena = Arena.ofShared();\n");
out.push_str(" var nativeResources = new NativeResources();\n");
emit_visitor_resources(out, func, opaque_types, bridge_param_names, bridge_type_aliases);
out.push_str(" var bridge = new VisitorBridge(");
out.push_str(&bridge.options_param_java);
out.push('.');
out.push_str(&bridge.options_field_java);
out.push_str("())) {\n");
emit_visitor_param_marshalling(out, func, prefix, opaque_types, bridge_param_names, bridge_type_aliases);
}
#[allow(clippy::too_many_arguments)]
fn emit_visitor_operation(
out: &mut String,
func: &FunctionDef,
prefix_upper: &str,
options_set_handle: &str,
opaque_types: &AHashSet<String>,
bridge_param_names: &HashSet<String>,
bridge_type_aliases: &HashSet<String>,
bridge: &VisitorFunctionBridge,
) {
out.push_str(" Throwable operationFailure = null;\n try {\n");
out.push_str(&crate::backends::java::template_env::render(
"ffi_options_set_visitor.jinja",
minijinja::context! { handle_name => options_set_handle, options_ptr => &bridge.options_param_c },
));
let ffi_handle = format!("NativeLib.{}_{}", prefix_upper, func.name.to_uppercase());
out.push_str(&crate::backends::java::template_env::render(
"ffi_result_ptr_call.jinja",
minijinja::context! { ffi_handle, args => visitor_call_args(func, opaque_types, bridge_param_names, bridge_type_aliases) },
));
out.push_str(&crate::backends::java::template_env::render(
"ffi_options_free_conditional.jinja",
minijinja::context! { pu => prefix_upper, options_ptr => &bridge.options_param_c, options_type_handle => &bridge.options_type_handle },
));
emit_visitor_result_conversion(out, func, prefix_upper);
}
#[allow(clippy::too_many_arguments)]
pub(super) fn gen_convert_with_visitor_internal_method(
func: &FunctionDef,
class_name: &str,
prefix: &str,
opaque_types: &AHashSet<String>,
bridge_param_names: &HashSet<String>,
bridge_type_aliases: &HashSet<String>,
visitor_bridge: &VisitorFunctionBridge,
) -> String {
let mut out = String::with_capacity(2048);
let pu = prefix.to_uppercase();
let options_set_handle = format!(
"{}_OPTIONS_SET_{}",
pu,
visitor_bridge.options_field_native.to_uppercase()
);
let exc = format!("{class_name}Exception");
emit_visitor_method_open(
&mut out,
func,
prefix,
&exc,
opaque_types,
bridge_param_names,
bridge_type_aliases,
visitor_bridge,
);
out.push('\n');
emit_visitor_handle_setup(&mut out, &pu, &exc, visitor_bridge);
emit_visitor_operation(
&mut out,
func,
&pu,
&options_set_handle,
opaque_types,
bridge_param_names,
bridge_type_aliases,
visitor_bridge,
);
emit_visitor_cleanup(&mut out, &pu, &exc);
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn visitor_cleanup_preserves_primary_and_attempts_every_release() {
let func = FunctionDef {
name: "convert".into(),
return_type: TypeRef::Named("ResultRecord".into()),
..Default::default()
};
let bridge = VisitorFunctionBridge {
options_param_java: "options".into(),
options_param_c: "cOptions".into(),
options_type_handle: "OPTIONS".into(),
options_field_java: "visitor".into(),
options_field_native: "visitor".into(),
internal_method_name: "convertWithVisitorInternal".into(),
};
let generated = gen_convert_with_visitor_internal_method(
&func,
"SampleRs",
"sample",
&AHashSet::new(),
&HashSet::new(),
&HashSet::new(),
&bridge,
);
let visitor_free = generated.find("SAMPLE_VISITOR_FREE.invoke").unwrap();
let bridge_error = generated.find("bridge.rethrowVisitorError()").unwrap();
assert!(visitor_free < bridge_error, "{generated}");
assert!(
generated.contains("operationFailure.addSuppressed(aggregate)"),
"{generated}"
);
}
}