fn non_unit_return_type(return_type: &TypeRef, rendered_return_type: &str) -> Option<String> {
if matches!(return_type, TypeRef::Unit) {
None
} else {
Some(rendered_return_type.to_string())
}
}
fn push_jni_external_fun(
out: &mut String,
native_name: &str,
params: &str,
return_type: Option<String>,
throws_class: Option<&str>,
) {
out.push_str(&template_env::render(
"jni_external_fun.jinja",
minijinja::context! {
native_name => native_name,
params => params,
return_type => return_type,
throws_class => throws_class,
},
));
out.push('\n');
}
pub fn emit_streaming_jni_external_funs(out: &mut String, config: &ResolvedCrateConfig, exception_class: &str) {
let streaming: Vec<_> = config
.adapters
.iter()
.filter(|a| matches!(a.pattern, AdapterPattern::Streaming) && a.owner_type.is_some())
.collect();
if streaming.is_empty() {
return;
}
out.push_str("\n // JNI streaming external funs — implementations are Rust JNI shims.\n");
for adapter in &streaming {
let Some(owner) = adapter.owner_type.as_deref() else {
continue;
};
let owner_pascal = to_pascal_case(owner);
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");
out.push('\n');
out.push_str(&template_env::render(
"jni_streaming_extern_comment.jinja",
minijinja::context! {
owner => owner,
adapter_name => to_lower_camel(&adapter.name),
},
));
push_jni_external_fun(
out,
&jni_start,
"clientHandle: Long, requestJson: String",
Some("Long".to_string()),
Some(exception_class),
);
push_jni_external_fun(
out,
&jni_next,
"streamHandle: Long",
Some("String?".to_string()),
Some(exception_class),
);
push_jni_external_fun(out, &jni_free, "streamHandle: Long", None, None);
}
}
fn emit_method_jni_external_funs(
out: &mut String,
api: &ApiSurface,
exclude_functions: &std::collections::HashSet<&str>,
exception_class: &str,
emitted_destructor_names: &mut std::collections::HashSet<String>,
) {
let client_types: Vec<_> = api
.types
.iter()
.filter(|t| t.is_opaque && !t.is_trait && t.methods.iter().any(|m| !m.is_static))
.collect();
if client_types.is_empty() {
return;
}
out.push_str("\n // JNI external funs for client instance methods.\n");
for ty in &client_types {
let owner_pascal = to_pascal_case(&ty.name);
for method in ty.methods.iter().filter(|m| !m.is_static) {
if exclude_functions.contains(method.name.as_str()) {
continue;
}
let native_name = format!("native{owner_pascal}{}", to_pascal_case(&method.name));
let return_ty = jni_return_type(&method.return_type);
let params = if method.params.is_empty() {
"handle: Long".to_string()
} else if method.params.len() == 1 && is_binary_param_type(&method.params[0].ty) {
format!("handle: Long, {}: ByteArray", to_lower_camel(&method.params[0].name))
} else {
"handle: Long, requestJson: String".to_string()
};
push_jni_external_fun(
out,
&native_name,
¶ms,
non_unit_return_type(&method.return_type, return_ty),
Some(exception_class),
);
}
let free_name = format!("nativeFree{owner_pascal}");
push_jni_external_fun(out, &free_name, "handle: Long", None, None);
emitted_destructor_names.insert(free_name);
}
}