use crate::backends::java::template_env;
use crate::core::backend::GeneratedFile;
use crate::core::config::ResolvedCrateConfig;
use crate::core::ir::{ApiSurface, EntrypointKind, ServiceDef, TypeRef};
use heck::{ToLowerCamelCase, ToSnakeCase, ToUpperCamelCase};
use minijinja::context;
use std::path::PathBuf;
fn is_opaque_metadata(ty: &TypeRef, api: &ApiSurface) -> bool {
matches!(ty, TypeRef::Named(n) if api.types.iter().any(|t| t.name == *n))
}
fn java_type_for_metadata(ty: &TypeRef, api: &ApiSurface) -> 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(),
TypeRef::Named(n) if api.types.iter().any(|t| t.name == *n) => {
n.clone()
}
_ => "Object".to_owned(),
}
}
fn gen_service_class(api: &ApiSurface, service: &ServiceDef, package: &str, config: &ResolvedCrateConfig) -> 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("import java.lang.foreign.*;\n");
out.push_str("import java.lang.invoke.MethodHandle;\n");
out.push_str("import java.lang.invoke.MethodHandles;\n");
out.push_str("import java.lang.invoke.MethodType;\n\n");
let class_name = &service.name;
let service_snake = service.name.to_snake_case();
let ffi_prefix = config.ffi_prefix().to_lowercase();
out.push_str("/**\n");
out.push_str(&format!(" * Service wrapper for {} using Panama FFM.\n", service.name));
out.push_str(" *\n");
out.push_str(" * Binds to C FFI symbols:\n");
out.push_str(&format!(
" * - {}_{}_new() -> opaque handle\n",
ffi_prefix, service_snake
));
out.push_str(&format!(" * - {}_{}_free(opaque)\n", ffi_prefix, service_snake));
for reg in &service.registrations {
out.push_str(&format!(
" * - {}_{}_register_{} (handler registration)\n",
ffi_prefix,
service_snake,
reg.method.to_snake_case()
));
}
for ep in &service.entrypoints {
out.push_str(&format!(
" * - {}_{}_ep_{} (entrypoint)\n",
ffi_prefix,
service_snake,
ep.method.to_snake_case()
));
}
out.push_str(" */\n");
out.push_str(&format!("public class {} implements AutoCloseable {{\n\n", class_name));
out.push_str(" private MemorySegment ownerHandle;\n");
out.push_str(" private final Arena arena = Arena.ofConfined();\n\n");
out.push_str(" private static final Linker LINKER = Linker.nativeLinker();\n");
out.push_str(" private static final SymbolLookup LOOKUP = SymbolLookup.loaderLookup();\n\n");
out.push_str(" static {\n");
out.push_str(" // Force NativeLib static initialization to load the native library\n");
out.push_str(" // This ensures all FFI symbols are available before we try to look them up.\n");
out.push_str(" // NativeLib is a package-private class, but accessing its class forces\n");
out.push_str(" // its static initializer to run and load the native library.\n");
out.push_str(" try {\n");
out.push_str(&format!(" Class.forName(\"{}.NativeLib\");\n", package));
out.push_str(" } catch (ClassNotFoundException ignored) {\n");
out.push_str(" // NativeLib not available; native library may be pre-loaded\n");
out.push_str(" }\n");
out.push_str(" }\n\n");
{
out.push_str(" // Adapter for handler upcalls: marshals C pointers <-> Java strings\n");
out.push_str(" private static MemorySegment invokeHandlerWithMarshal(\n");
out.push_str(" MemorySegment contextPtr,\n");
out.push_str(" MemorySegment requestPtr,\n");
out.push_str(" Callable handler,\n");
out.push_str(" Arena arena) throws Throwable {\n");
out.push_str(" String requestStr = requestPtr.getString(0);\n");
out.push_str(" String responseStr = handler.handle(requestStr);\n");
out.push_str(" return arena.allocateFrom(responseStr);\n");
out.push_str(" }\n\n");
}
{
out.push_str(" /**\n");
out.push_str(&format!(" * Create a new {}.\n", service.name));
out.push_str(&format!(
" * Invokes C FFI: {}_{}_new()\n",
ffi_prefix, service_snake
));
out.push_str(" */\n");
out.push_str(&format!(" public {}() {{\n", class_name));
out.push_str(" try {\n");
out.push_str(&format!(
" MemorySegment addr = LOOKUP.find(\"{}_{}_new\")\n",
ffi_prefix, service_snake
));
out.push_str(&format!(
" .or(() -> LOOKUP.find(\"_{}_{}_new\"))\n",
ffi_prefix, service_snake
));
out.push_str(" .orElseThrow();\n");
out.push_str(" FunctionDescriptor desc = FunctionDescriptor.of(ValueLayout.ADDRESS);\n");
out.push_str(" MethodHandle handle = LINKER.downcallHandle(addr, desc);\n");
out.push_str(" this.ownerHandle = (MemorySegment) handle.invoke();\n");
out.push_str(" if (this.ownerHandle == null) {\n");
out.push_str(" throw new RuntimeException(\"Failed to allocate service instance\");\n");
out.push_str(" }\n");
out.push_str(" } catch (Throwable e) {\n");
out.push_str(" throw new RuntimeException(\"Failed to invoke constructor\", e);\n");
out.push_str(" }\n");
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 reg_method_snake = reg_method.to_snake_case();
out.push_str(" /**\n");
out.push_str(&format!(" * Register a handler for {}.\n", reg_method));
out.push_str(&format!(
" * Invokes C FFI: {}_{}_register_{}\n",
ffi_prefix, service_snake, reg_method_snake
));
out.push_str(" *\n");
out.push_str(" * @param handler functional interface receiving JSON request, returning JSON response\n");
for meta_param in ®.metadata_params {
out.push_str(&format!(
" * @param {} {}\n",
meta_param.name.to_lower_camel_case(),
java_type_for_metadata(&meta_param.ty, api)
));
}
out.push_str(" * @return 0 on success, non-zero error code on failure\n");
out.push_str(" */\n");
out.push_str(&format!(
" public int register{}{}(Callable handler",
class_name, reg_method_camel
));
for meta_param in ®.metadata_params {
let java_type = java_type_for_metadata(&meta_param.ty, api);
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(" try {\n");
out.push_str(" // Wrap Java handler in an upcall stub for C to invoke\n");
out.push_str(" MethodHandles.Lookup lookup = MethodHandles.lookup();\n");
out.push_str(" MethodHandle mh = lookup.findVirtual(Callable.class, \"handle\",\n");
out.push_str(" MethodType.methodType(String.class, String.class));\n");
out.push_str(" MethodHandle boundMh = mh.bindTo(handler);\n\n");
out.push_str(" // Build C FFI signature: (context: ADDRESS, request: ADDRESS) -> ADDRESS\n");
out.push_str(" FunctionDescriptor upcallDesc = FunctionDescriptor.of(\n");
out.push_str(" ValueLayout.ADDRESS, // return: *mut c_char\n");
out.push_str(" ValueLayout.ADDRESS, // param 0: *mut c_void (context)\n");
out.push_str(" ValueLayout.ADDRESS // param 1: *const c_char (request JSON)\n");
out.push_str(" );\n\n");
out.push_str(
" // Create adapter: (context_ptr: ADDRESS, request_ptr: ADDRESS) -> response_ptr: ADDRESS\n",
);
out.push_str(" // Marshals C pointers <-> Java strings via Arena\n");
out.push_str(" MethodHandle baseMh = lookup.findStatic(");
out.push_str(&format!("{}.class, ", class_name));
out.push_str("\"invokeHandlerWithMarshal\",\n");
out.push_str(" MethodType.methodType(MemorySegment.class, MemorySegment.class, MemorySegment.class, Callable.class, Arena.class)\n");
out.push_str(" );\n");
out.push_str(
" MethodHandle adapter = MethodHandles.insertArguments(baseMh, 2, handler, arena);\n\n",
);
out.push_str(" MemorySegment upcallStub = LINKER.upcallStub(adapter, upcallDesc, arena);\n\n");
out.push_str(" // Get register downcall handle\n");
out.push_str(&format!(
" MemorySegment regAddr = LOOKUP.find(\"{}_{}_register_{}\")\n",
ffi_prefix, service_snake, reg_method_snake
));
out.push_str(&format!(
" .or(() -> LOOKUP.find(\"_{}_{}_register_{}\"))\n",
ffi_prefix, service_snake, reg_method_snake
));
out.push_str(" .orElseThrow();\n");
out.push_str(" FunctionDescriptor regDesc = FunctionDescriptor.of(\n");
out.push_str(" ValueLayout.JAVA_INT, // return: int\n");
out.push_str(" ValueLayout.ADDRESS, // owner: *mut opaque\n");
out.push_str(" ValueLayout.ADDRESS // callback: upcall stub\n");
for meta_param in ®.metadata_params {
match &meta_param.ty {
TypeRef::String => {
out.push_str(",\n ValueLayout.ADDRESS // ");
}
TypeRef::Primitive(p) => {
use crate::core::ir::PrimitiveType;
let layout = match p {
PrimitiveType::Bool => "ValueLayout.JAVA_INT",
PrimitiveType::U8 | PrimitiveType::I8 => "ValueLayout.JAVA_BYTE",
PrimitiveType::U16 | PrimitiveType::I16 => "ValueLayout.JAVA_SHORT",
PrimitiveType::U32 | PrimitiveType::I32 => "ValueLayout.JAVA_INT",
PrimitiveType::U64 | PrimitiveType::I64 => "ValueLayout.JAVA_LONG",
PrimitiveType::F32 => "ValueLayout.JAVA_FLOAT",
PrimitiveType::F64 => "ValueLayout.JAVA_DOUBLE",
PrimitiveType::Usize | PrimitiveType::Isize => "ValueLayout.ADDRESS",
};
out.push_str(&format!(",\n {} // ", layout));
}
_ => {
out.push_str(",\n ValueLayout.ADDRESS // ");
}
}
out.push_str(&meta_param.name.to_lower_camel_case());
out.push_str(" param\n");
}
out.push_str(" );\n");
out.push_str(" MethodHandle regHandle = LINKER.downcallHandle(regAddr, regDesc);\n\n");
out.push_str(" return (int) regHandle.invokeExact(\n");
out.push_str(" ownerHandle, // owner\n");
out.push_str(" upcallStub // callback\n");
for meta_param in ®.metadata_params {
let param_name = meta_param.name.to_lower_camel_case();
if is_opaque_metadata(&meta_param.ty, api) {
out.push_str(&format!(
",\n {}.handle() // opaque handle",
param_name
));
} else if matches!(meta_param.ty, TypeRef::Primitive(crate::core::ir::PrimitiveType::Bool)) {
out.push_str(&format!(",\n ({} ? 1 : 0) // metadata", param_name));
} else {
out.push_str(&format!(",\n {} // metadata", param_name));
}
}
out.push_str("\n );\n");
out.push_str(" } catch (Throwable e) {\n");
out.push_str(" throw new RuntimeException(\"Failed to register handler\", e);\n");
out.push_str(" }\n");
out.push_str(" }\n\n");
}
for reg in &service.registrations {
let reg_method_snake = reg.method.to_snake_case();
for variant in ®.variants {
let variant_method_name = variant.name.to_lower_camel_case();
let ffi_symbol = format!(
"{}_{}_register_{}_{}",
ffi_prefix,
service_snake,
reg_method_snake,
variant.name.to_snake_case()
);
let doc = variant.doc.clone();
let ctx = context! {
method_name => variant_method_name.clone(),
variant_name_display => variant.name.to_lower_camel_case(),
ffi_symbol => ffi_symbol.clone(),
doc => doc,
};
let rendered = template_env::render("registration_variant.java.jinja", ctx);
out.push_str(&rendered);
out.push_str("\n\n");
}
}
for ep in &service.entrypoints {
let ep_method = &ep.method;
let ep_method_snake = ep_method.to_snake_case();
out.push_str(" /**\n");
out.push_str(&format!(" * {}.\n", ep_method));
out.push_str(&format!(
" * Invokes C FFI: {}_{}_ep_{}\n",
ffi_prefix, service_snake, ep_method_snake
));
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, api);
let param_name = param.name.to_lower_camel_case();
out.push_str(&format!("{} {}", java_type, param_name));
}
out.push_str(") {\n");
out.push_str(" try {\n");
out.push_str(&format!(
" MemorySegment epAddr = LOOKUP.find(\"{}_{}_ep_{}\")\n",
ffi_prefix, service_snake, ep_method_snake
));
out.push_str(&format!(
" .or(() -> LOOKUP.find(\"_{}_{}_ep_{}\"))\n",
ffi_prefix, service_snake, ep_method_snake
));
out.push_str(" .orElseThrow();\n");
out.push_str(" FunctionDescriptor epDesc = FunctionDescriptor.of(\n");
match ep.kind {
EntrypointKind::Run => {
out.push_str(" ValueLayout.JAVA_INT, // return int (status)\n");
}
EntrypointKind::Finalize => {
out.push_str(" ValueLayout.ADDRESS, // return *mut opaque or int status\n");
}
}
out.push_str(" ValueLayout.ADDRESS // owner: *mut opaque\n");
for param in &ep.params {
match ¶m.ty {
TypeRef::String => {
out.push_str(",\n ValueLayout.ADDRESS // ");
}
TypeRef::Primitive(p) => {
use crate::core::ir::PrimitiveType;
let layout = match p {
PrimitiveType::Bool => "ValueLayout.JAVA_INT",
PrimitiveType::U8 | PrimitiveType::I8 => "ValueLayout.JAVA_BYTE",
PrimitiveType::U16 | PrimitiveType::I16 => "ValueLayout.JAVA_SHORT",
PrimitiveType::U32 | PrimitiveType::I32 => "ValueLayout.JAVA_INT",
PrimitiveType::U64 | PrimitiveType::I64 => "ValueLayout.JAVA_LONG",
PrimitiveType::F32 => "ValueLayout.JAVA_FLOAT",
PrimitiveType::F64 => "ValueLayout.JAVA_DOUBLE",
PrimitiveType::Usize | PrimitiveType::Isize => "ValueLayout.ADDRESS",
};
out.push_str(&format!(",\n {} // ", layout));
}
_ => {
out.push_str(",\n ValueLayout.ADDRESS // ");
}
}
out.push_str(¶m.name.to_lower_camel_case());
out.push_str(" param\n");
}
out.push_str(" );\n");
out.push_str(" MethodHandle epHandle = LINKER.downcallHandle(epAddr, epDesc);\n");
match ep.kind {
EntrypointKind::Run => {
out.push_str(" epHandle.invoke(ownerHandle");
}
EntrypointKind::Finalize => {
out.push_str(" return (long) epHandle.invoke(ownerHandle");
}
}
for param in &ep.params {
let param_name = param.name.to_lower_camel_case();
if is_opaque_metadata(¶m.ty, api) {
out.push_str(&format!(", {}.handle()", param_name));
} else if matches!(param.ty, TypeRef::Primitive(crate::core::ir::PrimitiveType::Bool)) {
out.push_str(&format!(", ({} ? 1 : 0)", param_name));
} else {
out.push_str(&format!(", {}", param_name));
}
}
out.push_str(");\n");
out.push_str(" } catch (Throwable e) {\n");
out.push_str(" throw new RuntimeException(\"Failed to invoke entrypoint\", e);\n");
out.push_str(" }\n");
out.push_str(" }\n\n");
}
out.push_str(" @Override\n");
out.push_str(" public void close() {\n");
out.push_str(" try {\n");
out.push_str(&format!(
" MemorySegment freeAddr = LOOKUP.find(\"{}_{}_free\")\n",
ffi_prefix, service_snake
));
out.push_str(&format!(
" .or(() -> LOOKUP.find(\"_{}_{}_free\"))\n",
ffi_prefix, service_snake
));
out.push_str(" .orElseThrow();\n");
out.push_str(" FunctionDescriptor freeDesc = FunctionDescriptor.ofVoid(ValueLayout.ADDRESS);\n");
out.push_str(" MethodHandle freeHandle = LINKER.downcallHandle(freeAddr, freeDesc);\n");
out.push_str(" if (ownerHandle != null) {\n");
out.push_str(" freeHandle.invoke(ownerHandle);\n");
out.push_str(" ownerHandle = null;\n");
out.push_str(" }\n");
out.push_str(" arena.close();\n");
out.push_str(" } catch (Throwable e) {\n");
out.push_str(" throw new RuntimeException(\"Failed to close service\", e);\n");
out.push_str(" }\n");
out.push_str(" }\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, config);
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(),
variants: vec![
crate::core::ir::RegistrationVariant {
name: "get".to_owned(),
overrides: vec![],
wrapper_call: None,
signature_params: vec![],
doc: Some("Register a GET handler.".to_owned()),
style: Default::default(),
},
crate::core::ir::RegistrationVariant {
name: "post".to_owned(),
overrides: vec![],
wrapper_call: None,
signature_params: vec![],
doc: Some("Register a POST handler.".to_owned()),
style: Default::default(),
},
],
};
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()),
dispatch_extra_params: vec![],
wire_param_name: None,
dispatch_return_type: None,
response_adapter: None,
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()
}
}
fn make_test_config() -> ResolvedCrateConfig {
ResolvedCrateConfig {
name: "test-crate".to_owned(),
..ResolvedCrateConfig::default()
}
}
#[test]
fn java_class_uses_panama_ffm() {
let surface = make_fixture_surface();
let config = make_test_config();
let java = gen_service_class(&surface, &surface.services[0], "com.example", &config);
assert!(java.contains("import java.lang.foreign.*;"), "should import Panama FFM");
assert!(java.contains("Linker.nativeLinker()"), "should use Linker");
assert!(java.contains("downcallHandle"), "should use downcalls");
assert!(java.contains("SymbolLookup"), "should lookup C symbols");
assert!(java.contains("FunctionDescriptor"), "should build function descriptors");
assert!(java.contains("MemorySegment"), "should use MemorySegment");
assert!(java.contains("Arena"), "should use Arena for lifetime management");
}
#[test]
fn java_class_contains_service_class() {
let surface = make_fixture_surface();
let config = make_test_config();
let java = gen_service_class(&surface, &surface.services[0], "com.example", &config);
assert!(java.contains("public class TestService"));
assert!(java.contains("implements AutoCloseable"));
assert!(java.contains("private MemorySegment ownerHandle"));
}
#[test]
fn java_class_constructor_uses_downcall() {
let surface = make_fixture_surface();
let config = make_test_config();
let java = gen_service_class(&surface, &surface.services[0], "com.example", &config);
assert!(java.contains("public TestService()"));
assert!(
java.contains("test_crate_test_service_new"),
"constructor should bind to C symbol"
);
assert!(
java.contains("LINKER.downcallHandle"),
"constructor should use downcall"
);
}
#[test]
fn java_class_contains_upcall_stub_for_handler() {
let surface = make_fixture_surface();
let config = make_test_config();
let java = gen_service_class(&surface, &surface.services[0], "com.example", &config);
assert!(
java.contains("LINKER.upcallStub"),
"registration should build upcall stub for handler"
);
assert!(java.contains("MethodHandle"), "should use MethodHandle to wrap handler");
}
#[test]
fn java_class_registration_binds_to_c_symbol() {
let surface = make_fixture_surface();
let config = make_test_config();
let java = gen_service_class(&surface, &surface.services[0], "com.example", &config);
assert!(
java.contains("test_crate_test_service_register_add_handler"),
"registration should bind to exact C FFI symbol"
);
}
#[test]
fn java_class_entrypoint_uses_downcall() {
let surface = make_fixture_surface();
let config = make_test_config();
let java = gen_service_class(&surface, &surface.services[0], "com.example", &config);
assert!(java.contains("public void run(String addr)"));
assert!(
java.contains("test_crate_test_service_ep_run"),
"entrypoint should bind to C symbol"
);
assert!(java.contains("LINKER.downcallHandle"), "entrypoint should use downcall");
}
#[test]
fn java_class_close_frees_via_downcall() {
let surface = make_fixture_surface();
let config = make_test_config();
let java = gen_service_class(&surface, &surface.services[0], "com.example", &config);
assert!(java.contains("@Override"));
assert!(java.contains("public void close()"));
assert!(
java.contains("test_crate_test_service_free"),
"close should bind to C symbol"
);
assert!(java.contains("LINKER.downcallHandle"), "close should use downcall");
assert!(java.contains("arena.close()"), "arena lifetime should be managed");
}
#[test]
fn java_class_no_native_method_declarations() {
let surface = make_fixture_surface();
let config = make_test_config();
let java = gen_service_class(&surface, &surface.services[0], "com.example", &config);
assert!(
!java.contains("native "),
"should not contain JNI native method declarations"
);
assert!(
!java.contains("System.loadLibrary"),
"should not load library (Panama manages it)"
);
assert!(!java.contains("Java_"), "should not contain Java_ JNI symbols");
}
#[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 = make_test_config();
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 = make_test_config();
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 config = make_test_config();
let java = gen_service_class(&surface, &surface.services[0], "com.example", &config);
assert!(
java.contains("public int registerTestServiceAddHandler(Callable handler, String path"),
"registration method must include all metadata parameters"
);
assert!(
java.contains("ValueLayout.ADDRESS") || java.contains("ValueLayout.JAVA_INT"),
"registration should build FunctionDescriptor with metadata param layouts"
);
}
#[test]
fn java_class_emits_registration_variants() {
let surface = make_fixture_surface();
let config = make_test_config();
let java = gen_service_class(&surface, &surface.services[0], "com.example", &config);
assert!(
java.contains("public int get(String path, Callable handler)"),
"should emit get variant method"
);
assert!(
java.contains("public int post(String path, Callable handler)"),
"should emit post variant method"
);
assert!(
java.contains("test_crate_test_service_register_add_handler_get"),
"should bind get variant to correct C symbol"
);
assert!(
java.contains("test_crate_test_service_register_add_handler_post"),
"should bind post variant to correct C symbol"
);
assert!(
java.contains("LINKER.downcallHandle"),
"variant methods should use Panama downcalls"
);
assert!(
java.contains("LINKER.upcallStub"),
"variant methods should create upcall stubs"
);
assert!(
java.contains("FunctionDescriptor.of"),
"variant methods should build function descriptors"
);
}
}