fn emit_jni_client_method(
m: &crate::core::ir::MethodDef,
class_name: &str,
bridge_name: &str,
out: &mut String,
imports: &mut BTreeSet<String>,
) {
if !m.doc.is_empty() {
for line in m.doc.lines() {
out.push_str(&template_env::render(
"line_comment.jinja",
minijinja::context! {
indent => " ",
line => line,
},
));
}
}
let method_name = to_lower_camel(&m.name);
let native_name = format!("native{}{}", to_pascal_case(class_name), to_pascal_case(&m.name));
let async_kw = if m.is_async { "suspend " } else { "" };
let params_with_types: Vec<String> = m.params.iter().map(|p| format_param_with_imports(p, imports)).collect();
let wrapper_return_ty = if is_binary_return_type(&m.return_type) {
"ByteArray".to_string()
} else if is_optional_binary_return_type(&m.return_type) {
"ByteArray?".to_string()
} else {
kotlin_type_with_string_imports(&m.return_type, false, imports)
};
out.push_str(&template_env::render(
"jni_client_method_header.jinja",
minijinja::context! {
async_kw => async_kw,
method_name => method_name,
params => params_with_types.join(", "),
return_type => wrapper_return_ty,
},
));
let bridge_call = build_bridge_call(m, bridge_name, &native_name);
emit_method_body(m, out, &bridge_call, imports);
out.push_str(" }\n\n");
}
fn build_bridge_call(m: &crate::core::ir::MethodDef, bridge_name: &str, native_name: &str) -> String {
if m.params.is_empty() {
return format!("{bridge_name}.{native_name}(handle)");
}
if m.params.len() == 1 && is_binary_param_type(&m.params[0].ty) {
let p = &m.params[0];
let param_name = to_lower_camel(&p.name);
let arg = if p.optional {
format!("{param_name} ?: ByteArray(0)")
} else {
param_name
};
return format!("{bridge_name}.{native_name}(handle, {arg})");
}
let request_json_expr = if m.params.len() == 1 {
let p = &m.params[0];
let param_name = to_lower_camel(&p.name);
if p.optional {
format!("{param_name}?.let {{ MAPPER.writeValueAsString(it) }} ?: \"\"")
} else {
format!("MAPPER.writeValueAsString({param_name})")
}
} else {
let map_entries: Vec<String> = m
.params
.iter()
.map(|p| {
let name = to_lower_camel(&p.name);
format!("\"{name}\" to {name}")
})
.collect();
format!("MAPPER.writeValueAsString(mapOf({}))", map_entries.join(", "))
};
format!("{bridge_name}.{native_name}(handle, {request_json_expr})")
}
fn emit_method_body(
m: &crate::core::ir::MethodDef,
out: &mut String,
bridge_call: &str,
imports: &mut BTreeSet<String>,
) {
let needs_deserialize = needs_json_deserialize(&m.return_type);
let return_kotlin_type = if needs_deserialize {
Some(kotlin_type_with_string_imports(&m.return_type, false, imports))
} else {
None
};
match &m.return_type {
TypeRef::Unit => {
out.push_str(&template_env::render(
"jni_unit_body.jinja",
minijinja::context! {
is_async => m.is_async,
bridge_call => bridge_call,
},
));
}
_ if needs_deserialize => {
let kotlin_ty = return_kotlin_type.unwrap();
let base_ty = kotlin_ty.trim_end_matches('?');
let use_type_reference = base_ty.contains('<');
let deserialize_call = if use_type_reference {
imports.insert("import com.fasterxml.jackson.core.type.TypeReference".to_string());
format!("MAPPER.readValue(responseJson, object : TypeReference<{base_ty}>() {{}})")
} else {
format!("MAPPER.readValue(responseJson, {base_ty}::class.java)")
};
out.push_str(&template_env::render(
"jni_deserialize_body.jinja",
minijinja::context! {
is_async => m.is_async,
bridge_call => bridge_call,
deserialize_call => deserialize_call,
},
));
}
_ => {
out.push_str(&template_env::render(
"jni_passthrough_body.jinja",
minijinja::context! {
is_async => m.is_async,
bridge_call => bridge_call,
},
));
}
}
}
fn emit_jni_streaming_client_method(
adapter: &crate::core::config::AdapterConfig,
class_name: &str,
bridge_name: &str,
out: &mut String,
) {
let method_name = to_lower_camel(&adapter.name);
let item_type = adapter.item_type.as_deref().unwrap_or("Any");
let owner_pascal = to_pascal_case(class_name);
let adapter_pascal = to_pascal_case(&adapter.name);
let jni_start = format!("native{owner_pascal}{adapter_pascal}Start");
let jni_next = format!("native{owner_pascal}{adapter_pascal}Next");
let jni_free = format!("native{owner_pascal}{adapter_pascal}Free");
let params: Vec<String> = adapter
.params
.iter()
.map(|p| {
let simple_ty = p.ty.rsplit("::").next().unwrap_or(&p.ty);
let param_name = to_lower_camel(&p.name);
format!("{param_name}: {simple_ty}")
})
.collect();
let first_param_name = adapter
.params
.first()
.map(|p| to_lower_camel(&p.name))
.unwrap_or_else(|| "request".to_string());
out.push_str(&template_env::render(
"jni_streaming_client_method.jinja",
minijinja::context! {
method_name => method_name,
params => params.join(", "),
item_type => item_type,
bridge_name => bridge_name,
jni_start => jni_start,
jni_next => jni_next,
jni_free => jni_free,
first_param_name => first_param_name,
},
));
}