use crate::core::config::ResolvedCrateConfig;
use heck::ToUpperCamelCase;
pub fn jni_package(config: &ResolvedCrateConfig) -> String {
config
.kotlin_android
.as_ref()
.and_then(|a| a.package.clone())
.or_else(|| config.kotlin.as_ref().and_then(|k| k.package.clone()))
.or_else(|| config.try_kotlin_package().ok())
.unwrap_or_else(|| {
let clean = config.name.replace(['-', '_'], "").to_lowercase();
format!("com.example.{clean}")
})
}
pub fn bridge_class_name(crate_name: &str) -> String {
format!("{}Bridge", crate_name.to_upper_camel_case())
}
pub fn service_bridge_class_name(service_name: &str) -> String {
format!("{}ServiceBridge", service_name.to_upper_camel_case())
}
pub fn bridge_method_name(owner: &str, method: &str) -> String {
let owner_pascal = owner.to_upper_camel_case();
let method_pascal = method.to_upper_camel_case();
if owner_pascal.is_empty() {
format!("native{method_pascal}")
} else {
format!("native{owner_pascal}{method_pascal}")
}
}
pub fn streaming_method_names(owner: &str, method: &str) -> (String, String, String) {
let owner_pascal = owner.to_upper_camel_case();
let method_pascal = method.to_upper_camel_case();
(
format!("native{owner_pascal}{method_pascal}Start"),
format!("native{owner_pascal}{method_pascal}Next"),
format!("native{owner_pascal}{method_pascal}Free"),
)
}
pub fn destructor_method_name(owner: &str) -> String {
let owner_pascal = owner.to_upper_camel_case();
format!("nativeFree{owner_pascal}")
}
pub fn jni_symbol(package: &str, class: &str, method: &str) -> String {
let encode = |s: &str| s.replace('_', "_1").replace('.', "_");
let pkg_encoded = encode(package);
let class_encoded = encode(class);
if method.is_empty() {
format!("Java_{pkg_encoded}_{class_encoded}")
} else {
let method_encoded = encode(method);
format!("Java_{pkg_encoded}_{class_encoded}_{method_encoded}")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn bridge_class_name_basic() {
assert_eq!(bridge_class_name("demo"), "DemoBridge");
assert_eq!(bridge_class_name("my-lib"), "MyLibBridge");
assert_eq!(bridge_class_name("my_lib"), "MyLibBridge");
}
#[test]
fn bridge_method_name_with_owner() {
assert_eq!(bridge_method_name("DemoClient", "foo"), "nativeDemoClientFoo");
assert_eq!(bridge_method_name("demo_client", "bar_baz"), "nativeDemoClientBarBaz");
}
#[test]
fn bridge_method_name_no_owner() {
assert_eq!(bridge_method_name("", "createClient"), "nativeCreateClient");
assert_eq!(bridge_method_name("", "create_client"), "nativeCreateClient");
}
#[test]
fn streaming_method_names_basic() {
let (s, n, f) = streaming_method_names("DemoClient", "streamData");
assert_eq!(s, "nativeDemoClientStreamDataStart");
assert_eq!(n, "nativeDemoClientStreamDataNext");
assert_eq!(f, "nativeDemoClientStreamDataFree");
}
#[test]
fn destructor_method_name_basic() {
assert_eq!(destructor_method_name("DemoClient"), "nativeFreeDemoClient");
assert_eq!(destructor_method_name("demo_client"), "nativeFreeDemoClient");
}
#[test]
fn jni_symbol_basic() {
let sym = jni_symbol("dev.sample_crate.demo", "DemoBridge", "nativeFoo");
assert_eq!(sym, "Java_dev_sample_1crate_demo_DemoBridge_nativeFoo");
}
#[test]
fn jni_symbol_underscore_in_class_encoded() {
let sym = jni_symbol("dev.demo", "Demo_Bridge", "nativeBar");
assert_eq!(sym, "Java_dev_demo_Demo_1Bridge_nativeBar");
}
#[test]
fn jni_symbol_empty_method_gives_prefix() {
let prefix = jni_symbol("dev.sample_crate.demo", "DemoBridge", "");
assert_eq!(prefix, "Java_dev_sample_1crate_demo_DemoBridge");
}
#[test]
fn jni_package_prefers_kotlin_android() {
let config = ResolvedCrateConfig {
name: "test-lib".to_owned(),
..ResolvedCrateConfig::default()
};
assert_eq!(jni_package(&config), "com.example.testlib");
}
}