use crate::type_map::{java_boxed_type, java_ffi_type, java_type};
use ahash::AHashSet;
use alef_codegen::naming::{to_class_name, to_java_name};
use alef_core::backend::{Backend, BuildConfig, Capabilities, GeneratedFile};
use alef_core::config::{AlefConfig, Language, resolve_output_dir};
use alef_core::ir::{ApiSurface, EnumDef, FunctionDef, PrimitiveType, TypeDef, TypeRef};
use heck::{ToLowerCamelCase, ToPascalCase, ToSnakeCase};
use std::fmt::Write;
use std::path::PathBuf;
const JAVA_OBJECT_METHOD_NAMES: &[&str] = &[
"wait",
"notify",
"notifyAll",
"getClass",
"hashCode",
"equals",
"toString",
"clone",
"finalize",
];
fn safe_java_field_name(name: &str) -> String {
let java_name = to_java_name(name);
if JAVA_OBJECT_METHOD_NAMES.contains(&java_name.as_str()) {
format!("{}Value", java_name)
} else {
java_name
}
}
pub struct JavaBackend;
impl JavaBackend {
fn resolve_main_class(api: &ApiSurface) -> String {
let base = to_class_name(&api.crate_name.replace('-', "_"));
if base.ends_with("Rs") {
base
} else {
format!("{}Rs", base)
}
}
}
impl Backend for JavaBackend {
fn name(&self) -> &str {
"java"
}
fn language(&self) -> Language {
Language::Java
}
fn capabilities(&self) -> Capabilities {
Capabilities {
supports_async: true,
supports_classes: true,
supports_enums: true,
supports_option: true,
supports_result: true,
..Capabilities::default()
}
}
fn generate_bindings(&self, api: &ApiSurface, config: &AlefConfig) -> anyhow::Result<Vec<GeneratedFile>> {
let package = config.java_package();
let prefix = config.ffi_prefix();
let main_class = Self::resolve_main_class(api);
let package_path = package.replace('.', "/");
let output_dir = resolve_output_dir(
config.output.java.as_ref(),
&config.crate_config.name,
"packages/java/src/main/java/",
);
let base_path = PathBuf::from(&output_dir).join(&package_path);
let mut files = Vec::new();
let description = config
.scaffold
.as_ref()
.and_then(|s| s.description.as_deref())
.unwrap_or("High-performance HTML to Markdown converter.");
files.push(GeneratedFile {
path: base_path.join("package-info.java"),
content: format!(
"/**\n * {description}\n */\npackage {package};\n",
description = description,
package = package,
),
generated_header: true,
});
files.push(GeneratedFile {
path: base_path.join("NativeLib.java"),
content: gen_native_lib(api, config, &package, &prefix),
generated_header: true,
});
files.push(GeneratedFile {
path: base_path.join(format!("{}.java", main_class)),
content: gen_main_class(api, config, &package, &main_class, &prefix),
generated_header: true,
});
files.push(GeneratedFile {
path: base_path.join(format!("{}Exception.java", main_class)),
content: gen_exception_class(&package, &main_class),
generated_header: true,
});
let complex_enums: AHashSet<String> = api
.enums
.iter()
.filter(|e| e.serde_tag.is_none() && e.variants.iter().any(|v| !v.fields.is_empty()))
.map(|e| e.name.clone())
.collect();
let lang_rename_all = config.serde_rename_all_for_language(Language::Java);
for typ in &api.types {
if !typ.is_opaque && !typ.fields.is_empty() {
files.push(GeneratedFile {
path: base_path.join(format!("{}.java", typ.name)),
content: gen_record_type(&package, typ, &complex_enums, &lang_rename_all),
generated_header: true,
});
if typ.has_default {
files.push(GeneratedFile {
path: base_path.join(format!("{}Builder.java", typ.name)),
content: gen_builder_class(&package, typ),
generated_header: true,
});
}
}
}
let builder_class_names: AHashSet<String> = api
.types
.iter()
.filter(|t| !t.is_opaque && !t.fields.is_empty() && t.has_default)
.map(|t| format!("{}Builder", t.name))
.collect();
for typ in &api.types {
if typ.is_opaque && !builder_class_names.contains(&typ.name) {
files.push(GeneratedFile {
path: base_path.join(format!("{}.java", typ.name)),
content: gen_opaque_handle_class(&package, typ, &prefix),
generated_header: true,
});
}
}
for enum_def in &api.enums {
files.push(GeneratedFile {
path: base_path.join(format!("{}.java", enum_def.name)),
content: gen_enum_class(&package, enum_def),
generated_header: true,
});
}
for error in &api.errors {
for (class_name, content) in alef_codegen::error_gen::gen_java_error_types(error, &package) {
files.push(GeneratedFile {
path: base_path.join(format!("{}.java", class_name)),
content,
generated_header: true,
});
}
}
let _adapter_bodies = alef_adapters::build_adapter_bodies(config, Language::Java)?;
Ok(files)
}
fn generate_public_api(&self, api: &ApiSurface, config: &AlefConfig) -> anyhow::Result<Vec<GeneratedFile>> {
let package = config.java_package();
let prefix = config.ffi_prefix();
let main_class = Self::resolve_main_class(api);
let package_path = package.replace('.', "/");
let output_dir = resolve_output_dir(
config.output.java.as_ref(),
&config.crate_config.name,
"packages/java/src/main/java/",
);
let base_path = PathBuf::from(&output_dir).join(&package_path);
let public_class = main_class.trim_end_matches("Rs").to_string();
let facade_content = gen_facade_class(api, &package, &public_class, &main_class, &prefix);
Ok(vec![GeneratedFile {
path: base_path.join(format!("{}.java", public_class)),
content: facade_content,
generated_header: true,
}])
}
fn build_config(&self) -> Option<BuildConfig> {
Some(BuildConfig {
tool: "mvn",
crate_suffix: "",
depends_on_ffi: true,
post_build: vec![],
})
}
}
fn gen_native_lib(api: &ApiSurface, config: &AlefConfig, package: &str, prefix: &str) -> String {
let mut body = String::with_capacity(2048);
let lib_name = config.ffi_lib_name();
writeln!(body, "final class NativeLib {{").ok();
writeln!(body, " private static final Linker LINKER = Linker.nativeLinker();").ok();
writeln!(body, " private static final SymbolLookup LIB;").ok();
writeln!(body).ok();
writeln!(body, " static {{").ok();
writeln!(body, " System.loadLibrary(\"{}\");", lib_name).ok();
writeln!(body, " LIB = SymbolLookup.loaderLookup();").ok();
writeln!(body, " }}").ok();
writeln!(body).ok();
for func in &api.functions {
let ffi_name = format!("{}_{}", prefix, func.name.to_lowercase());
let return_layout = gen_ffi_layout(&func.return_type);
let param_layouts: Vec<String> = func.params.iter().map(|p| gen_ffi_layout(&p.ty)).collect();
let layout_str = gen_function_descriptor(&return_layout, ¶m_layouts);
let handle_name = format!("{}_{}", prefix.to_uppercase(), func.name.to_uppercase());
writeln!(
body,
" static final MethodHandle {} = LINKER.downcallHandle(",
handle_name
)
.ok();
writeln!(body, " LIB.find(\"{}\").orElseThrow(),", ffi_name).ok();
writeln!(body, " {}", layout_str).ok();
writeln!(body, " );").ok();
}
{
let free_name = format!("{}_free_string", prefix);
let handle_name = format!("{}_FREE_STRING", prefix.to_uppercase());
writeln!(body).ok();
writeln!(
body,
" static final MethodHandle {} = LINKER.downcallHandle(",
handle_name
)
.ok();
writeln!(body, " LIB.find(\"{}\").orElseThrow(),", free_name).ok();
writeln!(body, " FunctionDescriptor.ofVoid(ValueLayout.ADDRESS)").ok();
writeln!(body, " );").ok();
}
{
writeln!(
body,
" static final MethodHandle {}_LAST_ERROR_CODE = LINKER.downcallHandle(",
prefix.to_uppercase()
)
.ok();
writeln!(body, " LIB.find(\"{}_last_error_code\").orElseThrow(),", prefix).ok();
writeln!(body, " FunctionDescriptor.of(ValueLayout.JAVA_INT)").ok();
writeln!(body, " );").ok();
writeln!(
body,
" static final MethodHandle {}_LAST_ERROR_CONTEXT = LINKER.downcallHandle(",
prefix.to_uppercase()
)
.ok();
writeln!(
body,
" LIB.find(\"{}_last_error_context\").orElseThrow(),",
prefix
)
.ok();
writeln!(body, " FunctionDescriptor.of(ValueLayout.ADDRESS)").ok();
writeln!(body, " );").ok();
}
let mut emitted_free_handles: AHashSet<String> = AHashSet::new();
let opaque_type_names: AHashSet<String> = api
.types
.iter()
.filter(|t| t.is_opaque)
.map(|t| t.name.clone())
.collect();
for func in &api.functions {
if let TypeRef::Named(name) = &func.return_type {
let type_snake = name.to_snake_case();
let type_upper = type_snake.to_uppercase();
let is_opaque = opaque_type_names.contains(name.as_str());
if is_opaque {
} else {
let to_json_handle = format!("{}_{}_TO_JSON", prefix.to_uppercase(), type_upper);
let to_json_ffi = format!("{}_{}_to_json", prefix, type_snake);
writeln!(body).ok();
writeln!(
body,
" static final MethodHandle {} = LINKER.downcallHandle(",
to_json_handle
)
.ok();
writeln!(body, " LIB.find(\"{}\").orElseThrow(),", to_json_ffi).ok();
writeln!(
body,
" FunctionDescriptor.of(ValueLayout.ADDRESS, ValueLayout.ADDRESS)"
)
.ok();
writeln!(body, " );").ok();
}
let free_handle = format!("{}_{}_FREE", prefix.to_uppercase(), type_upper);
let free_ffi = format!("{}_{}_free", prefix, type_snake);
if emitted_free_handles.insert(free_handle.clone()) {
writeln!(body).ok();
writeln!(
body,
" static final MethodHandle {} = LINKER.downcallHandle(",
free_handle
)
.ok();
writeln!(body, " LIB.find(\"{}\").orElseThrow(),", free_ffi).ok();
writeln!(body, " FunctionDescriptor.ofVoid(ValueLayout.ADDRESS)").ok();
writeln!(body, " );").ok();
}
}
}
let mut emitted_from_json_handles: AHashSet<String> = AHashSet::new();
for func in &api.functions {
for param in &func.params {
let inner_name = match ¶m.ty {
TypeRef::Named(n) => Some(n.clone()),
TypeRef::Optional(inner) => {
if let TypeRef::Named(n) = inner.as_ref() {
Some(n.clone())
} else {
None
}
}
_ => None,
};
if let Some(name) = inner_name {
if !opaque_type_names.contains(name.as_str()) {
let type_snake = name.to_snake_case();
let type_upper = type_snake.to_uppercase();
let from_json_handle = format!("{}_{}_FROM_JSON", prefix.to_uppercase(), type_upper);
let from_json_ffi = format!("{}_{}_from_json", prefix, type_snake);
if emitted_from_json_handles.insert(from_json_handle.clone()) {
writeln!(body).ok();
writeln!(
body,
" static final MethodHandle {} = LINKER.downcallHandle(",
from_json_handle
)
.ok();
writeln!(body, " LIB.find(\"{}\").orElseThrow(),", from_json_ffi).ok();
writeln!(
body,
" FunctionDescriptor.of(ValueLayout.ADDRESS, ValueLayout.ADDRESS)"
)
.ok();
writeln!(body, " );").ok();
}
let free_handle = format!("{}_{}_FREE", prefix.to_uppercase(), type_upper);
let free_ffi = format!("{}_{}_free", prefix, type_snake);
if emitted_free_handles.insert(free_handle.clone()) {
writeln!(body).ok();
writeln!(
body,
" static final MethodHandle {} = LINKER.downcallHandle(",
free_handle
)
.ok();
writeln!(body, " LIB.find(\"{}\").orElseThrow(),", free_ffi).ok();
writeln!(body, " FunctionDescriptor.ofVoid(ValueLayout.ADDRESS)").ok();
writeln!(body, " );").ok();
}
}
}
}
}
let builder_class_names: AHashSet<String> = api
.types
.iter()
.filter(|t| !t.is_opaque && !t.fields.is_empty() && t.has_default)
.map(|t| format!("{}Builder", t.name))
.collect();
for typ in &api.types {
if typ.is_opaque && !builder_class_names.contains(&typ.name) {
let type_snake = typ.name.to_snake_case();
let type_upper = type_snake.to_uppercase();
let free_handle = format!("{}_{}_FREE", prefix.to_uppercase(), type_upper);
let free_ffi = format!("{}_{}_free", prefix, type_snake);
if emitted_free_handles.insert(free_handle.clone()) {
writeln!(body).ok();
writeln!(
body,
" static final MethodHandle {} = LINKER.downcallHandle(",
free_handle
)
.ok();
writeln!(body, " LIB.find(\"{}\").orElseThrow(),", free_ffi).ok();
writeln!(body, " FunctionDescriptor.ofVoid(ValueLayout.ADDRESS)").ok();
writeln!(body, " );").ok();
}
}
}
writeln!(body, "}}").ok();
let mut out = String::with_capacity(body.len() + 512);
writeln!(out, "// DO NOT EDIT - auto-generated by alef").ok();
writeln!(out, "package {};", package).ok();
writeln!(out).ok();
if body.contains("Arena") {
writeln!(out, "import java.lang.foreign.Arena;").ok();
}
if body.contains("FunctionDescriptor") {
writeln!(out, "import java.lang.foreign.FunctionDescriptor;").ok();
}
if body.contains("Linker") {
writeln!(out, "import java.lang.foreign.Linker;").ok();
}
if body.contains("MemorySegment") {
writeln!(out, "import java.lang.foreign.MemorySegment;").ok();
}
if body.contains("SymbolLookup") {
writeln!(out, "import java.lang.foreign.SymbolLookup;").ok();
}
if body.contains("ValueLayout") {
writeln!(out, "import java.lang.foreign.ValueLayout;").ok();
}
if body.contains("MethodHandle") {
writeln!(out, "import java.lang.invoke.MethodHandle;").ok();
}
writeln!(out).ok();
out.push_str(&body);
out
}
fn gen_main_class(api: &ApiSurface, _config: &AlefConfig, package: &str, class_name: &str, prefix: &str) -> String {
let opaque_types: AHashSet<String> = api
.types
.iter()
.filter(|t| t.is_opaque)
.map(|t| t.name.clone())
.collect();
let mut body = String::with_capacity(4096);
writeln!(body, "public final class {} {{", class_name).ok();
writeln!(body, " private {}() {{ }}", class_name).ok();
writeln!(body).ok();
for func in &api.functions {
gen_sync_function_method(&mut body, func, prefix, class_name, &opaque_types);
writeln!(body).ok();
if func.is_async {
gen_async_wrapper_method(&mut body, func);
writeln!(body).ok();
}
}
gen_helper_methods(&mut body);
writeln!(body, "}}").ok();
let mut out = String::with_capacity(body.len() + 512);
writeln!(out, "// DO NOT EDIT - auto-generated by alef").ok();
writeln!(out, "package {};", package).ok();
writeln!(out).ok();
if body.contains("Arena") {
writeln!(out, "import java.lang.foreign.Arena;").ok();
}
if body.contains("FunctionDescriptor") {
writeln!(out, "import java.lang.foreign.FunctionDescriptor;").ok();
}
if body.contains("Linker") {
writeln!(out, "import java.lang.foreign.Linker;").ok();
}
if body.contains("MemorySegment") {
writeln!(out, "import java.lang.foreign.MemorySegment;").ok();
}
if body.contains("SymbolLookup") {
writeln!(out, "import java.lang.foreign.SymbolLookup;").ok();
}
if body.contains("ValueLayout") {
writeln!(out, "import java.lang.foreign.ValueLayout;").ok();
}
if body.contains("List<") {
writeln!(out, "import java.util.List;").ok();
}
if body.contains("Map<") {
writeln!(out, "import java.util.Map;").ok();
}
if body.contains("Optional<") {
writeln!(out, "import java.util.Optional;").ok();
}
if body.contains("HashMap<") || body.contains("new HashMap") {
writeln!(out, "import java.util.HashMap;").ok();
}
if body.contains("CompletableFuture") {
writeln!(out, "import java.util.concurrent.CompletableFuture;").ok();
}
if body.contains("CompletionException") {
writeln!(out, "import java.util.concurrent.CompletionException;").ok();
}
if body.contains(" ObjectMapper") {
writeln!(out, "import com.fasterxml.jackson.databind.ObjectMapper;").ok();
}
writeln!(out).ok();
out.push_str(&body);
out
}
fn gen_sync_function_method(
out: &mut String,
func: &FunctionDef,
prefix: &str,
class_name: &str,
opaque_types: &AHashSet<String>,
) {
let params: Vec<String> = func
.params
.iter()
.map(|p| {
let ptype = java_type(&p.ty);
format!("{} {}", ptype, to_java_name(&p.name))
})
.collect();
let return_type = java_type(&func.return_type);
writeln!(
out,
" public static {} {}({}) throws {}Exception {{",
return_type,
to_java_name(&func.name),
params.join(", "),
class_name
)
.ok();
writeln!(out, " try (var arena = Arena.ofConfined()) {{").ok();
let ffi_ptr_params: Vec<(String, String)> = func
.params
.iter()
.filter_map(|p| {
let inner_name = match &p.ty {
TypeRef::Named(n) if !opaque_types.contains(n.as_str()) => Some(n.clone()),
TypeRef::Optional(inner) => {
if let TypeRef::Named(n) = inner.as_ref() {
if !opaque_types.contains(n.as_str()) {
Some(n.clone())
} else {
None
}
} else {
None
}
}
_ => None,
};
inner_name.map(|type_name| {
let cname = "c".to_string() + &to_java_name(&p.name);
let type_snake = type_name.to_snake_case();
let free_handle = format!("NativeLib.{}_{}_FREE", prefix.to_uppercase(), type_snake.to_uppercase());
(cname, free_handle)
})
})
.collect();
for param in &func.params {
marshal_param_to_ffi(out, &to_java_name(¶m.name), ¶m.ty, opaque_types, prefix);
}
let ffi_handle = format!("NativeLib.{}_{}", prefix.to_uppercase(), func.name.to_uppercase());
let call_args: Vec<String> = func
.params
.iter()
.map(|p| ffi_param_name(&to_java_name(&p.name), &p.ty, opaque_types))
.collect();
let emit_ffi_ptr_cleanup = |out: &mut String| {
for (cname, free_handle) in &ffi_ptr_params {
writeln!(out, " if (!{}.equals(MemorySegment.NULL)) {{", cname).ok();
writeln!(out, " {}.invoke({});", free_handle, cname).ok();
writeln!(out, " }}").ok();
}
};
if matches!(func.return_type, TypeRef::Unit) {
writeln!(out, " {}.invoke({});", ffi_handle, call_args.join(", ")).ok();
emit_ffi_ptr_cleanup(out);
writeln!(out, " }} catch (Throwable e) {{").ok();
writeln!(
out,
" throw new {}Exception(\"FFI call failed\", e);",
class_name
)
.ok();
writeln!(out, " }}").ok();
} else if is_ffi_string_return(&func.return_type) {
let free_handle = format!("NativeLib.{}_FREE_STRING", prefix.to_uppercase());
writeln!(
out,
" var resultPtr = (MemorySegment) {}.invoke({});",
ffi_handle,
call_args.join(", ")
)
.ok();
emit_ffi_ptr_cleanup(out);
writeln!(out, " if (resultPtr.equals(MemorySegment.NULL)) {{").ok();
writeln!(
out,
" int errCode = (int) NativeLib.{}_LAST_ERROR_CODE.invoke();",
prefix.to_uppercase()
)
.ok();
writeln!(out, " if (errCode != 0) {{").ok();
writeln!(
out,
" var ctxPtr = (MemorySegment) NativeLib.{}_LAST_ERROR_CONTEXT.invoke();",
prefix.to_uppercase()
)
.ok();
writeln!(
out,
" String msg = ctxPtr.reinterpret(Long.MAX_VALUE).getString(0);"
)
.ok();
writeln!(
out,
" throw new {}Exception(errCode, msg);",
class_name
)
.ok();
writeln!(out, " }}").ok();
writeln!(out, " return null;").ok();
writeln!(out, " }}").ok();
writeln!(
out,
" String result = resultPtr.reinterpret(Long.MAX_VALUE).getString(0);"
)
.ok();
writeln!(out, " {}.invoke(resultPtr);", free_handle).ok();
writeln!(out, " return result;").ok();
writeln!(out, " }} catch (Throwable e) {{").ok();
writeln!(
out,
" throw new {}Exception(\"FFI call failed\", e);",
class_name
)
.ok();
writeln!(out, " }}").ok();
} else if matches!(func.return_type, TypeRef::Named(_)) {
let return_type_name = match &func.return_type {
TypeRef::Named(name) => name,
_ => unreachable!(),
};
let is_opaque = opaque_types.contains(return_type_name.as_str());
writeln!(
out,
" var resultPtr = (MemorySegment) {}.invoke({});",
ffi_handle,
call_args.join(", ")
)
.ok();
emit_ffi_ptr_cleanup(out);
writeln!(out, " if (resultPtr.equals(MemorySegment.NULL)) {{").ok();
writeln!(
out,
" int errCode = (int) NativeLib.{}_LAST_ERROR_CODE.invoke();",
prefix.to_uppercase()
)
.ok();
writeln!(out, " if (errCode != 0) {{").ok();
writeln!(
out,
" var ctxPtr = (MemorySegment) NativeLib.{}_LAST_ERROR_CONTEXT.invoke();",
prefix.to_uppercase()
)
.ok();
writeln!(
out,
" String msg = ctxPtr.reinterpret(Long.MAX_VALUE).getString(0);"
)
.ok();
writeln!(
out,
" throw new {}Exception(errCode, msg);",
class_name
)
.ok();
writeln!(out, " }}").ok();
writeln!(out, " return null;").ok();
writeln!(out, " }}").ok();
if is_opaque {
writeln!(out, " return new {}(resultPtr);", return_type_name).ok();
} else {
let type_snake = return_type_name.to_snake_case();
let free_handle = format!("NativeLib.{}_{}_FREE", prefix.to_uppercase(), type_snake.to_uppercase());
let to_json_handle = format!(
"NativeLib.{}_{}_TO_JSON",
prefix.to_uppercase(),
type_snake.to_uppercase()
);
writeln!(
out,
" var jsonPtr = (MemorySegment) {}.invoke(resultPtr);",
to_json_handle
)
.ok();
writeln!(out, " {}.invoke(resultPtr);", free_handle).ok();
writeln!(out, " if (jsonPtr.equals(MemorySegment.NULL)) {{").ok();
writeln!(
out,
" int errCode = (int) NativeLib.{}_LAST_ERROR_CODE.invoke();",
prefix.to_uppercase()
)
.ok();
writeln!(out, " if (errCode != 0) {{").ok();
writeln!(
out,
" var ctxPtr = (MemorySegment) NativeLib.{}_LAST_ERROR_CONTEXT.invoke();",
prefix.to_uppercase()
)
.ok();
writeln!(
out,
" String msg = ctxPtr.reinterpret(Long.MAX_VALUE).getString(0);"
)
.ok();
writeln!(
out,
" throw new {}Exception(errCode, msg);",
class_name
)
.ok();
writeln!(out, " }}").ok();
writeln!(out, " return null;").ok();
writeln!(out, " }}").ok();
writeln!(
out,
" String json = jsonPtr.reinterpret(Long.MAX_VALUE).getString(0);"
)
.ok();
writeln!(
out,
" NativeLib.{}_FREE_STRING.invoke(jsonPtr);",
prefix.to_uppercase()
)
.ok();
writeln!(
out,
" return createObjectMapper().readValue(json, {}.class);",
return_type_name
)
.ok();
}
writeln!(out, " }} catch (Throwable e) {{").ok();
writeln!(
out,
" throw new {}Exception(\"FFI call failed\", e);",
class_name
)
.ok();
writeln!(out, " }}").ok();
} else if matches!(func.return_type, TypeRef::Vec(_)) {
let free_handle = format!("NativeLib.{}_FREE_STRING", prefix.to_uppercase());
writeln!(
out,
" var resultPtr = (MemorySegment) {}.invoke({});",
ffi_handle,
call_args.join(", ")
)
.ok();
emit_ffi_ptr_cleanup(out);
writeln!(out, " if (resultPtr.equals(MemorySegment.NULL)) {{").ok();
writeln!(out, " return java.util.List.of();").ok();
writeln!(out, " }}").ok();
writeln!(
out,
" String json = resultPtr.reinterpret(Long.MAX_VALUE).getString(0);"
)
.ok();
writeln!(out, " {}.invoke(resultPtr);", free_handle).ok();
let element_type = match &func.return_type {
TypeRef::Vec(inner) => java_type(inner),
_ => unreachable!(),
};
writeln!(
out,
" return createObjectMapper().readValue(json, new com.fasterxml.jackson.core.type.TypeReference<java.util.List<{}>>() {{ }});",
element_type
)
.ok();
writeln!(out, " }} catch (Throwable e) {{").ok();
writeln!(
out,
" throw new {}Exception(\"FFI call failed\", e);",
class_name
)
.ok();
writeln!(out, " }}").ok();
} else {
writeln!(
out,
" var primitiveResult = ({}) {}.invoke({});",
java_ffi_return_cast(&func.return_type),
ffi_handle,
call_args.join(", ")
)
.ok();
emit_ffi_ptr_cleanup(out);
writeln!(out, " return primitiveResult;").ok();
writeln!(out, " }} catch (Throwable e) {{").ok();
writeln!(
out,
" throw new {}Exception(\"FFI call failed\", e);",
class_name
)
.ok();
writeln!(out, " }}").ok();
}
writeln!(out, " }}").ok();
}
fn gen_async_wrapper_method(out: &mut String, func: &FunctionDef) {
let params: Vec<String> = func
.params
.iter()
.map(|p| {
let ptype = java_type(&p.ty);
format!("{} {}", ptype, to_java_name(&p.name))
})
.collect();
let return_type = match &func.return_type {
TypeRef::Unit => "Void".to_string(),
other => java_boxed_type(other).to_string(),
};
let sync_method_name = to_java_name(&func.name);
let async_method_name = format!("{}Async", sync_method_name);
let param_names: Vec<String> = func.params.iter().map(|p| to_java_name(&p.name)).collect();
writeln!(
out,
" public static CompletableFuture<{}> {}({}) {{",
return_type,
async_method_name,
params.join(", ")
)
.ok();
writeln!(out, " return CompletableFuture.supplyAsync(() -> {{").ok();
writeln!(out, " try {{").ok();
writeln!(
out,
" return {}({});",
sync_method_name,
param_names.join(", ")
)
.ok();
writeln!(out, " }} catch (Throwable e) {{").ok();
writeln!(out, " throw new CompletionException(e);").ok();
writeln!(out, " }}").ok();
writeln!(out, " }});").ok();
writeln!(out, " }}").ok();
}
fn gen_exception_class(package: &str, class_name: &str) -> String {
let mut out = String::with_capacity(512);
writeln!(out, "// DO NOT EDIT - auto-generated by alef").ok();
writeln!(out, "package {};", package).ok();
writeln!(out).ok();
writeln!(out, "public class {}Exception extends Exception {{", class_name).ok();
writeln!(out, " private final int code;").ok();
writeln!(out).ok();
writeln!(out, " public {}Exception(int code, String message) {{", class_name).ok();
writeln!(out, " super(message);").ok();
writeln!(out, " this.code = code;").ok();
writeln!(out, " }}").ok();
writeln!(out).ok();
writeln!(
out,
" public {}Exception(String message, Throwable cause) {{",
class_name
)
.ok();
writeln!(out, " super(message, cause);").ok();
writeln!(out, " this.code = -1;").ok();
writeln!(out, " }}").ok();
writeln!(out).ok();
writeln!(out, " public int getCode() {{").ok();
writeln!(out, " return code;").ok();
writeln!(out, " }}").ok();
writeln!(out, "}}").ok();
out
}
fn gen_facade_class(api: &ApiSurface, package: &str, public_class: &str, raw_class: &str, _prefix: &str) -> String {
let mut body = String::with_capacity(4096);
writeln!(body, "public final class {} {{", public_class).ok();
writeln!(body, " private {}() {{ }}", public_class).ok();
writeln!(body).ok();
for func in &api.functions {
let params: Vec<String> = func
.params
.iter()
.map(|p| {
let ptype = java_type(&p.ty);
format!("{} {}", ptype, to_java_name(&p.name))
})
.collect();
let return_type = java_type(&func.return_type);
if !func.doc.is_empty() {
writeln!(body, " /**").ok();
for line in func.doc.lines() {
writeln!(body, " * {}", line).ok();
}
writeln!(body, " */").ok();
}
writeln!(
body,
" public static {} {}({}) throws {}Exception {{",
return_type,
to_java_name(&func.name),
params.join(", "),
raw_class
)
.ok();
for param in &func.params {
if !param.optional {
let pname = to_java_name(¶m.name);
writeln!(
body,
" java.util.Objects.requireNonNull({}, \"{} must not be null\");",
pname, pname
)
.ok();
}
}
let call_args: Vec<String> = func.params.iter().map(|p| to_java_name(&p.name)).collect();
if matches!(func.return_type, TypeRef::Unit) {
writeln!(
body,
" {}.{}({});",
raw_class,
to_java_name(&func.name),
call_args.join(", ")
)
.ok();
} else {
writeln!(
body,
" return {}.{}({});",
raw_class,
to_java_name(&func.name),
call_args.join(", ")
)
.ok();
}
writeln!(body, " }}").ok();
writeln!(body).ok();
let has_optional = func.params.iter().any(|p| p.optional);
if has_optional {
let required_params: Vec<String> = func
.params
.iter()
.filter(|p| !p.optional)
.map(|p| {
let ptype = java_type(&p.ty);
format!("{} {}", ptype, to_java_name(&p.name))
})
.collect();
writeln!(
body,
" public static {} {}({}) throws {}Exception {{",
return_type,
to_java_name(&func.name),
required_params.join(", "),
raw_class
)
.ok();
let full_args: Vec<String> = func
.params
.iter()
.map(|p| {
if p.optional {
"null".to_string()
} else {
to_java_name(&p.name)
}
})
.collect();
if matches!(func.return_type, TypeRef::Unit) {
writeln!(body, " {}({});", to_java_name(&func.name), full_args.join(", ")).ok();
} else {
writeln!(
body,
" return {}({});",
to_java_name(&func.name),
full_args.join(", ")
)
.ok();
}
writeln!(body, " }}").ok();
writeln!(body).ok();
}
}
writeln!(body, "}}").ok();
let mut out = String::with_capacity(body.len() + 512);
writeln!(out, "// DO NOT EDIT - auto-generated by alef").ok();
writeln!(out, "package {};", package).ok();
let has_list = body.contains("List<");
let has_map = body.contains("Map<");
let has_optional = body.contains("Optional<");
let has_imports = has_list || has_map || has_optional;
if has_imports {
writeln!(out).ok();
if has_list {
writeln!(out, "import java.util.List;").ok();
}
if has_map {
writeln!(out, "import java.util.Map;").ok();
}
if has_optional {
writeln!(out, "import java.util.Optional;").ok();
}
}
writeln!(out).ok();
out.push_str(&body);
out
}
fn gen_opaque_handle_class(package: &str, typ: &TypeDef, prefix: &str) -> String {
let mut out = String::with_capacity(1024);
let class_name = &typ.name;
let type_snake = class_name.to_snake_case();
writeln!(out, "// DO NOT EDIT - auto-generated by alef").ok();
writeln!(out, "package {};", package).ok();
writeln!(out).ok();
writeln!(out, "import java.lang.foreign.MemorySegment;").ok();
writeln!(out).ok();
if !typ.doc.is_empty() {
writeln!(out, "/**").ok();
for line in typ.doc.lines() {
writeln!(out, " * {}", line).ok();
}
writeln!(out, " */").ok();
}
writeln!(out, "public class {} implements AutoCloseable {{", class_name).ok();
writeln!(out, " private final MemorySegment handle;").ok();
writeln!(out).ok();
writeln!(out, " {}(MemorySegment handle) {{", class_name).ok();
writeln!(out, " this.handle = handle;").ok();
writeln!(out, " }}").ok();
writeln!(out).ok();
writeln!(out, " MemorySegment handle() {{").ok();
writeln!(out, " return this.handle;").ok();
writeln!(out, " }}").ok();
writeln!(out).ok();
writeln!(out, " @Override").ok();
writeln!(out, " public void close() {{").ok();
writeln!(
out,
" if (handle != null && !handle.equals(MemorySegment.NULL)) {{"
)
.ok();
writeln!(out, " try {{").ok();
writeln!(
out,
" NativeLib.{}_{}_FREE.invoke(handle);",
prefix.to_uppercase(),
type_snake.to_uppercase()
)
.ok();
writeln!(out, " }} catch (Throwable e) {{").ok();
writeln!(
out,
" throw new RuntimeException(\"Failed to free {}: \" + e.getMessage(), e);",
class_name
)
.ok();
writeln!(out, " }}").ok();
writeln!(out, " }}").ok();
writeln!(out, " }}").ok();
writeln!(out, "}}").ok();
out
}
const RECORD_LINE_WRAP_THRESHOLD: usize = 100;
fn gen_record_type(package: &str, typ: &TypeDef, complex_enums: &AHashSet<String>, lang_rename_all: &str) -> String {
let field_list: Vec<String> = typ
.fields
.iter()
.map(|f| {
let is_complex = matches!(&f.ty, TypeRef::Named(n) if complex_enums.contains(n.as_str()));
let ftype = if is_complex {
"Object".to_string()
} else if f.optional {
format!("Optional<{}>", java_boxed_type(&f.ty))
} else {
java_type(&f.ty).to_string()
};
let jname = safe_java_field_name(&f.name);
if lang_rename_all == "camelCase" && f.name.contains('_') {
format!("@JsonProperty(\"{}\") {} {}", f.name, ftype, jname)
} else {
format!("{} {}", ftype, jname)
}
})
.collect();
let single_line = format!("public record {}({}) {{ }}", typ.name, field_list.join(", "));
let mut record_block = String::new();
if single_line.len() > RECORD_LINE_WRAP_THRESHOLD && field_list.len() > 1 {
writeln!(record_block, "public record {}(", typ.name).ok();
for (i, field) in field_list.iter().enumerate() {
let comma = if i < field_list.len() - 1 { "," } else { "" };
writeln!(record_block, " {}{}", field, comma).ok();
}
writeln!(record_block, ") {{").ok();
} else {
writeln!(record_block, "public record {}({}) {{", typ.name, field_list.join(", ")).ok();
}
if typ.has_default {
writeln!(record_block, " public static {}Builder builder() {{", typ.name).ok();
writeln!(record_block, " return new {}Builder();", typ.name).ok();
writeln!(record_block, " }}").ok();
}
writeln!(record_block, "}}").ok();
let needs_json_property = field_list.iter().any(|f| f.contains("@JsonProperty("));
let mut out = String::with_capacity(record_block.len() + 512);
writeln!(out, "// DO NOT EDIT - auto-generated by alef").ok();
writeln!(out, "package {};", package).ok();
writeln!(out).ok();
if single_line.contains("List<") {
writeln!(out, "import java.util.List;").ok();
}
if single_line.contains("Map<") {
writeln!(out, "import java.util.Map;").ok();
}
if single_line.contains("Optional<") {
writeln!(out, "import java.util.Optional;").ok();
}
if needs_json_property {
writeln!(out, "import com.fasterxml.jackson.annotation.JsonProperty;").ok();
}
writeln!(out).ok();
write!(out, "{}", record_block).ok();
out
}
fn java_apply_rename_all(name: &str, rename_all: Option<&str>) -> String {
match rename_all {
Some("snake_case") => name.to_snake_case(),
Some("camelCase") => name.to_lower_camel_case(),
Some("PascalCase") => name.to_pascal_case(),
Some("SCREAMING_SNAKE_CASE") => name.to_snake_case().to_uppercase(),
Some("lowercase") => name.to_lowercase(),
Some("UPPERCASE") => name.to_uppercase(),
_ => name.to_lowercase(),
}
}
fn gen_enum_class(package: &str, enum_def: &EnumDef) -> String {
let has_data_variants = enum_def.variants.iter().any(|v| !v.fields.is_empty());
if enum_def.serde_tag.is_some() && has_data_variants {
return gen_java_tagged_union(package, enum_def);
}
let mut out = String::with_capacity(1024);
writeln!(out, "// DO NOT EDIT - auto-generated by alef").ok();
writeln!(out, "package {};", package).ok();
writeln!(out).ok();
writeln!(out, "import com.fasterxml.jackson.annotation.JsonCreator;").ok();
writeln!(out, "import com.fasterxml.jackson.annotation.JsonValue;").ok();
writeln!(out).ok();
writeln!(out, "public enum {} {{", enum_def.name).ok();
for (i, variant) in enum_def.variants.iter().enumerate() {
let comma = if i < enum_def.variants.len() - 1 { "," } else { ";" };
let json_name = variant
.serde_rename
.clone()
.unwrap_or_else(|| java_apply_rename_all(&variant.name, enum_def.serde_rename_all.as_deref()));
writeln!(out, " {}(\"{}\"){}", variant.name, json_name, comma).ok();
}
writeln!(out).ok();
writeln!(out, " private final String value;").ok();
writeln!(out).ok();
writeln!(out, " {}(String value) {{", enum_def.name).ok();
writeln!(out, " this.value = value;").ok();
writeln!(out, " }}").ok();
writeln!(out).ok();
writeln!(out, " @JsonValue").ok();
writeln!(out, " public String getValue() {{").ok();
writeln!(out, " return value;").ok();
writeln!(out, " }}").ok();
writeln!(out).ok();
writeln!(out, " @JsonCreator").ok();
writeln!(out, " public static {} fromValue(String value) {{", enum_def.name).ok();
writeln!(out, " for ({} e : values()) {{", enum_def.name).ok();
writeln!(out, " if (e.value.equalsIgnoreCase(value)) {{").ok();
writeln!(out, " return e;").ok();
writeln!(out, " }}").ok();
writeln!(out, " }}").ok();
writeln!(
out,
" throw new IllegalArgumentException(\"Unknown value: \" + value);"
)
.ok();
writeln!(out, " }}").ok();
writeln!(out, "}}").ok();
out
}
fn gen_java_tagged_union(package: &str, enum_def: &EnumDef) -> String {
let tag_field = enum_def.serde_tag.as_deref().unwrap_or("type");
let variant_names: std::collections::HashSet<&str> = enum_def.variants.iter().map(|v| v.name.as_str()).collect();
let optional_type = if variant_names.contains("Optional") {
"java.util.Optional"
} else {
"Optional"
};
let mut out = String::with_capacity(2048);
writeln!(out, "// DO NOT EDIT - auto-generated by alef").ok();
writeln!(out, "package {};", package).ok();
writeln!(out).ok();
writeln!(out, "import com.fasterxml.jackson.annotation.JsonProperty;").ok();
writeln!(out, "import com.fasterxml.jackson.annotation.JsonSubTypes;").ok();
writeln!(out, "import com.fasterxml.jackson.annotation.JsonTypeInfo;").ok();
let needs_list = !variant_names.contains("List")
&& enum_def
.variants
.iter()
.any(|v| v.fields.iter().any(|f| matches!(&f.ty, TypeRef::Vec(_))));
let needs_map = !variant_names.contains("Map")
&& enum_def
.variants
.iter()
.any(|v| v.fields.iter().any(|f| matches!(&f.ty, TypeRef::Map(_, _))));
let needs_optional =
!variant_names.contains("Optional") && enum_def.variants.iter().any(|v| v.fields.iter().any(|f| f.optional));
if needs_list {
writeln!(out, "import java.util.List;").ok();
}
if needs_map {
writeln!(out, "import java.util.Map;").ok();
}
if needs_optional {
writeln!(out, "import java.util.Optional;").ok();
}
writeln!(out).ok();
writeln!(
out,
"@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = \"{tag_field}\", visible = false)"
)
.ok();
writeln!(out, "@JsonSubTypes({{").ok();
for (i, variant) in enum_def.variants.iter().enumerate() {
let discriminator = variant
.serde_rename
.clone()
.unwrap_or_else(|| java_apply_rename_all(&variant.name, enum_def.serde_rename_all.as_deref()));
let comma = if i < enum_def.variants.len() - 1 { "," } else { "" };
writeln!(
out,
" @JsonSubTypes.Type(value = {}.{}.class, name = \"{}\"){}",
enum_def.name, variant.name, discriminator, comma
)
.ok();
}
writeln!(out, "}})").ok();
writeln!(out, "public sealed interface {} {{", enum_def.name).ok();
for variant in &enum_def.variants {
writeln!(out).ok();
if variant.fields.is_empty() {
writeln!(out, " record {}() implements {} {{", variant.name, enum_def.name).ok();
writeln!(out, " }}").ok();
} else {
let field_parts: Vec<String> = variant
.fields
.iter()
.map(|f| {
let json_name = f.name.trim_start_matches('_');
let ftype = if f.optional {
let inner = java_boxed_type(&f.ty);
let inner_str = inner.as_ref();
let inner_qualified = if inner_str.starts_with("List<") && variant_names.contains("List") {
inner_str.replacen("List<", "java.util.List<", 1)
} else if inner_str.starts_with("Map<") && variant_names.contains("Map") {
inner_str.replacen("Map<", "java.util.Map<", 1)
} else {
inner_str.to_string()
};
format!("{optional_type}<{inner_qualified}>")
} else {
let t = java_type(&f.ty);
let t_str = t.as_ref();
if t_str.starts_with("List<") && variant_names.contains("List") {
t_str.replacen("List<", "java.util.List<", 1)
} else if t_str.starts_with("Map<") && variant_names.contains("Map") {
t_str.replacen("Map<", "java.util.Map<", 1)
} else {
t_str.to_string()
}
};
let jname = safe_java_field_name(json_name);
format!("@JsonProperty(\"{json_name}\") {ftype} {jname}")
})
.collect();
let single = format!(
" record {}({}) implements {} {{ }}",
variant.name,
field_parts.join(", "),
enum_def.name
);
if single.len() > RECORD_LINE_WRAP_THRESHOLD && field_parts.len() > 1 {
writeln!(out, " record {}(", variant.name).ok();
for (i, fp) in field_parts.iter().enumerate() {
let comma = if i < field_parts.len() - 1 { "," } else { "" };
writeln!(out, " {}{}", fp, comma).ok();
}
writeln!(out, " ) implements {} {{", enum_def.name).ok();
writeln!(out, " }}").ok();
} else {
writeln!(
out,
" record {}({}) implements {} {{ }}",
variant.name,
field_parts.join(", "),
enum_def.name
)
.ok();
}
}
}
writeln!(out).ok();
writeln!(out, "}}").ok();
out
}
fn gen_ffi_layout(ty: &TypeRef) -> String {
match ty {
TypeRef::Primitive(prim) => java_ffi_type(prim).to_string(),
TypeRef::String | TypeRef::Char | TypeRef::Path | TypeRef::Json => "ValueLayout.ADDRESS".to_string(),
TypeRef::Bytes => "ValueLayout.ADDRESS".to_string(),
TypeRef::Optional(inner) => gen_ffi_layout(inner),
TypeRef::Vec(_) => "ValueLayout.ADDRESS".to_string(),
TypeRef::Map(_, _) => "ValueLayout.ADDRESS".to_string(),
TypeRef::Named(_) => "ValueLayout.ADDRESS".to_string(),
TypeRef::Unit => "".to_string(),
TypeRef::Duration => "ValueLayout.JAVA_LONG".to_string(),
}
}
fn marshal_param_to_ffi(out: &mut String, name: &str, ty: &TypeRef, opaque_types: &AHashSet<String>, prefix: &str) {
match ty {
TypeRef::String | TypeRef::Char | TypeRef::Path | TypeRef::Json => {
let cname = "c".to_string() + name;
writeln!(out, " var {} = arena.allocateFrom({});", cname, name).ok();
}
TypeRef::Named(type_name) => {
let cname = "c".to_string() + name;
if opaque_types.contains(type_name.as_str()) {
writeln!(out, " var {} = {}.handle();", cname, name).ok();
} else {
let type_snake = type_name.to_snake_case();
let from_json_handle = format!(
"NativeLib.{}_{}_FROM_JSON",
prefix.to_uppercase(),
type_snake.to_uppercase()
);
let _free_handle = format!("NativeLib.{}_{}_FREE", prefix.to_uppercase(), type_snake.to_uppercase());
writeln!(
out,
" var {}Json = {} != null ? createObjectMapper().writeValueAsString({}) : null;",
cname, name, name
)
.ok();
writeln!(
out,
" var {}JsonSeg = {}Json != null ? arena.allocateFrom({}Json) : MemorySegment.NULL;",
cname, cname, cname
)
.ok();
writeln!(out, " var {} = {}Json != null", cname, cname).ok();
writeln!(
out,
" ? (MemorySegment) {}.invoke({}JsonSeg)",
from_json_handle, cname
)
.ok();
writeln!(out, " : MemorySegment.NULL;").ok();
}
}
TypeRef::Optional(inner) => {
match inner.as_ref() {
TypeRef::String | TypeRef::Char | TypeRef::Path | TypeRef::Json => {
let cname = "c".to_string() + name;
writeln!(
out,
" var {} = {} != null ? arena.allocateFrom({}) : MemorySegment.NULL;",
cname, name, name
)
.ok();
}
TypeRef::Named(type_name) => {
let cname = "c".to_string() + name;
if opaque_types.contains(type_name.as_str()) {
writeln!(
out,
" var {} = {} != null ? {}.handle() : MemorySegment.NULL;",
cname, name, name
)
.ok();
} else {
let type_snake = type_name.to_snake_case();
let from_json_handle = format!(
"NativeLib.{}_{}_FROM_JSON",
prefix.to_uppercase(),
type_snake.to_uppercase()
);
writeln!(
out,
" var {}Json = {} != null ? createObjectMapper().writeValueAsString({}) : null;",
cname, name, name
)
.ok();
writeln!(out, " var {}JsonSeg = {}Json != null ? arena.allocateFrom({}Json) : MemorySegment.NULL;", cname, cname, cname).ok();
writeln!(out, " var {} = {}Json != null", cname, cname).ok();
writeln!(
out,
" ? (MemorySegment) {}.invoke({}JsonSeg)",
from_json_handle, cname
)
.ok();
writeln!(out, " : MemorySegment.NULL;").ok();
}
}
_ => {
}
}
}
_ => {
}
}
}
fn ffi_param_name(name: &str, ty: &TypeRef, _opaque_types: &AHashSet<String>) -> String {
match ty {
TypeRef::String | TypeRef::Char | TypeRef::Path | TypeRef::Json => "c".to_string() + name,
TypeRef::Named(_) => "c".to_string() + name,
TypeRef::Optional(inner) => match inner.as_ref() {
TypeRef::String | TypeRef::Char | TypeRef::Path | TypeRef::Json | TypeRef::Named(_) => {
"c".to_string() + name
}
_ => name.to_string(),
},
_ => name.to_string(),
}
}
fn gen_function_descriptor(return_layout: &str, param_layouts: &[String]) -> String {
if return_layout.is_empty() {
if param_layouts.is_empty() {
"FunctionDescriptor.ofVoid()".to_string()
} else {
format!("FunctionDescriptor.ofVoid({})", param_layouts.join(", "))
}
} else {
if param_layouts.is_empty() {
format!("FunctionDescriptor.of({})", return_layout)
} else {
format!("FunctionDescriptor.of({}, {})", return_layout, param_layouts.join(", "))
}
}
}
fn is_ffi_string_return(ty: &TypeRef) -> bool {
match ty {
TypeRef::String | TypeRef::Char | TypeRef::Path | TypeRef::Json => true,
TypeRef::Optional(inner) => is_ffi_string_return(inner),
_ => false,
}
}
fn java_ffi_return_cast(ty: &TypeRef) -> &'static str {
match ty {
TypeRef::Primitive(prim) => match prim {
PrimitiveType::Bool => "boolean",
PrimitiveType::U8 | PrimitiveType::I8 => "byte",
PrimitiveType::U16 | PrimitiveType::I16 => "short",
PrimitiveType::U32 | PrimitiveType::I32 => "int",
PrimitiveType::U64 | PrimitiveType::I64 | PrimitiveType::Usize | PrimitiveType::Isize => "long",
PrimitiveType::F32 => "float",
PrimitiveType::F64 => "double",
},
TypeRef::Bytes | TypeRef::Vec(_) | TypeRef::Map(_, _) | TypeRef::Named(_) => "MemorySegment",
_ => "MemorySegment",
}
}
fn gen_helper_methods(out: &mut String) {
let needs_read_cstring = out.contains("readCString(");
let needs_read_bytes = out.contains("readBytes(");
let needs_create_object_mapper = out.contains("createObjectMapper()");
if !needs_read_cstring && !needs_read_bytes && !needs_create_object_mapper {
return;
}
writeln!(out, " // Helper methods for FFI marshalling").ok();
writeln!(out).ok();
if needs_create_object_mapper {
writeln!(
out,
" private static com.fasterxml.jackson.databind.ObjectMapper createObjectMapper() {{"
)
.ok();
writeln!(out, " return new com.fasterxml.jackson.databind.ObjectMapper()").ok();
writeln!(
out,
" .registerModule(new com.fasterxml.jackson.datatype.jdk8.Jdk8Module())"
)
.ok();
writeln!(out, " .findAndRegisterModules()").ok();
writeln!(
out,
" .setSerializationInclusion(com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL)"
)
.ok();
writeln!(
out,
" .setSerializationInclusion(com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL)"
)
.ok();
writeln!(
out,
" .configure(com.fasterxml.jackson.databind.MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS, true);"
)
.ok();
writeln!(out, " }}").ok();
writeln!(out).ok();
}
if needs_read_cstring {
writeln!(out, " private static String readCString(MemorySegment ptr) {{").ok();
writeln!(out, " if (ptr == null || ptr.address() == 0) {{").ok();
writeln!(out, " return null;").ok();
writeln!(out, " }}").ok();
writeln!(out, " return ptr.getUtf8String(0);").ok();
writeln!(out, " }}").ok();
writeln!(out).ok();
}
if needs_read_bytes {
writeln!(
out,
" private static byte[] readBytes(MemorySegment ptr, long len) {{"
)
.ok();
writeln!(out, " if (ptr == null || ptr.address() == 0) {{").ok();
writeln!(out, " return new byte[0];").ok();
writeln!(out, " }}").ok();
writeln!(out, " byte[] bytes = new byte[(int) len];").ok();
writeln!(
out,
" MemorySegment.copy(ptr, ValueLayout.JAVA_BYTE.byteSize() * 0, bytes, 0, (int) len);"
)
.ok();
writeln!(out, " return bytes;").ok();
writeln!(out, " }}").ok();
}
}
fn format_optional_value(ty: &TypeRef, default: &str) -> String {
if default.contains("Optional.") {
return default.to_string();
}
let inner_ty = match ty {
TypeRef::Optional(inner) => inner.as_ref(),
other => other,
};
let formatted_value = match inner_ty {
TypeRef::Primitive(p) => match p {
PrimitiveType::I64 | PrimitiveType::U64 | PrimitiveType::Isize | PrimitiveType::Usize => {
if default.ends_with('L') || default.ends_with('l') {
default.to_string()
} else if default.parse::<i64>().is_ok() {
format!("{}L", default)
} else {
default.to_string()
}
}
PrimitiveType::F32 => {
if default.ends_with('f') || default.ends_with('F') {
default.to_string()
} else if default.parse::<f32>().is_ok() {
format!("{}f", default)
} else {
default.to_string()
}
}
PrimitiveType::F64 => {
default.to_string()
}
_ => default.to_string(),
},
_ => default.to_string(),
};
format!("Optional.of({})", formatted_value)
}
fn gen_builder_class(package: &str, typ: &TypeDef) -> String {
let mut body = String::with_capacity(2048);
writeln!(body, "public class {}Builder {{", typ.name).ok();
writeln!(body).ok();
for field in &typ.fields {
let field_name = safe_java_field_name(&field.name);
if field.name.starts_with('_') && field.name[1..].chars().all(|c| c.is_ascii_digit())
|| field.name.chars().next().is_none_or(|c| c.is_ascii_digit())
{
continue;
}
let field_type = if field.optional {
format!("Optional<{}>", java_boxed_type(&field.ty))
} else if matches!(field.ty, TypeRef::Duration) {
java_boxed_type(&field.ty).to_string()
} else {
java_type(&field.ty).to_string()
};
let default_value = if field.optional {
if let Some(default) = &field.default {
format_optional_value(&field.ty, default)
} else {
"Optional.empty()".to_string()
}
} else {
if let Some(default) = &field.default {
default.clone()
} else {
match &field.ty {
TypeRef::String | TypeRef::Char | TypeRef::Path => "\"\"".to_string(),
TypeRef::Json => "null".to_string(),
TypeRef::Bytes => "new byte[0]".to_string(),
TypeRef::Primitive(p) => match p {
PrimitiveType::Bool => "false".to_string(),
PrimitiveType::F32 | PrimitiveType::F64 => "0.0".to_string(),
_ => "0".to_string(),
},
TypeRef::Vec(_) => "List.of()".to_string(),
TypeRef::Map(_, _) => "Map.of()".to_string(),
TypeRef::Optional(_) => "Optional.empty()".to_string(),
TypeRef::Duration => "null".to_string(),
_ => "null".to_string(),
}
}
};
writeln!(body, " private {} {} = {};", field_type, field_name, default_value).ok();
}
writeln!(body).ok();
for field in &typ.fields {
if field.name.starts_with('_') && field.name[1..].chars().all(|c| c.is_ascii_digit())
|| field.name.chars().next().is_none_or(|c| c.is_ascii_digit())
{
continue;
}
let field_name = safe_java_field_name(&field.name);
let field_name_pascal = to_class_name(&field.name);
let field_type = if field.optional {
format!("Optional<{}>", java_boxed_type(&field.ty))
} else if matches!(field.ty, TypeRef::Duration) {
java_boxed_type(&field.ty).to_string()
} else {
java_type(&field.ty).to_string()
};
writeln!(
body,
" public {}Builder with{}({} value) {{",
typ.name, field_name_pascal, field_type
)
.ok();
writeln!(body, " this.{} = value;", field_name).ok();
writeln!(body, " return this;").ok();
writeln!(body, " }}").ok();
writeln!(body).ok();
}
writeln!(body, " public {} build() {{", typ.name).ok();
writeln!(body, " return new {}(", typ.name).ok();
let non_tuple_fields: Vec<_> = typ
.fields
.iter()
.filter(|f| {
!(f.name.starts_with('_') && f.name[1..].chars().all(|c| c.is_ascii_digit())
|| f.name.chars().next().is_none_or(|c| c.is_ascii_digit()))
})
.collect();
for (i, field) in non_tuple_fields.iter().enumerate() {
let field_name = safe_java_field_name(&field.name);
let comma = if i < non_tuple_fields.len() - 1 { "," } else { "" };
writeln!(body, " {}{}", field_name, comma).ok();
}
writeln!(body, " );").ok();
writeln!(body, " }}").ok();
writeln!(body, "}}").ok();
let mut out = String::with_capacity(body.len() + 512);
writeln!(out, "// DO NOT EDIT - auto-generated by alef").ok();
writeln!(out, "package {};", package).ok();
writeln!(out).ok();
if body.contains("List<") {
writeln!(out, "import java.util.List;").ok();
}
if body.contains("Map<") {
writeln!(out, "import java.util.Map;").ok();
}
if body.contains("Optional<") {
writeln!(out, "import java.util.Optional;").ok();
}
writeln!(out).ok();
out.push_str(&body);
out
}