use crate::backends::kotlin_android::naming::bridge_object_name;
use crate::core::config::TraitBridgeConfig;
use crate::core::ir::TypeDef;
use heck::ToUpperCamelCase;
use std::collections::BTreeSet;
pub fn gen_trait_bridge_object(
package: &str,
trait_name: &str,
bridge_cfg: &TraitBridgeConfig,
_trait_def: &TypeDef,
bridge_class_name: &str,
) -> Option<(String, String)> {
let interface_name = format!("I{trait_name}");
let bridge_obj = bridge_object_name(trait_name);
let _imports = BTreeSet::new();
let mut body = String::new();
body.push_str(&format!("object {bridge_obj} {{\n"));
body.push_str(&format!(
" private val registered = mutableMapOf<String, {interface_name}>()\n\n"
));
let has_super_trait = bridge_cfg.super_trait.is_some();
if has_super_trait {
body.push_str(&format!(" fun register(impl: {interface_name}): Unit {{\n"));
body.push_str(" val name = impl.name()\n");
} else {
body.push_str(&format!(
" fun register(impl: {interface_name}, name: String): Unit {{\n"
));
}
body.push_str(" registered[name] = impl\n");
let native_fn = format!("nativeRegister{}", trait_name.to_upper_camel_case());
if has_super_trait {
body.push_str(&format!(" {bridge_class_name}.{native_fn}(impl)\n"));
} else {
body.push_str(&format!(" {bridge_class_name}.{native_fn}(impl, name)\n"));
}
body.push_str(" }\n\n");
if bridge_cfg.unregister_fn.is_some() {
body.push_str(" fun unregister(name: String): Unit {\n");
body.push_str(" registered.remove(name)\n");
let native_fn = format!("nativeUnregister{}", trait_name.to_upper_camel_case());
body.push_str(&format!(" {bridge_class_name}.{native_fn}(name)\n"));
body.push_str(" }\n\n");
}
if bridge_cfg.clear_fn.is_some() {
body.push_str(" fun clearAll(): Unit {\n");
body.push_str(" registered.clear()\n");
let native_fn = format!("nativeClear{}s", trait_name.to_upper_camel_case());
body.push_str(&format!(" {bridge_class_name}.{native_fn}()\n"));
body.push_str(" }\n\n");
}
body.push_str(&format!(
" fun getAll(): Map<String, {interface_name}> = registered.toMap()\n"
));
body.push_str("}\n");
let content = assemble_kt_content(package, &_imports, &body);
Some((format!("{bridge_class_name}.kt"), content))
}
fn assemble_kt_content(package: &str, imports: &BTreeSet<String>, body: &str) -> String {
let mut out = String::new();
out.push_str("// Generated by alef. Do not edit by hand.\n\n");
out.push_str(&format!("package {package}\n"));
if !imports.is_empty() {
out.push('\n');
for import in imports {
out.push_str(&format!("import {import}\n"));
}
}
if !imports.is_empty() || !body.is_empty() {
out.push('\n');
}
out.push_str(body);
out
}
#[cfg(test)]
mod tests {
use super::*;
use heck::ToSnakeCase;
fn make_bridge_cfg(trait_name: &str, super_trait: Option<&str>) -> TraitBridgeConfig {
TraitBridgeConfig {
trait_name: trait_name.to_string(),
param_name: None,
type_alias: None,
exclude_languages: vec![],
super_trait: super_trait.map(|s| s.to_string()),
registry_getter: None,
register_fn: Some(format!("register_{}", trait_name.to_snake_case())),
unregister_fn: Some(format!("unregister_{}", trait_name.to_snake_case())),
clear_fn: Some(format!("clear_{}", trait_name.to_snake_case())),
register_extra_args: None,
bind_via: crate::core::config::BridgeBinding::FunctionParam,
options_type: None,
options_field: None,
context_type: None,
result_type: None,
ffi_skip_methods: Vec::new(),
}
}
fn make_trait_def(name: &str) -> TypeDef {
TypeDef {
name: name.to_string(),
rust_path: format!("testcrate::{}", name),
original_rust_path: String::new(),
fields: vec![],
methods: vec![],
is_opaque: false,
is_clone: true,
is_copy: false,
is_trait: true,
has_default: false,
has_stripped_cfg_fields: false,
is_return_type: false,
serde_rename_all: None,
has_serde: false,
super_traits: vec![],
doc: String::new(),
cfg: None,
binding_excluded: false,
binding_exclusion_reason: None,
}
}
#[test]
fn test_bridge_object_with_super_trait() {
let trait_def = make_trait_def("OcrBackend");
let bridge_cfg = make_bridge_cfg("OcrBackend", Some("Plugin"));
let (_filename, content) =
gen_trait_bridge_object("dev.kreuzberg", "OcrBackend", &bridge_cfg, &trait_def, "TestBridge")
.expect("should generate bridge");
assert!(content.contains("object OcrBackendBridge"));
assert!(content.contains("fun register(impl: IOcrBackend): Unit"));
assert!(content.contains("val name = impl.name()"));
assert!(content.contains("TestBridge.nativeRegisterOcrBackend(impl)"));
assert!(content.contains("nativeRegisterOcrBackend"));
}
#[test]
fn test_bridge_object_without_super_trait() {
let trait_def = make_trait_def("OcrBackend");
let mut bridge_cfg = make_bridge_cfg("OcrBackend", None);
bridge_cfg.super_trait = None;
let (_filename, content) =
gen_trait_bridge_object("dev.kreuzberg", "OcrBackend", &bridge_cfg, &trait_def, "TestBridge")
.expect("should generate bridge");
assert!(content.contains("object OcrBackendBridge"));
assert!(content.contains("fun register(impl: IOcrBackend, name: String): Unit"));
assert!(!content.contains("val name = impl.name()"));
assert!(content.contains("TestBridge.nativeRegisterOcrBackend(impl, name)"));
}
#[test]
fn test_bridge_object_with_unregister() {
let trait_def = make_trait_def("OcrBackend");
let bridge_cfg = make_bridge_cfg("OcrBackend", Some("Plugin"));
let (_filename, content) =
gen_trait_bridge_object("dev.kreuzberg", "OcrBackend", &bridge_cfg, &trait_def, "TestBridge")
.expect("should generate bridge");
assert!(content.contains("fun unregister(name: String): Unit"));
assert!(content.contains("TestBridge.nativeUnregisterOcrBackend(name)"));
assert!(content.contains("nativeUnregisterOcrBackend"));
}
#[test]
fn test_bridge_object_with_clear() {
let trait_def = make_trait_def("OcrBackend");
let bridge_cfg = make_bridge_cfg("OcrBackend", Some("Plugin"));
let (_filename, content) =
gen_trait_bridge_object("dev.kreuzberg", "OcrBackend", &bridge_cfg, &trait_def, "TestBridge")
.expect("should generate bridge");
assert!(content.contains("fun clearAll(): Unit"));
assert!(content.contains("TestBridge.nativeClearOcrBackends()"));
assert!(content.contains("nativeClearOcrBackends"));
}
#[test]
fn test_bridge_object_without_unregister() {
let trait_def = make_trait_def("OcrBackend");
let mut bridge_cfg = make_bridge_cfg("OcrBackend", Some("Plugin"));
bridge_cfg.unregister_fn = None;
let (_filename, content) =
gen_trait_bridge_object("dev.kreuzberg", "OcrBackend", &bridge_cfg, &trait_def, "TestBridge")
.expect("should generate bridge");
assert!(!content.contains("fun unregister"));
}
#[test]
fn test_bridge_object_without_clear() {
let trait_def = make_trait_def("OcrBackend");
let mut bridge_cfg = make_bridge_cfg("OcrBackend", Some("Plugin"));
bridge_cfg.clear_fn = None;
let (_filename, content) =
gen_trait_bridge_object("dev.kreuzberg", "OcrBackend", &bridge_cfg, &trait_def, "TestBridge")
.expect("should generate bridge");
assert!(!content.contains("fun clearAll"));
}
#[test]
fn test_bridge_object_includes_getall() {
let trait_def = make_trait_def("OcrBackend");
let bridge_cfg = make_bridge_cfg("OcrBackend", Some("Plugin"));
let (_filename, content) =
gen_trait_bridge_object("dev.kreuzberg", "OcrBackend", &bridge_cfg, &trait_def, "TestBridge")
.expect("should generate bridge");
assert!(content.contains("fun getAll(): Map<String, IOcrBackend>"));
}
}