use crate::core::backend::GeneratedFile;
use crate::core::config::ResolvedCrateConfig;
use crate::core::ir::{ApiSurface, EntrypointKind, ServiceDef, TypeRef};
use heck::{ToLowerCamelCase, ToSnakeCase, ToUpperCamelCase};
use std::path::PathBuf;
fn java_type_for_metadata(ty: &TypeRef) -> String {
match ty {
TypeRef::String | TypeRef::Char => "String".to_owned(),
TypeRef::Primitive(p) => {
use crate::core::ir::PrimitiveType;
match p {
PrimitiveType::Bool => "boolean".to_owned(),
PrimitiveType::U8 | PrimitiveType::I8 => "byte".to_owned(),
PrimitiveType::U16 | PrimitiveType::I16 => "short".to_owned(),
PrimitiveType::U32 | PrimitiveType::I32 => "int".to_owned(),
PrimitiveType::U64 | PrimitiveType::I64 => "long".to_owned(),
PrimitiveType::F32 => "float".to_owned(),
PrimitiveType::F64 => "double".to_owned(),
PrimitiveType::Usize | PrimitiveType::Isize => "long".to_owned(),
}
}
TypeRef::Bytes => "byte[]".to_owned(),
TypeRef::Unit => "void".to_owned(),
_ => "Object".to_owned(),
}
}
fn gen_service_class(_api: &ApiSurface, service: &ServiceDef, package: &str) -> String {
let mut out = String::new();
out.push_str("// Auto-generated by alef — DO NOT EDIT\n\n");
out.push_str(&format!("package {};\n\n", package));
let class_name = &service.name;
out.push_str("/**\n");
out.push_str(&format!(" * Service wrapper for {}.\n", service.name));
out.push_str(" */\n");
out.push_str(&format!(
"public class {} implements AutoCloseable {{\n\n",
class_name
));
out.push_str(" private long ownerHandle;\n\n");
{
out.push_str(" /**\n");
out.push_str(&format!(" * Create a new {}.\n", service.name));
out.push_str(" */\n");
out.push_str(&format!(" public {}() {{\n", class_name));
let service_snake = service.name.to_snake_case();
out.push_str(&format!(
" this.ownerHandle = nativeConstructor{}();\n",
service_snake.to_upper_camel_case()
));
out.push_str(" }\n\n");
}
for reg in &service.registrations {
let reg_method = ®.method;
let reg_method_camel = reg_method.to_upper_camel_case();
let service_pascal = service.name.to_upper_camel_case();
out.push_str(" /**\n");
out.push_str(&format!(" * Register a handler for {}.\n", reg_method));
out.push_str(" */\n");
out.push_str(&format!(
" public int register{}{}(Callable handler",
service_pascal, reg_method_camel
));
for meta_param in ®.metadata_params {
let java_type = java_type_for_metadata(&meta_param.ty);
let param_name = meta_param.name.to_lower_camel_case();
out.push_str(&format!(", {} {}", java_type, param_name));
}
out.push_str(") {\n");
out.push_str(&format!(
" return nativeRegister{}{}(ownerHandle, handler",
service_pascal, reg_method_camel
));
for meta_param in ®.metadata_params {
let param_name = meta_param.name.to_lower_camel_case();
out.push_str(&format!(", {}", param_name));
}
out.push_str(");\n");
out.push_str(" }\n\n");
}
for ep in &service.entrypoints {
let ep_method = &ep.method;
let ep_method_camel = ep_method.to_upper_camel_case();
let service_pascal = service.name.to_upper_camel_case();
out.push_str(" /**\n");
out.push_str(&format!(" * {}.\n", ep_method));
out.push_str(" */\n");
let return_type = match ep.kind {
EntrypointKind::Run => "void",
EntrypointKind::Finalize => "long",
};
out.push_str(&format!(
" public {} {}(",
return_type, ep_method
));
for (i, param) in ep.params.iter().enumerate() {
if i > 0 {
out.push_str(", ");
}
let java_type = java_type_for_metadata(¶m.ty);
let param_name = param.name.to_lower_camel_case();
out.push_str(&format!("{} {}", java_type, param_name));
}
out.push_str(") {\n");
let call_prefix = if matches!(ep.kind, EntrypointKind::Finalize) {
"return "
} else {
""
};
out.push_str(&format!(
" {}nativeEntrypoint{}{}(ownerHandle",
call_prefix, ep_method_camel, service_pascal
));
for param in &ep.params {
let param_name = param.name.to_lower_camel_case();
out.push_str(&format!(", {}", param_name));
}
out.push_str(");\n");
out.push_str(" }\n\n");
}
let service_snake = service.name.to_snake_case();
out.push_str(" @Override\n");
out.push_str(" public void close() {\n");
out.push_str(&format!(
" if (ownerHandle != 0L) {{\n nativeFree{}(ownerHandle);\n ownerHandle = 0L;\n }}\n",
service_snake.to_upper_camel_case()
));
out.push_str(" }\n\n");
out.push_str(" // ─── Native method declarations ───\n\n");
out.push_str(" /**\n");
out.push_str(" * Allocate a new service instance via JNI.\n");
out.push_str(" *\n");
out.push_str(&format!(
" * Maps to: Java_com_example_constructor_{service_snake}()\n"
));
out.push_str(" */\n");
out.push_str(&format!(
" private static native long nativeConstructor{}();\n\n",
service_snake.to_upper_camel_case()
));
out.push_str(" /**\n");
out.push_str(" * Free the service instance via JNI.\n");
out.push_str(" *\n");
out.push_str(&format!(
" * Maps to: Java_com_example_free_{service_snake}(env, class, handle)\n"
));
out.push_str(" */\n");
out.push_str(&format!(
" private static native void nativeFree{}(long handle);\n\n",
service_snake.to_upper_camel_case()
));
for reg in &service.registrations {
let service_pascal = service.name.to_upper_camel_case();
let reg_method_camel = reg.method.to_upper_camel_case();
out.push_str(" /**\n");
out.push_str(&format!(
" * Register a handler for {} via JNI.\n",
reg.method
));
out.push_str(" *\n");
out.push_str(&format!(
" * Maps to: Java_com_example_register{}{}(...)\n",
service_pascal, reg_method_camel
));
out.push_str(" */\n");
out.push_str(&format!(
" private static native int nativeRegister{}{}(long ownerHandle, Callable handler",
service_pascal, reg_method_camel
));
for meta_param in ®.metadata_params {
let java_type = java_type_for_metadata(&meta_param.ty);
out.push_str(&format!(", {} {}", java_type, meta_param.name));
}
out.push_str(");\n\n");
}
for ep in &service.entrypoints {
let service_pascal = service.name.to_upper_camel_case();
let ep_method_camel = ep.method.to_upper_camel_case();
let return_type = match ep.kind {
EntrypointKind::Run => "void",
EntrypointKind::Finalize => "long",
};
out.push_str(" /**\n");
out.push_str(&format!(
" * Drive {} entrypoint via JNI.\n",
ep.method
));
out.push_str(" *\n");
out.push_str(&format!(
" * Maps to: Java_com_example_{}{}(env, class, ownerHandle, ...)\n",
ep.method, service_pascal
));
out.push_str(" */\n");
out.push_str(&format!(
" private static native {} nativeEntrypoint{}{}(long ownerHandle",
return_type, ep_method_camel, service_pascal
));
for param in &ep.params {
let java_type = java_type_for_metadata(¶m.ty);
out.push_str(&format!(", {} {}", java_type, param.name));
}
out.push_str(");\n\n");
}
out.push_str("}\n");
out
}
fn gen_callable_interface(package: &str) -> String {
let mut out = String::new();
out.push_str("// Auto-generated by alef — DO NOT EDIT\n\n");
out.push_str(&format!("package {};\n\n", package));
out.push_str("/**\n");
out.push_str(" * Functional interface for service handlers.\n");
out.push_str(" *\n");
out.push_str(" * Implementations receive a JSON request string and return a JSON response string.\n");
out.push_str(" */\n");
out.push_str("@FunctionalInterface\n");
out.push_str("public interface Callable {\n");
out.push_str(" /**\n");
out.push_str(" * Handle a request.\n");
out.push_str(" *\n");
out.push_str(" * @param request JSON request string\n");
out.push_str(" * @return JSON response string\n");
out.push_str(" */\n");
out.push_str(" String handle(String request);\n");
out.push_str("}\n");
out
}
pub fn generate(api: &ApiSurface, config: &ResolvedCrateConfig) -> anyhow::Result<Vec<GeneratedFile>> {
if api.services.is_empty() {
return Ok(vec![]);
}
let package = config.java_package();
let package_path = package.replace('.', "/");
let output_dir = config
.output_for("java")
.map(|p| p.to_string_lossy().into_owned())
.unwrap_or_else(|| "packages/java/src/main/java/".to_string());
let base_path = if output_dir.ends_with(&package_path) || output_dir.ends_with(&format!("{}/", package_path)) {
PathBuf::from(&output_dir)
} else {
PathBuf::from(&output_dir).join(&package_path)
};
let mut files = Vec::new();
for service in &api.services {
let service_class = gen_service_class(api, service, &package);
files.push(GeneratedFile {
path: base_path.join(format!("{}.java", service.name)),
content: service_class,
generated_header: false, });
}
files.push(GeneratedFile {
path: base_path.join("Callable.java"),
content: gen_callable_interface(&package),
generated_header: false,
});
Ok(files)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::ir::{
EntrypointDef, EntrypointKind, HandlerContractDef, MethodDef, ParamDef, RegistrationDef, ServiceDef, TypeRef,
};
fn make_fixture_surface() -> ApiSurface {
let constructor = MethodDef {
name: "new".to_owned(),
params: vec![],
return_type: TypeRef::Unit,
is_async: false,
is_static: true,
error_type: None,
doc: "Create a new service owner.".to_owned(),
receiver: None,
sanitized: false,
trait_source: None,
returns_ref: false,
returns_cow: false,
return_newtype_wrapper: None,
has_default_impl: false,
binding_excluded: false,
binding_exclusion_reason: None,
};
let registration = RegistrationDef {
method: "add_handler".to_owned(),
callback_param: "handler".to_owned(),
callback_contract: "RequestHandler".to_owned(),
metadata_params: vec![ParamDef {
name: "path".to_owned(),
ty: TypeRef::String,
optional: false,
default: None,
..ParamDef::default()
}],
receiver: Some(crate::core::ir::ReceiverKind::RefMut),
return_type: TypeRef::Unit,
error_type: None,
doc: "Register a request handler.".to_owned(),
};
let run_ep = EntrypointDef {
method: "run".to_owned(),
kind: EntrypointKind::Run,
is_async: true,
params: vec![ParamDef {
name: "addr".to_owned(),
ty: TypeRef::String,
optional: false,
default: None,
..ParamDef::default()
}],
return_type: TypeRef::Unit,
error_type: Some("ServiceError".to_owned()),
doc: "Run the service.".to_owned(),
};
let service = ServiceDef {
name: "TestService".to_owned(),
rust_path: "my_crate::TestService".to_owned(),
constructor,
configurators: vec![],
registrations: vec![registration],
entrypoints: vec![run_ep],
doc: "A test service owner.".to_owned(),
cfg: None,
};
let dispatch_method = MethodDef {
name: "handle".to_owned(),
params: vec![ParamDef {
name: "request".to_owned(),
ty: TypeRef::Named("RequestData".to_owned()),
optional: false,
default: None,
..ParamDef::default()
}],
return_type: TypeRef::Named("ResponseData".to_owned()),
is_async: true,
is_static: false,
error_type: Some("HandlerError".to_owned()),
doc: "Dispatch a request.".to_owned(),
receiver: Some(crate::core::ir::ReceiverKind::Ref),
sanitized: false,
trait_source: None,
returns_ref: false,
returns_cow: false,
return_newtype_wrapper: None,
has_default_impl: false,
binding_excluded: false,
binding_exclusion_reason: None,
};
let contract = HandlerContractDef {
trait_name: "RequestHandler".to_owned(),
rust_path: "my_crate::RequestHandler".to_owned(),
dispatch: dispatch_method,
optional_methods: vec![],
wire_request_type: Some("RequestData".to_owned()),
wire_response_type: Some("ResponseData".to_owned()),
doc: "Async trait for handling requests.".to_owned(),
};
ApiSurface {
crate_name: "my_crate".to_owned(),
version: "0.1.0".to_owned(),
services: vec![service],
handler_contracts: vec![contract],
..ApiSurface::default()
}
}
#[test]
fn java_class_contains_service_class() {
let surface = make_fixture_surface();
let java = gen_service_class(&surface, &surface.services[0], "com.example");
assert!(java.contains("public class TestService"));
assert!(java.contains("implements AutoCloseable"));
assert!(java.contains("private long ownerHandle"));
}
#[test]
fn java_class_contains_constructor() {
let surface = make_fixture_surface();
let java = gen_service_class(&surface, &surface.services[0], "com.example");
assert!(java.contains("public TestService()"));
assert!(java.contains("nativeConstructorTestService()"));
}
#[test]
fn java_class_contains_registration_method() {
let surface = make_fixture_surface();
let java = gen_service_class(&surface, &surface.services[0], "com.example");
assert!(java.contains("public int registerTestServiceAddHandler("));
assert!(java.contains("Callable handler"));
assert!(java.contains("String path"));
assert!(java.contains("nativeRegisterTestServiceAddHandler(ownerHandle, handler, path)"));
}
#[test]
fn java_class_contains_native_register_declaration() {
let surface = make_fixture_surface();
let java = gen_service_class(&surface, &surface.services[0], "com.example");
assert!(java.contains("private static native int nativeRegisterTestServiceAddHandler("));
assert!(java.contains("long ownerHandle, Callable handler, String path"));
assert!(
java.contains("Maps to: Java_com_example_registerTestServiceAddHandler"),
"native method should document JNI mapping: {}",
java
);
}
#[test]
fn java_class_contains_entrypoint_method() {
let surface = make_fixture_surface();
let java = gen_service_class(&surface, &surface.services[0], "com.example");
assert!(java.contains("public void run(String addr)"));
assert!(java.contains("nativeEntrypointRunTestService(ownerHandle, addr)"));
}
#[test]
fn java_class_contains_native_entrypoint_declaration() {
let surface = make_fixture_surface();
let java = gen_service_class(&surface, &surface.services[0], "com.example");
assert!(java.contains("private static native void nativeEntrypointRunTestService("));
assert!(java.contains("long ownerHandle, String addr"));
assert!(
java.contains("Maps to: Java_com_example_runTestService"),
"native entrypoint should document JNI mapping"
);
}
#[test]
fn java_class_contains_close_method() {
let surface = make_fixture_surface();
let java = gen_service_class(&surface, &surface.services[0], "com.example");
assert!(java.contains("@Override"));
assert!(java.contains("public void close()"));
assert!(java.contains("nativeFreeTestService(ownerHandle)"));
}
#[test]
fn java_class_contains_native_free_declaration() {
let surface = make_fixture_surface();
let java = gen_service_class(&surface, &surface.services[0], "com.example");
assert!(java.contains("private static native void nativeFreeTestService(long handle)"));
assert!(java.contains("Maps to: Java_com_example_free_test_service"));
}
#[test]
fn callable_interface_is_functional() {
let iface = gen_callable_interface("com.example");
assert!(iface.contains("@FunctionalInterface"));
assert!(iface.contains("public interface Callable"));
assert!(iface.contains("String handle(String request)"));
}
#[test]
fn generate_returns_service_and_callable() {
let surface = make_fixture_surface();
let config = ResolvedCrateConfig {
name: "my-crate".to_owned(),
..ResolvedCrateConfig::default()
};
let files = generate(&surface, &config).expect("generate should not fail");
assert!(files.len() >= 2, "expected at least service class + Callable interface");
let has_service_class = files.iter().any(|f| f.path.to_string_lossy().contains("TestService.java"));
let has_callable = files.iter().any(|f| f.path.to_string_lossy().contains("Callable.java"));
assert!(has_service_class, "expected TestService.java");
assert!(has_callable, "expected Callable.java");
}
#[test]
fn generate_returns_empty_for_no_services() {
let surface = ApiSurface::default();
let config = ResolvedCrateConfig {
name: "my-crate".to_owned(),
..ResolvedCrateConfig::default()
};
let files = generate(&surface, &config).expect("generate should not fail");
assert!(files.is_empty(), "expected no files for surface without services");
}
#[test]
fn java_class_passes_all_metadata_params() {
let mut surface = make_fixture_surface();
let reg = &mut surface.services[0].registrations[0];
reg.metadata_params.push(ParamDef {
name: "method".to_owned(),
ty: TypeRef::String,
optional: false,
default: None,
..ParamDef::default()
});
reg.metadata_params.push(ParamDef {
name: "priority".to_owned(),
ty: TypeRef::Primitive(crate::core::ir::PrimitiveType::I32),
optional: false,
default: None,
..ParamDef::default()
});
let java = gen_service_class(&surface, &surface.services[0], "com.example");
assert!(
java.contains("public int registerTestServiceAddHandler(Callable handler, String path, String method, int priority)"),
"registration method must include all metadata parameters"
);
assert!(
java.contains("nativeRegisterTestServiceAddHandler(ownerHandle, handler, path, method, priority)"),
"native call must pass all metadata parameters"
);
assert!(
java.contains("private static native int nativeRegisterTestServiceAddHandler(long ownerHandle, Callable handler, String path, String method, int priority)"),
"native declaration must include all parameters"
);
}
fn make_test_config() -> ResolvedCrateConfig {
ResolvedCrateConfig {
name: "my-crate".to_owned(),
..ResolvedCrateConfig::default()
}
}
}