Skip to main content

alef_backend_java/
gen_bindings.rs

1use crate::type_map::{java_boxed_type, java_ffi_type, java_type};
2use ahash::AHashSet;
3use alef_codegen::naming::{to_class_name, to_java_name};
4use alef_core::backend::{Backend, BuildConfig, Capabilities, GeneratedFile};
5use alef_core::config::{AlefConfig, Language, resolve_output_dir};
6use alef_core::ir::{ApiSurface, EnumDef, FunctionDef, PrimitiveType, TypeDef, TypeRef};
7use heck::{ToLowerCamelCase, ToPascalCase, ToSnakeCase};
8use std::fmt::Write;
9use std::path::PathBuf;
10
11/// Names that conflict with methods on `java.lang.Object` and are therefore
12/// illegal as record component names or method names in generated Java code.
13const JAVA_OBJECT_METHOD_NAMES: &[&str] = &[
14    "wait",
15    "notify",
16    "notifyAll",
17    "getClass",
18    "hashCode",
19    "equals",
20    "toString",
21    "clone",
22    "finalize",
23];
24
25/// Sanitise a field/parameter name that would conflict with `java.lang.Object`
26/// methods.  Conflicting names get a `_` suffix (e.g. `wait` -> `wait_`), which
27/// is then converted to camelCase by `to_java_name`.
28fn safe_java_field_name(name: &str) -> String {
29    let java_name = to_java_name(name);
30    if JAVA_OBJECT_METHOD_NAMES.contains(&java_name.as_str()) {
31        format!("{}Value", java_name)
32    } else {
33        java_name
34    }
35}
36
37pub struct JavaBackend;
38
39impl JavaBackend {
40    /// Convert crate name to main class name (PascalCase + "Rs" suffix).
41    ///
42    /// The "Rs" suffix ensures the raw FFI wrapper class has a distinct name from
43    /// the public facade class (which strips the "Rs" suffix). Without this, the
44    /// facade would delegate to itself, causing infinite recursion.
45    fn resolve_main_class(api: &ApiSurface) -> String {
46        let base = to_class_name(&api.crate_name.replace('-', "_"));
47        if base.ends_with("Rs") {
48            base
49        } else {
50            format!("{}Rs", base)
51        }
52    }
53}
54
55impl Backend for JavaBackend {
56    fn name(&self) -> &str {
57        "java"
58    }
59
60    fn language(&self) -> Language {
61        Language::Java
62    }
63
64    fn capabilities(&self) -> Capabilities {
65        Capabilities {
66            supports_async: true,
67            supports_classes: true,
68            supports_enums: true,
69            supports_option: true,
70            supports_result: true,
71            ..Capabilities::default()
72        }
73    }
74
75    fn generate_bindings(&self, api: &ApiSurface, config: &AlefConfig) -> anyhow::Result<Vec<GeneratedFile>> {
76        let package = config.java_package();
77        let prefix = config.ffi_prefix();
78        let main_class = Self::resolve_main_class(api);
79        let package_path = package.replace('.', "/");
80
81        let output_dir = resolve_output_dir(
82            config.output.java.as_ref(),
83            &config.crate_config.name,
84            "packages/java/src/main/java/",
85        );
86
87        let base_path = PathBuf::from(&output_dir).join(&package_path);
88
89        let mut files = Vec::new();
90
91        // 0. package-info.java - required by Checkstyle
92        let description = config
93            .scaffold
94            .as_ref()
95            .and_then(|s| s.description.as_deref())
96            .unwrap_or("High-performance HTML to Markdown converter.");
97        files.push(GeneratedFile {
98            path: base_path.join("package-info.java"),
99            content: format!(
100                "/**\n * {description}\n */\npackage {package};\n",
101                description = description,
102                package = package,
103            ),
104            generated_header: true,
105        });
106
107        // 1. NativeLib.java - FFI method handles
108        files.push(GeneratedFile {
109            path: base_path.join("NativeLib.java"),
110            content: gen_native_lib(api, config, &package, &prefix),
111            generated_header: true,
112        });
113
114        // 2. Main wrapper class
115        files.push(GeneratedFile {
116            path: base_path.join(format!("{}.java", main_class)),
117            content: gen_main_class(api, config, &package, &main_class, &prefix),
118            generated_header: true,
119        });
120
121        // 3. Exception class
122        files.push(GeneratedFile {
123            path: base_path.join(format!("{}Exception.java", main_class)),
124            content: gen_exception_class(&package, &main_class),
125            generated_header: true,
126        });
127
128        // Collect complex enums (enums with data variants and no serde tag) — use Object for these fields.
129        // Tagged unions (serde_tag is set) are now generated as proper sealed interfaces
130        // and can be deserialized as their concrete types, so they are NOT complex_enums.
131        let complex_enums: AHashSet<String> = api
132            .enums
133            .iter()
134            .filter(|e| e.serde_tag.is_none() && e.variants.iter().any(|v| !v.fields.is_empty()))
135            .map(|e| e.name.clone())
136            .collect();
137
138        // Resolve language-level serde rename strategy (always wins over IR type-level).
139        let lang_rename_all = config.serde_rename_all_for_language(Language::Java);
140
141        // 4. Record types
142        for typ in &api.types {
143            if !typ.is_opaque && !typ.fields.is_empty() {
144                files.push(GeneratedFile {
145                    path: base_path.join(format!("{}.java", typ.name)),
146                    content: gen_record_type(&package, typ, &complex_enums, &lang_rename_all),
147                    generated_header: true,
148                });
149                // Generate builder class for types with defaults
150                if typ.has_default {
151                    files.push(GeneratedFile {
152                        path: base_path.join(format!("{}Builder.java", typ.name)),
153                        content: gen_builder_class(&package, typ),
154                        generated_header: true,
155                    });
156                }
157            }
158        }
159
160        // Collect builder class names generated from record types with defaults,
161        // so we can skip opaque types that would collide with them.
162        let builder_class_names: AHashSet<String> = api
163            .types
164            .iter()
165            .filter(|t| !t.is_opaque && !t.fields.is_empty() && t.has_default)
166            .map(|t| format!("{}Builder", t.name))
167            .collect();
168
169        // 4b. Opaque handle types (skip if a pure-Java builder already covers this name)
170        for typ in &api.types {
171            if typ.is_opaque && !builder_class_names.contains(&typ.name) {
172                files.push(GeneratedFile {
173                    path: base_path.join(format!("{}.java", typ.name)),
174                    content: gen_opaque_handle_class(&package, typ, &prefix),
175                    generated_header: true,
176                });
177            }
178        }
179
180        // 5. Enums
181        for enum_def in &api.enums {
182            files.push(GeneratedFile {
183                path: base_path.join(format!("{}.java", enum_def.name)),
184                content: gen_enum_class(&package, enum_def),
185                generated_header: true,
186            });
187        }
188
189        // 6. Error exception classes
190        for error in &api.errors {
191            for (class_name, content) in alef_codegen::error_gen::gen_java_error_types(error, &package) {
192                files.push(GeneratedFile {
193                    path: base_path.join(format!("{}.java", class_name)),
194                    content,
195                    generated_header: true,
196                });
197            }
198        }
199
200        // Build adapter body map (consumed by generators via body substitution)
201        let _adapter_bodies = alef_adapters::build_adapter_bodies(config, Language::Java)?;
202
203        Ok(files)
204    }
205
206    fn generate_public_api(&self, api: &ApiSurface, config: &AlefConfig) -> anyhow::Result<Vec<GeneratedFile>> {
207        let package = config.java_package();
208        let prefix = config.ffi_prefix();
209        let main_class = Self::resolve_main_class(api);
210        let package_path = package.replace('.', "/");
211
212        let output_dir = resolve_output_dir(
213            config.output.java.as_ref(),
214            &config.crate_config.name,
215            "packages/java/src/main/java/",
216        );
217
218        let base_path = PathBuf::from(&output_dir).join(&package_path);
219
220        // Generate a high-level public API class that wraps the raw FFI class.
221        // Class name = main_class without "Rs" suffix (e.g., HtmlToMarkdownRs -> HtmlToMarkdown)
222        let public_class = main_class.trim_end_matches("Rs").to_string();
223        let facade_content = gen_facade_class(api, &package, &public_class, &main_class, &prefix);
224
225        Ok(vec![GeneratedFile {
226            path: base_path.join(format!("{}.java", public_class)),
227            content: facade_content,
228            generated_header: true,
229        }])
230    }
231
232    fn build_config(&self) -> Option<BuildConfig> {
233        Some(BuildConfig {
234            tool: "mvn",
235            crate_suffix: "",
236            depends_on_ffi: true,
237            post_build: vec![],
238        })
239    }
240}
241
242// ---------------------------------------------------------------------------
243// NativeLib.java - FFI method handles
244// ---------------------------------------------------------------------------
245
246fn gen_native_lib(api: &ApiSurface, config: &AlefConfig, package: &str, prefix: &str) -> String {
247    // Generate the class body first, then scan it to determine which imports are needed.
248    let mut body = String::with_capacity(2048);
249    // Derive the native library name from the FFI output path (directory name with hyphens replaced
250    // by underscores), falling back to `{ffi_prefix}_ffi`.
251    let lib_name = config.ffi_lib_name();
252
253    writeln!(body, "final class NativeLib {{").ok();
254    writeln!(body, "    private static final Linker LINKER = Linker.nativeLinker();").ok();
255    writeln!(body, "    private static final SymbolLookup LIB;").ok();
256    writeln!(body).ok();
257    writeln!(body, "    static {{").ok();
258    writeln!(body, "        System.loadLibrary(\"{}\");", lib_name).ok();
259    writeln!(body, "        LIB = SymbolLookup.loaderLookup();").ok();
260    writeln!(body, "    }}").ok();
261    writeln!(body).ok();
262
263    // Generate method handles for free functions.
264    // All functions get handles regardless of is_async — the FFI layer always exposes
265    // synchronous C functions, and the Java async wrapper delegates to the sync method.
266    for func in &api.functions {
267        let ffi_name = format!("{}_{}", prefix, func.name.to_lowercase());
268        let return_layout = gen_ffi_layout(&func.return_type);
269        let param_layouts: Vec<String> = func.params.iter().map(|p| gen_ffi_layout(&p.ty)).collect();
270
271        let layout_str = gen_function_descriptor(&return_layout, &param_layouts);
272
273        let handle_name = format!("{}_{}", prefix.to_uppercase(), func.name.to_uppercase());
274
275        writeln!(
276            body,
277            "    static final MethodHandle {} = LINKER.downcallHandle(",
278            handle_name
279        )
280        .ok();
281        writeln!(body, "        LIB.find(\"{}\").orElseThrow(),", ffi_name).ok();
282        writeln!(body, "        {}", layout_str).ok();
283        writeln!(body, "    );").ok();
284    }
285
286    // free_string handle for releasing FFI-allocated strings
287    {
288        let free_name = format!("{}_free_string", prefix);
289        let handle_name = format!("{}_FREE_STRING", prefix.to_uppercase());
290        writeln!(body).ok();
291        writeln!(
292            body,
293            "    static final MethodHandle {} = LINKER.downcallHandle(",
294            handle_name
295        )
296        .ok();
297        writeln!(body, "        LIB.find(\"{}\").orElseThrow(),", free_name).ok();
298        writeln!(body, "        FunctionDescriptor.ofVoid(ValueLayout.ADDRESS)").ok();
299        writeln!(body, "    );").ok();
300    }
301
302    // Error handling — use the FFI's last_error_code and last_error_context symbols
303    {
304        writeln!(
305            body,
306            "    static final MethodHandle {}_LAST_ERROR_CODE = LINKER.downcallHandle(",
307            prefix.to_uppercase()
308        )
309        .ok();
310        writeln!(body, "        LIB.find(\"{}_last_error_code\").orElseThrow(),", prefix).ok();
311        writeln!(body, "        FunctionDescriptor.of(ValueLayout.JAVA_INT)").ok();
312        writeln!(body, "    );").ok();
313
314        writeln!(
315            body,
316            "    static final MethodHandle {}_LAST_ERROR_CONTEXT = LINKER.downcallHandle(",
317            prefix.to_uppercase()
318        )
319        .ok();
320        writeln!(
321            body,
322            "        LIB.find(\"{}_last_error_context\").orElseThrow(),",
323            prefix
324        )
325        .ok();
326        writeln!(body, "        FunctionDescriptor.of(ValueLayout.ADDRESS)").ok();
327        writeln!(body, "    );").ok();
328    }
329
330    // Track emitted free handles to avoid duplicates (a type may appear both as
331    // a function return type AND as an opaque type).
332    let mut emitted_free_handles: AHashSet<String> = AHashSet::new();
333
334    // Build the set of opaque type names so we can pick the right accessor below.
335    let opaque_type_names: AHashSet<String> = api
336        .types
337        .iter()
338        .filter(|t| t.is_opaque)
339        .map(|t| t.name.clone())
340        .collect();
341
342    // Accessor handles for Named return types (struct pointer → field accessor + free)
343    for func in &api.functions {
344        if let TypeRef::Named(name) = &func.return_type {
345            let type_snake = name.to_snake_case();
346            let type_upper = type_snake.to_uppercase();
347            let is_opaque = opaque_type_names.contains(name.as_str());
348
349            if is_opaque {
350                // Opaque handles: the caller wraps the pointer directly, no JSON needed.
351                // No content accessor is emitted for opaque types.
352            } else {
353                // Non-opaque record types: use _to_json to serialize the full struct to JSON,
354                // which the Java side then deserializes with ObjectMapper.
355                // NOTE: _content returns only the markdown string field, not the full JSON.
356                let to_json_handle = format!("{}_{}_TO_JSON", prefix.to_uppercase(), type_upper);
357                let to_json_ffi = format!("{}_{}_to_json", prefix, type_snake);
358                writeln!(body).ok();
359                writeln!(
360                    body,
361                    "    static final MethodHandle {} = LINKER.downcallHandle(",
362                    to_json_handle
363                )
364                .ok();
365                writeln!(body, "        LIB.find(\"{}\").orElseThrow(),", to_json_ffi).ok();
366                writeln!(
367                    body,
368                    "        FunctionDescriptor.of(ValueLayout.ADDRESS, ValueLayout.ADDRESS)"
369                )
370                .ok();
371                writeln!(body, "    );").ok();
372            }
373
374            // _free: (struct_ptr) -> void
375            let free_handle = format!("{}_{}_FREE", prefix.to_uppercase(), type_upper);
376            let free_ffi = format!("{}_{}_free", prefix, type_snake);
377            if emitted_free_handles.insert(free_handle.clone()) {
378                writeln!(body).ok();
379                writeln!(
380                    body,
381                    "    static final MethodHandle {} = LINKER.downcallHandle(",
382                    free_handle
383                )
384                .ok();
385                writeln!(body, "        LIB.find(\"{}\").orElseThrow(),", free_ffi).ok();
386                writeln!(body, "        FunctionDescriptor.ofVoid(ValueLayout.ADDRESS)").ok();
387                writeln!(body, "    );").ok();
388            }
389        }
390    }
391
392    // FROM_JSON + FREE handles for non-opaque Named types used as parameters.
393    // These allow serializing a Java record to JSON and passing it to the FFI.
394    let mut emitted_from_json_handles: AHashSet<String> = AHashSet::new();
395    for func in &api.functions {
396        for param in &func.params {
397            // Handle both Named and Optional<Named> params
398            let inner_name = match &param.ty {
399                TypeRef::Named(n) => Some(n.clone()),
400                TypeRef::Optional(inner) => {
401                    if let TypeRef::Named(n) = inner.as_ref() {
402                        Some(n.clone())
403                    } else {
404                        None
405                    }
406                }
407                _ => None,
408            };
409            if let Some(name) = inner_name {
410                if !opaque_type_names.contains(name.as_str()) {
411                    let type_snake = name.to_snake_case();
412                    let type_upper = type_snake.to_uppercase();
413
414                    // _from_json: (char*) -> struct_ptr
415                    let from_json_handle = format!("{}_{}_FROM_JSON", prefix.to_uppercase(), type_upper);
416                    let from_json_ffi = format!("{}_{}_from_json", prefix, type_snake);
417                    if emitted_from_json_handles.insert(from_json_handle.clone()) {
418                        writeln!(body).ok();
419                        writeln!(
420                            body,
421                            "    static final MethodHandle {} = LINKER.downcallHandle(",
422                            from_json_handle
423                        )
424                        .ok();
425                        writeln!(body, "        LIB.find(\"{}\").orElseThrow(),", from_json_ffi).ok();
426                        writeln!(
427                            body,
428                            "        FunctionDescriptor.of(ValueLayout.ADDRESS, ValueLayout.ADDRESS)"
429                        )
430                        .ok();
431                        writeln!(body, "    );").ok();
432                    }
433
434                    // _free: (struct_ptr) -> void
435                    let free_handle = format!("{}_{}_FREE", prefix.to_uppercase(), type_upper);
436                    let free_ffi = format!("{}_{}_free", prefix, type_snake);
437                    if emitted_free_handles.insert(free_handle.clone()) {
438                        writeln!(body).ok();
439                        writeln!(
440                            body,
441                            "    static final MethodHandle {} = LINKER.downcallHandle(",
442                            free_handle
443                        )
444                        .ok();
445                        writeln!(body, "        LIB.find(\"{}\").orElseThrow(),", free_ffi).ok();
446                        writeln!(body, "        FunctionDescriptor.ofVoid(ValueLayout.ADDRESS)").ok();
447                        writeln!(body, "    );").ok();
448                    }
449                }
450            }
451        }
452    }
453
454    // Collect builder class names from record types with defaults, so we skip
455    // opaque types that are superseded by a pure-Java builder class.
456    let builder_class_names: AHashSet<String> = api
457        .types
458        .iter()
459        .filter(|t| !t.is_opaque && !t.fields.is_empty() && t.has_default)
460        .map(|t| format!("{}Builder", t.name))
461        .collect();
462
463    // Free handles for opaque types (handle pointer → void)
464    for typ in &api.types {
465        if typ.is_opaque && !builder_class_names.contains(&typ.name) {
466            let type_snake = typ.name.to_snake_case();
467            let type_upper = type_snake.to_uppercase();
468            let free_handle = format!("{}_{}_FREE", prefix.to_uppercase(), type_upper);
469            let free_ffi = format!("{}_{}_free", prefix, type_snake);
470            if emitted_free_handles.insert(free_handle.clone()) {
471                writeln!(body).ok();
472                writeln!(
473                    body,
474                    "    static final MethodHandle {} = LINKER.downcallHandle(",
475                    free_handle
476                )
477                .ok();
478                writeln!(body, "        LIB.find(\"{}\").orElseThrow(),", free_ffi).ok();
479                writeln!(body, "        FunctionDescriptor.ofVoid(ValueLayout.ADDRESS)").ok();
480                writeln!(body, "    );").ok();
481            }
482        }
483    }
484
485    writeln!(body, "}}").ok();
486
487    // Now assemble the file with only the imports that are actually used in the body.
488    let mut out = String::with_capacity(body.len() + 512);
489
490    writeln!(out, "// DO NOT EDIT - auto-generated by alef").ok();
491    writeln!(out, "package {};", package).ok();
492    writeln!(out).ok();
493    if body.contains("Arena") {
494        writeln!(out, "import java.lang.foreign.Arena;").ok();
495    }
496    if body.contains("FunctionDescriptor") {
497        writeln!(out, "import java.lang.foreign.FunctionDescriptor;").ok();
498    }
499    if body.contains("Linker") {
500        writeln!(out, "import java.lang.foreign.Linker;").ok();
501    }
502    if body.contains("MemorySegment") {
503        writeln!(out, "import java.lang.foreign.MemorySegment;").ok();
504    }
505    if body.contains("SymbolLookup") {
506        writeln!(out, "import java.lang.foreign.SymbolLookup;").ok();
507    }
508    if body.contains("ValueLayout") {
509        writeln!(out, "import java.lang.foreign.ValueLayout;").ok();
510    }
511    if body.contains("MethodHandle") {
512        writeln!(out, "import java.lang.invoke.MethodHandle;").ok();
513    }
514    writeln!(out).ok();
515
516    out.push_str(&body);
517
518    out
519}
520
521// ---------------------------------------------------------------------------
522// Main wrapper class
523// ---------------------------------------------------------------------------
524
525fn gen_main_class(api: &ApiSurface, _config: &AlefConfig, package: &str, class_name: &str, prefix: &str) -> String {
526    // Build the set of opaque type names so we can distinguish opaque handles from records
527    let opaque_types: AHashSet<String> = api
528        .types
529        .iter()
530        .filter(|t| t.is_opaque)
531        .map(|t| t.name.clone())
532        .collect();
533
534    // Generate the class body first, then scan it to determine which imports are needed.
535    let mut body = String::with_capacity(4096);
536
537    writeln!(body, "public final class {} {{", class_name).ok();
538    writeln!(body, "    private {}() {{ }}", class_name).ok();
539    writeln!(body).ok();
540
541    // Generate static methods for free functions
542    for func in &api.functions {
543        // Always generate sync method
544        gen_sync_function_method(&mut body, func, prefix, class_name, &opaque_types);
545        writeln!(body).ok();
546
547        // Also generate async wrapper if marked as async
548        if func.is_async {
549            gen_async_wrapper_method(&mut body, func);
550            writeln!(body).ok();
551        }
552    }
553
554    // Add helper methods only if they are referenced in the body
555    gen_helper_methods(&mut body);
556
557    writeln!(body, "}}").ok();
558
559    // Now assemble the file with only the imports that are actually used in the body.
560    let mut out = String::with_capacity(body.len() + 512);
561
562    writeln!(out, "// DO NOT EDIT - auto-generated by alef").ok();
563    writeln!(out, "package {};", package).ok();
564    writeln!(out).ok();
565    if body.contains("Arena") {
566        writeln!(out, "import java.lang.foreign.Arena;").ok();
567    }
568    if body.contains("FunctionDescriptor") {
569        writeln!(out, "import java.lang.foreign.FunctionDescriptor;").ok();
570    }
571    if body.contains("Linker") {
572        writeln!(out, "import java.lang.foreign.Linker;").ok();
573    }
574    if body.contains("MemorySegment") {
575        writeln!(out, "import java.lang.foreign.MemorySegment;").ok();
576    }
577    if body.contains("SymbolLookup") {
578        writeln!(out, "import java.lang.foreign.SymbolLookup;").ok();
579    }
580    if body.contains("ValueLayout") {
581        writeln!(out, "import java.lang.foreign.ValueLayout;").ok();
582    }
583    if body.contains("List<") {
584        writeln!(out, "import java.util.List;").ok();
585    }
586    if body.contains("Map<") {
587        writeln!(out, "import java.util.Map;").ok();
588    }
589    if body.contains("Optional<") {
590        writeln!(out, "import java.util.Optional;").ok();
591    }
592    if body.contains("HashMap<") || body.contains("new HashMap") {
593        writeln!(out, "import java.util.HashMap;").ok();
594    }
595    if body.contains("CompletableFuture") {
596        writeln!(out, "import java.util.concurrent.CompletableFuture;").ok();
597    }
598    if body.contains("CompletionException") {
599        writeln!(out, "import java.util.concurrent.CompletionException;").ok();
600    }
601    // Only import the short name `ObjectMapper` when it's used as a type reference (not just via
602    // `createObjectMapper()` which uses fully qualified names internally).
603    // Check for " ObjectMapper" (space before) which indicates use as a type, not a method name suffix.
604    if body.contains(" ObjectMapper") {
605        writeln!(out, "import com.fasterxml.jackson.databind.ObjectMapper;").ok();
606    }
607    writeln!(out).ok();
608
609    out.push_str(&body);
610
611    out
612}
613
614fn gen_sync_function_method(
615    out: &mut String,
616    func: &FunctionDef,
617    prefix: &str,
618    class_name: &str,
619    opaque_types: &AHashSet<String>,
620) {
621    let params: Vec<String> = func
622        .params
623        .iter()
624        .map(|p| {
625            let ptype = java_type(&p.ty);
626            format!("{} {}", ptype, to_java_name(&p.name))
627        })
628        .collect();
629
630    let return_type = java_type(&func.return_type);
631
632    writeln!(
633        out,
634        "    public static {} {}({}) throws {}Exception {{",
635        return_type,
636        to_java_name(&func.name),
637        params.join(", "),
638        class_name
639    )
640    .ok();
641
642    writeln!(out, "        try (var arena = Arena.ofConfined()) {{").ok();
643
644    // Collect non-opaque Named params that need FFI pointer cleanup after the call.
645    // These are Rust-allocated by _from_json and must be freed with _free.
646    let ffi_ptr_params: Vec<(String, String)> = func
647        .params
648        .iter()
649        .filter_map(|p| {
650            let inner_name = match &p.ty {
651                TypeRef::Named(n) if !opaque_types.contains(n.as_str()) => Some(n.clone()),
652                TypeRef::Optional(inner) => {
653                    if let TypeRef::Named(n) = inner.as_ref() {
654                        if !opaque_types.contains(n.as_str()) {
655                            Some(n.clone())
656                        } else {
657                            None
658                        }
659                    } else {
660                        None
661                    }
662                }
663                _ => None,
664            };
665            inner_name.map(|type_name| {
666                let cname = "c".to_string() + &to_java_name(&p.name);
667                let type_snake = type_name.to_snake_case();
668                let free_handle = format!("NativeLib.{}_{}_FREE", prefix.to_uppercase(), type_snake.to_uppercase());
669                (cname, free_handle)
670            })
671        })
672        .collect();
673
674    // Marshal parameters (use camelCase Java names)
675    for param in &func.params {
676        marshal_param_to_ffi(out, &to_java_name(&param.name), &param.ty, opaque_types, prefix);
677    }
678
679    // Call FFI
680    let ffi_handle = format!("NativeLib.{}_{}", prefix.to_uppercase(), func.name.to_uppercase());
681
682    let call_args: Vec<String> = func
683        .params
684        .iter()
685        .map(|p| ffi_param_name(&to_java_name(&p.name), &p.ty, opaque_types))
686        .collect();
687
688    // Emit a helper closure to free FFI-allocated param pointers (e.g. options created by _from_json)
689    let emit_ffi_ptr_cleanup = |out: &mut String| {
690        for (cname, free_handle) in &ffi_ptr_params {
691            writeln!(out, "            if (!{}.equals(MemorySegment.NULL)) {{", cname).ok();
692            writeln!(out, "                {}.invoke({});", free_handle, cname).ok();
693            writeln!(out, "            }}").ok();
694        }
695    };
696
697    if matches!(func.return_type, TypeRef::Unit) {
698        writeln!(out, "            {}.invoke({});", ffi_handle, call_args.join(", ")).ok();
699        emit_ffi_ptr_cleanup(out);
700        writeln!(out, "        }} catch (Throwable e) {{").ok();
701        writeln!(
702            out,
703            "            throw new {}Exception(\"FFI call failed\", e);",
704            class_name
705        )
706        .ok();
707        writeln!(out, "        }}").ok();
708    } else if is_ffi_string_return(&func.return_type) {
709        let free_handle = format!("NativeLib.{}_FREE_STRING", prefix.to_uppercase());
710        writeln!(
711            out,
712            "            var resultPtr = (MemorySegment) {}.invoke({});",
713            ffi_handle,
714            call_args.join(", ")
715        )
716        .ok();
717        emit_ffi_ptr_cleanup(out);
718        writeln!(out, "            if (resultPtr.equals(MemorySegment.NULL)) {{").ok();
719        writeln!(
720            out,
721            "                int errCode = (int) NativeLib.{}_LAST_ERROR_CODE.invoke();",
722            prefix.to_uppercase()
723        )
724        .ok();
725        writeln!(out, "                if (errCode != 0) {{").ok();
726        writeln!(
727            out,
728            "                    var ctxPtr = (MemorySegment) NativeLib.{}_LAST_ERROR_CONTEXT.invoke();",
729            prefix.to_uppercase()
730        )
731        .ok();
732        writeln!(
733            out,
734            "                    String msg = ctxPtr.reinterpret(Long.MAX_VALUE).getString(0);"
735        )
736        .ok();
737        writeln!(
738            out,
739            "                    throw new {}Exception(errCode, msg);",
740            class_name
741        )
742        .ok();
743        writeln!(out, "                }}").ok();
744        writeln!(out, "                return null;").ok();
745        writeln!(out, "            }}").ok();
746        writeln!(
747            out,
748            "            String result = resultPtr.reinterpret(Long.MAX_VALUE).getString(0);"
749        )
750        .ok();
751        writeln!(out, "            {}.invoke(resultPtr);", free_handle).ok();
752        writeln!(out, "            return result;").ok();
753        writeln!(out, "        }} catch (Throwable e) {{").ok();
754        writeln!(
755            out,
756            "            throw new {}Exception(\"FFI call failed\", e);",
757            class_name
758        )
759        .ok();
760        writeln!(out, "        }}").ok();
761    } else if matches!(func.return_type, TypeRef::Named(_)) {
762        // Named return types: FFI returns a struct pointer.
763        let return_type_name = match &func.return_type {
764            TypeRef::Named(name) => name,
765            _ => unreachable!(),
766        };
767        let is_opaque = opaque_types.contains(return_type_name.as_str());
768
769        writeln!(
770            out,
771            "            var resultPtr = (MemorySegment) {}.invoke({});",
772            ffi_handle,
773            call_args.join(", ")
774        )
775        .ok();
776        emit_ffi_ptr_cleanup(out);
777        writeln!(out, "            if (resultPtr.equals(MemorySegment.NULL)) {{").ok();
778        writeln!(
779            out,
780            "                int errCode = (int) NativeLib.{}_LAST_ERROR_CODE.invoke();",
781            prefix.to_uppercase()
782        )
783        .ok();
784        writeln!(out, "                if (errCode != 0) {{").ok();
785        writeln!(
786            out,
787            "                    var ctxPtr = (MemorySegment) NativeLib.{}_LAST_ERROR_CONTEXT.invoke();",
788            prefix.to_uppercase()
789        )
790        .ok();
791        writeln!(
792            out,
793            "                    String msg = ctxPtr.reinterpret(Long.MAX_VALUE).getString(0);"
794        )
795        .ok();
796        writeln!(
797            out,
798            "                    throw new {}Exception(errCode, msg);",
799            class_name
800        )
801        .ok();
802        writeln!(out, "                }}").ok();
803        writeln!(out, "                return null;").ok();
804        writeln!(out, "            }}").ok();
805
806        if is_opaque {
807            // Opaque handles: wrap the raw pointer directly, caller owns and will close()
808            writeln!(out, "            return new {}(resultPtr);", return_type_name).ok();
809        } else {
810            // Record types: use _to_json to serialize the full struct to JSON, then deserialize.
811            // NOTE: _content only returns the markdown string field, not a full JSON object.
812            let type_snake = return_type_name.to_snake_case();
813            let free_handle = format!("NativeLib.{}_{}_FREE", prefix.to_uppercase(), type_snake.to_uppercase());
814            let to_json_handle = format!(
815                "NativeLib.{}_{}_TO_JSON",
816                prefix.to_uppercase(),
817                type_snake.to_uppercase()
818            );
819            writeln!(
820                out,
821                "            var jsonPtr = (MemorySegment) {}.invoke(resultPtr);",
822                to_json_handle
823            )
824            .ok();
825            writeln!(out, "            {}.invoke(resultPtr);", free_handle).ok();
826            writeln!(out, "            if (jsonPtr.equals(MemorySegment.NULL)) {{").ok();
827            writeln!(
828                out,
829                "                int errCode = (int) NativeLib.{}_LAST_ERROR_CODE.invoke();",
830                prefix.to_uppercase()
831            )
832            .ok();
833            writeln!(out, "                if (errCode != 0) {{").ok();
834            writeln!(
835                out,
836                "                    var ctxPtr = (MemorySegment) NativeLib.{}_LAST_ERROR_CONTEXT.invoke();",
837                prefix.to_uppercase()
838            )
839            .ok();
840            writeln!(
841                out,
842                "                    String msg = ctxPtr.reinterpret(Long.MAX_VALUE).getString(0);"
843            )
844            .ok();
845            writeln!(
846                out,
847                "                    throw new {}Exception(errCode, msg);",
848                class_name
849            )
850            .ok();
851            writeln!(out, "                }}").ok();
852            writeln!(out, "                return null;").ok();
853            writeln!(out, "            }}").ok();
854            writeln!(
855                out,
856                "            String json = jsonPtr.reinterpret(Long.MAX_VALUE).getString(0);"
857            )
858            .ok();
859            writeln!(
860                out,
861                "            NativeLib.{}_FREE_STRING.invoke(jsonPtr);",
862                prefix.to_uppercase()
863            )
864            .ok();
865            writeln!(
866                out,
867                "            return createObjectMapper().readValue(json, {}.class);",
868                return_type_name
869            )
870            .ok();
871        }
872
873        writeln!(out, "        }} catch (Throwable e) {{").ok();
874        writeln!(
875            out,
876            "            throw new {}Exception(\"FFI call failed\", e);",
877            class_name
878        )
879        .ok();
880        writeln!(out, "        }}").ok();
881    } else if matches!(func.return_type, TypeRef::Vec(_)) {
882        // Vec return types: FFI returns a JSON string pointer; deserialize into List<T>.
883        let free_handle = format!("NativeLib.{}_FREE_STRING", prefix.to_uppercase());
884        writeln!(
885            out,
886            "            var resultPtr = (MemorySegment) {}.invoke({});",
887            ffi_handle,
888            call_args.join(", ")
889        )
890        .ok();
891        emit_ffi_ptr_cleanup(out);
892        writeln!(out, "            if (resultPtr.equals(MemorySegment.NULL)) {{").ok();
893        writeln!(out, "                return java.util.List.of();").ok();
894        writeln!(out, "            }}").ok();
895        writeln!(
896            out,
897            "            String json = resultPtr.reinterpret(Long.MAX_VALUE).getString(0);"
898        )
899        .ok();
900        writeln!(out, "            {}.invoke(resultPtr);", free_handle).ok();
901        // Determine the element type for deserialization
902        let element_type = match &func.return_type {
903            TypeRef::Vec(inner) => java_type(inner),
904            _ => unreachable!(),
905        };
906        writeln!(
907            out,
908            "            return createObjectMapper().readValue(json, new com.fasterxml.jackson.core.type.TypeReference<java.util.List<{}>>() {{ }});",
909            element_type
910        )
911        .ok();
912        writeln!(out, "        }} catch (Throwable e) {{").ok();
913        writeln!(
914            out,
915            "            throw new {}Exception(\"FFI call failed\", e);",
916            class_name
917        )
918        .ok();
919        writeln!(out, "        }}").ok();
920    } else {
921        writeln!(
922            out,
923            "            var primitiveResult = ({}) {}.invoke({});",
924            java_ffi_return_cast(&func.return_type),
925            ffi_handle,
926            call_args.join(", ")
927        )
928        .ok();
929        emit_ffi_ptr_cleanup(out);
930        writeln!(out, "            return primitiveResult;").ok();
931        writeln!(out, "        }} catch (Throwable e) {{").ok();
932        writeln!(
933            out,
934            "            throw new {}Exception(\"FFI call failed\", e);",
935            class_name
936        )
937        .ok();
938        writeln!(out, "        }}").ok();
939    }
940
941    writeln!(out, "    }}").ok();
942}
943
944fn gen_async_wrapper_method(out: &mut String, func: &FunctionDef) {
945    let params: Vec<String> = func
946        .params
947        .iter()
948        .map(|p| {
949            let ptype = java_type(&p.ty);
950            format!("{} {}", ptype, to_java_name(&p.name))
951        })
952        .collect();
953
954    let return_type = match &func.return_type {
955        TypeRef::Unit => "Void".to_string(),
956        other => java_boxed_type(other).to_string(),
957    };
958
959    let sync_method_name = to_java_name(&func.name);
960    let async_method_name = format!("{}Async", sync_method_name);
961    let param_names: Vec<String> = func.params.iter().map(|p| to_java_name(&p.name)).collect();
962
963    writeln!(
964        out,
965        "    public static CompletableFuture<{}> {}({}) {{",
966        return_type,
967        async_method_name,
968        params.join(", ")
969    )
970    .ok();
971    writeln!(out, "        return CompletableFuture.supplyAsync(() -> {{").ok();
972    writeln!(out, "            try {{").ok();
973    writeln!(
974        out,
975        "                return {}({});",
976        sync_method_name,
977        param_names.join(", ")
978    )
979    .ok();
980    writeln!(out, "            }} catch (Throwable e) {{").ok();
981    writeln!(out, "                throw new CompletionException(e);").ok();
982    writeln!(out, "            }}").ok();
983    writeln!(out, "        }});").ok();
984    writeln!(out, "    }}").ok();
985}
986
987// ---------------------------------------------------------------------------
988// Exception class
989// ---------------------------------------------------------------------------
990
991fn gen_exception_class(package: &str, class_name: &str) -> String {
992    let mut out = String::with_capacity(512);
993
994    writeln!(out, "// DO NOT EDIT - auto-generated by alef").ok();
995    writeln!(out, "package {};", package).ok();
996    writeln!(out).ok();
997
998    writeln!(out, "public class {}Exception extends Exception {{", class_name).ok();
999    writeln!(out, "    private final int code;").ok();
1000    writeln!(out).ok();
1001    writeln!(out, "    public {}Exception(int code, String message) {{", class_name).ok();
1002    writeln!(out, "        super(message);").ok();
1003    writeln!(out, "        this.code = code;").ok();
1004    writeln!(out, "    }}").ok();
1005    writeln!(out).ok();
1006    writeln!(
1007        out,
1008        "    public {}Exception(String message, Throwable cause) {{",
1009        class_name
1010    )
1011    .ok();
1012    writeln!(out, "        super(message, cause);").ok();
1013    writeln!(out, "        this.code = -1;").ok();
1014    writeln!(out, "    }}").ok();
1015    writeln!(out).ok();
1016    writeln!(out, "    public int getCode() {{").ok();
1017    writeln!(out, "        return code;").ok();
1018    writeln!(out, "    }}").ok();
1019    writeln!(out, "}}").ok();
1020
1021    out
1022}
1023
1024// ---------------------------------------------------------------------------
1025// High-level facade class (public API)
1026// ---------------------------------------------------------------------------
1027
1028fn gen_facade_class(api: &ApiSurface, package: &str, public_class: &str, raw_class: &str, _prefix: &str) -> String {
1029    let mut body = String::with_capacity(4096);
1030
1031    writeln!(body, "public final class {} {{", public_class).ok();
1032    writeln!(body, "    private {}() {{ }}", public_class).ok();
1033    writeln!(body).ok();
1034
1035    // Generate static methods for free functions
1036    for func in &api.functions {
1037        // Sync method
1038        let params: Vec<String> = func
1039            .params
1040            .iter()
1041            .map(|p| {
1042                let ptype = java_type(&p.ty);
1043                format!("{} {}", ptype, to_java_name(&p.name))
1044            })
1045            .collect();
1046
1047        let return_type = java_type(&func.return_type);
1048
1049        if !func.doc.is_empty() {
1050            writeln!(body, "    /**").ok();
1051            for line in func.doc.lines() {
1052                writeln!(body, "     * {}", line).ok();
1053            }
1054            writeln!(body, "     */").ok();
1055        }
1056
1057        writeln!(
1058            body,
1059            "    public static {} {}({}) throws {}Exception {{",
1060            return_type,
1061            to_java_name(&func.name),
1062            params.join(", "),
1063            raw_class
1064        )
1065        .ok();
1066
1067        // Null checks for required parameters
1068        for param in &func.params {
1069            if !param.optional {
1070                let pname = to_java_name(&param.name);
1071                writeln!(
1072                    body,
1073                    "        java.util.Objects.requireNonNull({}, \"{} must not be null\");",
1074                    pname, pname
1075                )
1076                .ok();
1077            }
1078        }
1079
1080        // Delegate to the raw FFI class
1081        let call_args: Vec<String> = func.params.iter().map(|p| to_java_name(&p.name)).collect();
1082
1083        if matches!(func.return_type, TypeRef::Unit) {
1084            writeln!(
1085                body,
1086                "        {}.{}({});",
1087                raw_class,
1088                to_java_name(&func.name),
1089                call_args.join(", ")
1090            )
1091            .ok();
1092        } else {
1093            writeln!(
1094                body,
1095                "        return {}.{}({});",
1096                raw_class,
1097                to_java_name(&func.name),
1098                call_args.join(", ")
1099            )
1100            .ok();
1101        }
1102
1103        writeln!(body, "    }}").ok();
1104        writeln!(body).ok();
1105
1106        // Generate overload without optional params (convenience method)
1107        let has_optional = func.params.iter().any(|p| p.optional);
1108        if has_optional {
1109            let required_params: Vec<String> = func
1110                .params
1111                .iter()
1112                .filter(|p| !p.optional)
1113                .map(|p| {
1114                    let ptype = java_type(&p.ty);
1115                    format!("{} {}", ptype, to_java_name(&p.name))
1116                })
1117                .collect();
1118
1119            writeln!(
1120                body,
1121                "    public static {} {}({}) throws {}Exception {{",
1122                return_type,
1123                to_java_name(&func.name),
1124                required_params.join(", "),
1125                raw_class
1126            )
1127            .ok();
1128
1129            // Build call with null for optional params
1130            let full_args: Vec<String> = func
1131                .params
1132                .iter()
1133                .map(|p| {
1134                    if p.optional {
1135                        "null".to_string()
1136                    } else {
1137                        to_java_name(&p.name)
1138                    }
1139                })
1140                .collect();
1141
1142            if matches!(func.return_type, TypeRef::Unit) {
1143                writeln!(body, "        {}({});", to_java_name(&func.name), full_args.join(", ")).ok();
1144            } else {
1145                writeln!(
1146                    body,
1147                    "        return {}({});",
1148                    to_java_name(&func.name),
1149                    full_args.join(", ")
1150                )
1151                .ok();
1152            }
1153
1154            writeln!(body, "    }}").ok();
1155            writeln!(body).ok();
1156        }
1157    }
1158
1159    writeln!(body, "}}").ok();
1160
1161    // Now assemble the file with imports
1162    let mut out = String::with_capacity(body.len() + 512);
1163
1164    writeln!(out, "// DO NOT EDIT - auto-generated by alef").ok();
1165    writeln!(out, "package {};", package).ok();
1166
1167    // Check what imports are needed based on content
1168    let has_list = body.contains("List<");
1169    let has_map = body.contains("Map<");
1170    let has_optional = body.contains("Optional<");
1171    let has_imports = has_list || has_map || has_optional;
1172
1173    if has_imports {
1174        writeln!(out).ok();
1175        if has_list {
1176            writeln!(out, "import java.util.List;").ok();
1177        }
1178        if has_map {
1179            writeln!(out, "import java.util.Map;").ok();
1180        }
1181        if has_optional {
1182            writeln!(out, "import java.util.Optional;").ok();
1183        }
1184    }
1185
1186    writeln!(out).ok();
1187    out.push_str(&body);
1188
1189    out
1190}
1191
1192// ---------------------------------------------------------------------------
1193// Opaque handle classes
1194// ---------------------------------------------------------------------------
1195
1196fn gen_opaque_handle_class(package: &str, typ: &TypeDef, prefix: &str) -> String {
1197    let mut out = String::with_capacity(1024);
1198    let class_name = &typ.name;
1199    let type_snake = class_name.to_snake_case();
1200
1201    writeln!(out, "// DO NOT EDIT - auto-generated by alef").ok();
1202    writeln!(out, "package {};", package).ok();
1203    writeln!(out).ok();
1204    writeln!(out, "import java.lang.foreign.MemorySegment;").ok();
1205    writeln!(out).ok();
1206
1207    if !typ.doc.is_empty() {
1208        writeln!(out, "/**").ok();
1209        for line in typ.doc.lines() {
1210            writeln!(out, " * {}", line).ok();
1211        }
1212        writeln!(out, " */").ok();
1213    }
1214
1215    writeln!(out, "public class {} implements AutoCloseable {{", class_name).ok();
1216    writeln!(out, "    private final MemorySegment handle;").ok();
1217    writeln!(out).ok();
1218    writeln!(out, "    {}(MemorySegment handle) {{", class_name).ok();
1219    writeln!(out, "        this.handle = handle;").ok();
1220    writeln!(out, "    }}").ok();
1221    writeln!(out).ok();
1222    writeln!(out, "    MemorySegment handle() {{").ok();
1223    writeln!(out, "        return this.handle;").ok();
1224    writeln!(out, "    }}").ok();
1225    writeln!(out).ok();
1226    writeln!(out, "    @Override").ok();
1227    writeln!(out, "    public void close() {{").ok();
1228    writeln!(
1229        out,
1230        "        if (handle != null && !handle.equals(MemorySegment.NULL)) {{"
1231    )
1232    .ok();
1233    writeln!(out, "            try {{").ok();
1234    writeln!(
1235        out,
1236        "                NativeLib.{}_{}_FREE.invoke(handle);",
1237        prefix.to_uppercase(),
1238        type_snake.to_uppercase()
1239    )
1240    .ok();
1241    writeln!(out, "            }} catch (Throwable e) {{").ok();
1242    writeln!(
1243        out,
1244        "                throw new RuntimeException(\"Failed to free {}: \" + e.getMessage(), e);",
1245        class_name
1246    )
1247    .ok();
1248    writeln!(out, "            }}").ok();
1249    writeln!(out, "        }}").ok();
1250    writeln!(out, "    }}").ok();
1251    writeln!(out, "}}").ok();
1252
1253    out
1254}
1255
1256// ---------------------------------------------------------------------------
1257// Record types (Java records)
1258// ---------------------------------------------------------------------------
1259
1260/// Maximum line length before splitting record fields across multiple lines.
1261/// Checkstyle enforces 120 chars; we split at 100 to leave headroom for indentation.
1262const RECORD_LINE_WRAP_THRESHOLD: usize = 100;
1263
1264fn gen_record_type(package: &str, typ: &TypeDef, complex_enums: &AHashSet<String>, lang_rename_all: &str) -> String {
1265    // Generate the record body first, then scan for needed imports.
1266    // For each field, if the language uses camelCase but the JSON key is snake_case
1267    // (the Rust default), annotate with @JsonProperty so Jackson maps correctly.
1268    let field_list: Vec<String> = typ
1269        .fields
1270        .iter()
1271        .map(|f| {
1272            // Complex enums (tagged unions with data) can't be simple Java enums.
1273            // Use Object for flexible Jackson deserialization.
1274            let is_complex = matches!(&f.ty, TypeRef::Named(n) if complex_enums.contains(n.as_str()));
1275            let ftype = if is_complex {
1276                "Object".to_string()
1277            } else if f.optional {
1278                format!("Optional<{}>", java_boxed_type(&f.ty))
1279            } else {
1280                java_type(&f.ty).to_string()
1281            };
1282            let jname = safe_java_field_name(&f.name);
1283            // When the language convention is camelCase but the JSON wire format uses
1284            // snake_case (the Rust/serde default), add an explicit @JsonProperty annotation
1285            // so Jackson serialises/deserialises using the correct snake_case key.
1286            if lang_rename_all == "camelCase" && f.name.contains('_') {
1287                format!("@JsonProperty(\"{}\") {} {}", f.name, ftype, jname)
1288            } else {
1289                format!("{} {}", ftype, jname)
1290            }
1291        })
1292        .collect();
1293
1294    // Build the single-line form to check length and scan for imports.
1295    let single_line = format!("public record {}({}) {{ }}", typ.name, field_list.join(", "));
1296
1297    // Build the actual record declaration, splitting across lines if too long.
1298    let mut record_block = String::new();
1299    if single_line.len() > RECORD_LINE_WRAP_THRESHOLD && field_list.len() > 1 {
1300        writeln!(record_block, "public record {}(", typ.name).ok();
1301        for (i, field) in field_list.iter().enumerate() {
1302            let comma = if i < field_list.len() - 1 { "," } else { "" };
1303            writeln!(record_block, "    {}{}", field, comma).ok();
1304        }
1305        writeln!(record_block, ") {{").ok();
1306    } else {
1307        writeln!(record_block, "public record {}({}) {{", typ.name, field_list.join(", ")).ok();
1308    }
1309
1310    // Add builder() factory method if type has defaults
1311    if typ.has_default {
1312        writeln!(record_block, "    public static {}Builder builder() {{", typ.name).ok();
1313        writeln!(record_block, "        return new {}Builder();", typ.name).ok();
1314        writeln!(record_block, "    }}").ok();
1315    }
1316
1317    writeln!(record_block, "}}").ok();
1318
1319    // Scan the single-line form to determine which imports are needed
1320    let needs_json_property = field_list.iter().any(|f| f.contains("@JsonProperty("));
1321    let mut out = String::with_capacity(record_block.len() + 512);
1322    writeln!(out, "// DO NOT EDIT - auto-generated by alef").ok();
1323    writeln!(out, "package {};", package).ok();
1324    writeln!(out).ok();
1325    if single_line.contains("List<") {
1326        writeln!(out, "import java.util.List;").ok();
1327    }
1328    if single_line.contains("Map<") {
1329        writeln!(out, "import java.util.Map;").ok();
1330    }
1331    if single_line.contains("Optional<") {
1332        writeln!(out, "import java.util.Optional;").ok();
1333    }
1334    if needs_json_property {
1335        writeln!(out, "import com.fasterxml.jackson.annotation.JsonProperty;").ok();
1336    }
1337    writeln!(out).ok();
1338    write!(out, "{}", record_block).ok();
1339
1340    out
1341}
1342
1343// ---------------------------------------------------------------------------
1344// Enum classes
1345// ---------------------------------------------------------------------------
1346
1347/// Apply a serde `rename_all` strategy to a variant name for Java codegen.
1348fn java_apply_rename_all(name: &str, rename_all: Option<&str>) -> String {
1349    match rename_all {
1350        Some("snake_case") => name.to_snake_case(),
1351        Some("camelCase") => name.to_lower_camel_case(),
1352        Some("PascalCase") => name.to_pascal_case(),
1353        Some("SCREAMING_SNAKE_CASE") => name.to_snake_case().to_uppercase(),
1354        Some("lowercase") => name.to_lowercase(),
1355        Some("UPPERCASE") => name.to_uppercase(),
1356        _ => name.to_lowercase(),
1357    }
1358}
1359
1360fn gen_enum_class(package: &str, enum_def: &EnumDef) -> String {
1361    let has_data_variants = enum_def.variants.iter().any(|v| !v.fields.is_empty());
1362
1363    // Tagged union: enum has a serde tag AND data variants → generate sealed interface hierarchy
1364    if enum_def.serde_tag.is_some() && has_data_variants {
1365        return gen_java_tagged_union(package, enum_def);
1366    }
1367
1368    let mut out = String::with_capacity(1024);
1369
1370    writeln!(out, "// DO NOT EDIT - auto-generated by alef").ok();
1371    writeln!(out, "package {};", package).ok();
1372    writeln!(out).ok();
1373    writeln!(out, "import com.fasterxml.jackson.annotation.JsonCreator;").ok();
1374    writeln!(out, "import com.fasterxml.jackson.annotation.JsonValue;").ok();
1375    writeln!(out).ok();
1376
1377    writeln!(out, "public enum {} {{", enum_def.name).ok();
1378
1379    for (i, variant) in enum_def.variants.iter().enumerate() {
1380        let comma = if i < enum_def.variants.len() - 1 { "," } else { ";" };
1381        // Use serde_rename if available, otherwise apply rename_all strategy
1382        let json_name = variant
1383            .serde_rename
1384            .clone()
1385            .unwrap_or_else(|| java_apply_rename_all(&variant.name, enum_def.serde_rename_all.as_deref()));
1386        writeln!(out, "    {}(\"{}\"){}", variant.name, json_name, comma).ok();
1387    }
1388
1389    writeln!(out).ok();
1390    writeln!(out, "    private final String value;").ok();
1391    writeln!(out).ok();
1392    writeln!(out, "    {}(String value) {{", enum_def.name).ok();
1393    writeln!(out, "        this.value = value;").ok();
1394    writeln!(out, "    }}").ok();
1395    writeln!(out).ok();
1396    writeln!(out, "    @JsonValue").ok();
1397    writeln!(out, "    public String getValue() {{").ok();
1398    writeln!(out, "        return value;").ok();
1399    writeln!(out, "    }}").ok();
1400    writeln!(out).ok();
1401    writeln!(out, "    @JsonCreator").ok();
1402    writeln!(out, "    public static {} fromValue(String value) {{", enum_def.name).ok();
1403    writeln!(out, "        for ({} e : values()) {{", enum_def.name).ok();
1404    writeln!(out, "            if (e.value.equalsIgnoreCase(value)) {{").ok();
1405    writeln!(out, "                return e;").ok();
1406    writeln!(out, "            }}").ok();
1407    writeln!(out, "        }}").ok();
1408    writeln!(
1409        out,
1410        "        throw new IllegalArgumentException(\"Unknown value: \" + value);"
1411    )
1412    .ok();
1413    writeln!(out, "    }}").ok();
1414
1415    writeln!(out, "}}").ok();
1416
1417    out
1418}
1419
1420/// Generate a Java sealed interface hierarchy for internally tagged enums.
1421///
1422/// Maps `#[serde(tag = "type_field", rename_all = "snake_case")]` Rust enums to
1423/// `@JsonTypeInfo` / `@JsonSubTypes` Java sealed interfaces with record implementations.
1424fn gen_java_tagged_union(package: &str, enum_def: &EnumDef) -> String {
1425    let tag_field = enum_def.serde_tag.as_deref().unwrap_or("type");
1426
1427    // Collect variant names to detect Java type name conflicts.
1428    // If a variant is named "List", "Map", or "Optional", using those type names
1429    // inside the sealed interface would refer to the nested record, not java.util.*.
1430    // We use fully qualified names in that case.
1431    let variant_names: std::collections::HashSet<&str> = enum_def.variants.iter().map(|v| v.name.as_str()).collect();
1432    let optional_type = if variant_names.contains("Optional") {
1433        "java.util.Optional"
1434    } else {
1435        "Optional"
1436    };
1437
1438    let mut out = String::with_capacity(2048);
1439    writeln!(out, "// DO NOT EDIT - auto-generated by alef").ok();
1440    writeln!(out, "package {};", package).ok();
1441    writeln!(out).ok();
1442    writeln!(out, "import com.fasterxml.jackson.annotation.JsonProperty;").ok();
1443    writeln!(out, "import com.fasterxml.jackson.annotation.JsonSubTypes;").ok();
1444    writeln!(out, "import com.fasterxml.jackson.annotation.JsonTypeInfo;").ok();
1445
1446    // Check if any field types need list/map/optional imports (only when not conflicting)
1447    let needs_list = !variant_names.contains("List")
1448        && enum_def
1449            .variants
1450            .iter()
1451            .any(|v| v.fields.iter().any(|f| matches!(&f.ty, TypeRef::Vec(_))));
1452    let needs_map = !variant_names.contains("Map")
1453        && enum_def
1454            .variants
1455            .iter()
1456            .any(|v| v.fields.iter().any(|f| matches!(&f.ty, TypeRef::Map(_, _))));
1457    let needs_optional =
1458        !variant_names.contains("Optional") && enum_def.variants.iter().any(|v| v.fields.iter().any(|f| f.optional));
1459    if needs_list {
1460        writeln!(out, "import java.util.List;").ok();
1461    }
1462    if needs_map {
1463        writeln!(out, "import java.util.Map;").ok();
1464    }
1465    if needs_optional {
1466        writeln!(out, "import java.util.Optional;").ok();
1467    }
1468    writeln!(out).ok();
1469
1470    // @JsonTypeInfo and @JsonSubTypes annotations
1471    writeln!(
1472        out,
1473        "@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = \"{tag_field}\", visible = false)"
1474    )
1475    .ok();
1476    writeln!(out, "@JsonSubTypes({{").ok();
1477    for (i, variant) in enum_def.variants.iter().enumerate() {
1478        let discriminator = variant
1479            .serde_rename
1480            .clone()
1481            .unwrap_or_else(|| java_apply_rename_all(&variant.name, enum_def.serde_rename_all.as_deref()));
1482        let comma = if i < enum_def.variants.len() - 1 { "," } else { "" };
1483        writeln!(
1484            out,
1485            "    @JsonSubTypes.Type(value = {}.{}.class, name = \"{}\"){}",
1486            enum_def.name, variant.name, discriminator, comma
1487        )
1488        .ok();
1489    }
1490    writeln!(out, "}})").ok();
1491    writeln!(out, "public sealed interface {} {{", enum_def.name).ok();
1492
1493    // Nested records for each variant
1494    for variant in &enum_def.variants {
1495        writeln!(out).ok();
1496        if variant.fields.is_empty() {
1497            // Unit variant
1498            writeln!(out, "    record {}() implements {} {{", variant.name, enum_def.name).ok();
1499            writeln!(out, "    }}").ok();
1500        } else {
1501            // Build field list using fully qualified names where variant names shadow imports
1502            let field_parts: Vec<String> = variant
1503                .fields
1504                .iter()
1505                .map(|f| {
1506                    let json_name = f.name.trim_start_matches('_');
1507                    let ftype = if f.optional {
1508                        let inner = java_boxed_type(&f.ty);
1509                        let inner_str = inner.as_ref();
1510                        // Replace "List"/"Map" with fully qualified if conflicting
1511                        let inner_qualified = if inner_str.starts_with("List<") && variant_names.contains("List") {
1512                            inner_str.replacen("List<", "java.util.List<", 1)
1513                        } else if inner_str.starts_with("Map<") && variant_names.contains("Map") {
1514                            inner_str.replacen("Map<", "java.util.Map<", 1)
1515                        } else {
1516                            inner_str.to_string()
1517                        };
1518                        format!("{optional_type}<{inner_qualified}>")
1519                    } else {
1520                        let t = java_type(&f.ty);
1521                        let t_str = t.as_ref();
1522                        if t_str.starts_with("List<") && variant_names.contains("List") {
1523                            t_str.replacen("List<", "java.util.List<", 1)
1524                        } else if t_str.starts_with("Map<") && variant_names.contains("Map") {
1525                            t_str.replacen("Map<", "java.util.Map<", 1)
1526                        } else {
1527                            t_str.to_string()
1528                        }
1529                    };
1530                    let jname = safe_java_field_name(json_name);
1531                    format!("@JsonProperty(\"{json_name}\") {ftype} {jname}")
1532                })
1533                .collect();
1534
1535            let single = format!(
1536                "    record {}({}) implements {} {{ }}",
1537                variant.name,
1538                field_parts.join(", "),
1539                enum_def.name
1540            );
1541
1542            if single.len() > RECORD_LINE_WRAP_THRESHOLD && field_parts.len() > 1 {
1543                writeln!(out, "    record {}(", variant.name).ok();
1544                for (i, fp) in field_parts.iter().enumerate() {
1545                    let comma = if i < field_parts.len() - 1 { "," } else { "" };
1546                    writeln!(out, "        {}{}", fp, comma).ok();
1547                }
1548                writeln!(out, "    ) implements {} {{", enum_def.name).ok();
1549                writeln!(out, "    }}").ok();
1550            } else {
1551                writeln!(
1552                    out,
1553                    "    record {}({}) implements {} {{ }}",
1554                    variant.name,
1555                    field_parts.join(", "),
1556                    enum_def.name
1557                )
1558                .ok();
1559            }
1560        }
1561    }
1562
1563    writeln!(out).ok();
1564    writeln!(out, "}}").ok();
1565    out
1566}
1567
1568// ---------------------------------------------------------------------------
1569// Helper functions for FFI marshalling
1570// ---------------------------------------------------------------------------
1571
1572fn gen_ffi_layout(ty: &TypeRef) -> String {
1573    match ty {
1574        TypeRef::Primitive(prim) => java_ffi_type(prim).to_string(),
1575        TypeRef::String | TypeRef::Char | TypeRef::Path | TypeRef::Json => "ValueLayout.ADDRESS".to_string(),
1576        TypeRef::Bytes => "ValueLayout.ADDRESS".to_string(),
1577        TypeRef::Optional(inner) => gen_ffi_layout(inner),
1578        TypeRef::Vec(_) => "ValueLayout.ADDRESS".to_string(),
1579        TypeRef::Map(_, _) => "ValueLayout.ADDRESS".to_string(),
1580        TypeRef::Named(_) => "ValueLayout.ADDRESS".to_string(),
1581        TypeRef::Unit => "".to_string(),
1582        TypeRef::Duration => "ValueLayout.JAVA_LONG".to_string(),
1583    }
1584}
1585
1586fn marshal_param_to_ffi(out: &mut String, name: &str, ty: &TypeRef, opaque_types: &AHashSet<String>, prefix: &str) {
1587    match ty {
1588        TypeRef::String | TypeRef::Char | TypeRef::Path | TypeRef::Json => {
1589            let cname = "c".to_string() + name;
1590            writeln!(out, "            var {} = arena.allocateFrom({});", cname, name).ok();
1591        }
1592        TypeRef::Named(type_name) => {
1593            let cname = "c".to_string() + name;
1594            if opaque_types.contains(type_name.as_str()) {
1595                // Opaque handles: pass the inner MemorySegment via .handle()
1596                writeln!(out, "            var {} = {}.handle();", cname, name).ok();
1597            } else {
1598                // Non-opaque named types: serialize to JSON, call _from_json to get FFI pointer.
1599                // The pointer must be freed after the FFI call with _free.
1600                let type_snake = type_name.to_snake_case();
1601                let from_json_handle = format!(
1602                    "NativeLib.{}_{}_FROM_JSON",
1603                    prefix.to_uppercase(),
1604                    type_snake.to_uppercase()
1605                );
1606                let _free_handle = format!("NativeLib.{}_{}_FREE", prefix.to_uppercase(), type_snake.to_uppercase());
1607                writeln!(
1608                    out,
1609                    "            var {}Json = {} != null ? createObjectMapper().writeValueAsString({}) : null;",
1610                    cname, name, name
1611                )
1612                .ok();
1613                writeln!(
1614                    out,
1615                    "            var {}JsonSeg = {}Json != null ? arena.allocateFrom({}Json) : MemorySegment.NULL;",
1616                    cname, cname, cname
1617                )
1618                .ok();
1619                writeln!(out, "            var {} = {}Json != null", cname, cname).ok();
1620                writeln!(
1621                    out,
1622                    "                ? (MemorySegment) {}.invoke({}JsonSeg)",
1623                    from_json_handle, cname
1624                )
1625                .ok();
1626                writeln!(out, "                : MemorySegment.NULL;").ok();
1627            }
1628        }
1629        TypeRef::Optional(inner) => {
1630            // For optional types, marshal the inner type if not null
1631            match inner.as_ref() {
1632                TypeRef::String | TypeRef::Char | TypeRef::Path | TypeRef::Json => {
1633                    let cname = "c".to_string() + name;
1634                    writeln!(
1635                        out,
1636                        "            var {} = {} != null ? arena.allocateFrom({}) : MemorySegment.NULL;",
1637                        cname, name, name
1638                    )
1639                    .ok();
1640                }
1641                TypeRef::Named(type_name) => {
1642                    let cname = "c".to_string() + name;
1643                    if opaque_types.contains(type_name.as_str()) {
1644                        writeln!(
1645                            out,
1646                            "            var {} = {} != null ? {}.handle() : MemorySegment.NULL;",
1647                            cname, name, name
1648                        )
1649                        .ok();
1650                    } else {
1651                        // Non-opaque named type in Optional: serialize to JSON and call _from_json
1652                        let type_snake = type_name.to_snake_case();
1653                        let from_json_handle = format!(
1654                            "NativeLib.{}_{}_FROM_JSON",
1655                            prefix.to_uppercase(),
1656                            type_snake.to_uppercase()
1657                        );
1658                        writeln!(
1659                            out,
1660                            "            var {}Json = {} != null ? createObjectMapper().writeValueAsString({}) : null;",
1661                            cname, name, name
1662                        )
1663                        .ok();
1664                        writeln!(out, "            var {}JsonSeg = {}Json != null ? arena.allocateFrom({}Json) : MemorySegment.NULL;", cname, cname, cname).ok();
1665                        writeln!(out, "            var {} = {}Json != null", cname, cname).ok();
1666                        writeln!(
1667                            out,
1668                            "                ? (MemorySegment) {}.invoke({}JsonSeg)",
1669                            from_json_handle, cname
1670                        )
1671                        .ok();
1672                        writeln!(out, "                : MemorySegment.NULL;").ok();
1673                    }
1674                }
1675                _ => {
1676                    // Other optional types (primitives) pass through
1677                }
1678            }
1679        }
1680        _ => {
1681            // Primitives and others pass through directly
1682        }
1683    }
1684}
1685
1686fn ffi_param_name(name: &str, ty: &TypeRef, _opaque_types: &AHashSet<String>) -> String {
1687    match ty {
1688        TypeRef::String | TypeRef::Char | TypeRef::Path | TypeRef::Json => "c".to_string() + name,
1689        TypeRef::Named(_) => "c".to_string() + name,
1690        TypeRef::Optional(inner) => match inner.as_ref() {
1691            TypeRef::String | TypeRef::Char | TypeRef::Path | TypeRef::Json | TypeRef::Named(_) => {
1692                "c".to_string() + name
1693            }
1694            _ => name.to_string(),
1695        },
1696        _ => name.to_string(),
1697    }
1698}
1699
1700/// Build a `FunctionDescriptor` string for a given return layout and parameter layouts.
1701/// Handles void returns (ofVoid) and non-void returns (of) correctly.
1702fn gen_function_descriptor(return_layout: &str, param_layouts: &[String]) -> String {
1703    if return_layout.is_empty() {
1704        // Void return
1705        if param_layouts.is_empty() {
1706            "FunctionDescriptor.ofVoid()".to_string()
1707        } else {
1708            format!("FunctionDescriptor.ofVoid({})", param_layouts.join(", "))
1709        }
1710    } else {
1711        // Non-void return
1712        if param_layouts.is_empty() {
1713            format!("FunctionDescriptor.of({})", return_layout)
1714        } else {
1715            format!("FunctionDescriptor.of({}, {})", return_layout, param_layouts.join(", "))
1716        }
1717    }
1718}
1719
1720/// Returns true if the given return type maps to an FFI ADDRESS that represents a string
1721/// (i.e. the FFI returns `*mut c_char` which must be unmarshaled and freed).
1722fn is_ffi_string_return(ty: &TypeRef) -> bool {
1723    match ty {
1724        TypeRef::String | TypeRef::Char | TypeRef::Path | TypeRef::Json => true,
1725        TypeRef::Optional(inner) => is_ffi_string_return(inner),
1726        _ => false,
1727    }
1728}
1729
1730/// Returns the appropriate Java cast type for non-string FFI return values.
1731fn java_ffi_return_cast(ty: &TypeRef) -> &'static str {
1732    match ty {
1733        TypeRef::Primitive(prim) => match prim {
1734            PrimitiveType::Bool => "boolean",
1735            PrimitiveType::U8 | PrimitiveType::I8 => "byte",
1736            PrimitiveType::U16 | PrimitiveType::I16 => "short",
1737            PrimitiveType::U32 | PrimitiveType::I32 => "int",
1738            PrimitiveType::U64 | PrimitiveType::I64 | PrimitiveType::Usize | PrimitiveType::Isize => "long",
1739            PrimitiveType::F32 => "float",
1740            PrimitiveType::F64 => "double",
1741        },
1742        TypeRef::Bytes | TypeRef::Vec(_) | TypeRef::Map(_, _) | TypeRef::Named(_) => "MemorySegment",
1743        _ => "MemorySegment",
1744    }
1745}
1746
1747fn gen_helper_methods(out: &mut String) {
1748    // Only emit helper methods that are actually called in the generated body.
1749    let needs_read_cstring = out.contains("readCString(");
1750    let needs_read_bytes = out.contains("readBytes(");
1751    let needs_create_object_mapper = out.contains("createObjectMapper()");
1752
1753    if !needs_read_cstring && !needs_read_bytes && !needs_create_object_mapper {
1754        return;
1755    }
1756
1757    writeln!(out, "    // Helper methods for FFI marshalling").ok();
1758    writeln!(out).ok();
1759
1760    if needs_create_object_mapper {
1761        // Emit a configured ObjectMapper factory:
1762        //   - findAndRegisterModules() to pick up jackson-datatype-jdk8 (Optional support)
1763        //   - ACCEPT_CASE_INSENSITIVE_ENUMS so enum names like "json_ld" match JsonLd, etc.
1764        // Field name mapping relies on explicit @JsonProperty annotations on record components
1765        // (generated by alef for snake_case FFI fields on camelCase Java records).
1766        writeln!(
1767            out,
1768            "    private static com.fasterxml.jackson.databind.ObjectMapper createObjectMapper() {{"
1769        )
1770        .ok();
1771        writeln!(out, "        return new com.fasterxml.jackson.databind.ObjectMapper()").ok();
1772        writeln!(
1773            out,
1774            "            .registerModule(new com.fasterxml.jackson.datatype.jdk8.Jdk8Module())"
1775        )
1776        .ok();
1777        writeln!(out, "            .findAndRegisterModules()").ok();
1778        writeln!(
1779            out,
1780            "            .setSerializationInclusion(com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL)"
1781        )
1782        .ok();
1783        writeln!(
1784            out,
1785            "            .setSerializationInclusion(com.fasterxml.jackson.annotation.JsonInclude.Include.NON_DEFAULT)"
1786        )
1787        .ok();
1788        writeln!(
1789            out,
1790            "            .configure(com.fasterxml.jackson.databind.MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS, true);"
1791        )
1792        .ok();
1793        writeln!(out, "    }}").ok();
1794        writeln!(out).ok();
1795    }
1796
1797    if needs_read_cstring {
1798        writeln!(out, "    private static String readCString(MemorySegment ptr) {{").ok();
1799        writeln!(out, "        if (ptr == null || ptr.address() == 0) {{").ok();
1800        writeln!(out, "            return null;").ok();
1801        writeln!(out, "        }}").ok();
1802        writeln!(out, "        return ptr.getUtf8String(0);").ok();
1803        writeln!(out, "    }}").ok();
1804        writeln!(out).ok();
1805    }
1806
1807    if needs_read_bytes {
1808        writeln!(
1809            out,
1810            "    private static byte[] readBytes(MemorySegment ptr, long len) {{"
1811        )
1812        .ok();
1813        writeln!(out, "        if (ptr == null || ptr.address() == 0) {{").ok();
1814        writeln!(out, "            return new byte[0];").ok();
1815        writeln!(out, "        }}").ok();
1816        writeln!(out, "        byte[] bytes = new byte[(int) len];").ok();
1817        writeln!(
1818            out,
1819            "        MemorySegment.copy(ptr, ValueLayout.JAVA_BYTE.byteSize() * 0, bytes, 0, (int) len);"
1820        )
1821        .ok();
1822        writeln!(out, "        return bytes;").ok();
1823        writeln!(out, "    }}").ok();
1824    }
1825}
1826
1827// ---------------------------------------------------------------------------
1828// Builder class for types with defaults
1829// ---------------------------------------------------------------------------
1830
1831/// Format a default value for an Optional field, wrapping it in Optional.of()
1832/// with proper Java literal syntax.
1833fn format_optional_value(ty: &TypeRef, default: &str) -> String {
1834    // Check if the default is already wrapped (e.g., "Optional.of(...)" or "Optional.empty()")
1835    if default.contains("Optional.") {
1836        return default.to_string();
1837    }
1838
1839    // Unwrap Optional types to get the inner type
1840    let inner_ty = match ty {
1841        TypeRef::Optional(inner) => inner.as_ref(),
1842        other => other,
1843    };
1844
1845    // Determine the proper literal suffix based on type
1846    let formatted_value = match inner_ty {
1847        TypeRef::Primitive(p) => match p {
1848            PrimitiveType::I64 | PrimitiveType::U64 | PrimitiveType::Isize | PrimitiveType::Usize => {
1849                // Add 'L' suffix for long values if not already present
1850                if default.ends_with('L') || default.ends_with('l') {
1851                    default.to_string()
1852                } else if default.parse::<i64>().is_ok() {
1853                    format!("{}L", default)
1854                } else {
1855                    default.to_string()
1856                }
1857            }
1858            PrimitiveType::F32 => {
1859                // Add 'f' suffix for float values if not already present
1860                if default.ends_with('f') || default.ends_with('F') {
1861                    default.to_string()
1862                } else if default.parse::<f32>().is_ok() {
1863                    format!("{}f", default)
1864                } else {
1865                    default.to_string()
1866                }
1867            }
1868            PrimitiveType::F64 => {
1869                // Double defaults can have optional 'd' suffix, but 0.0 is fine
1870                default.to_string()
1871            }
1872            _ => default.to_string(),
1873        },
1874        _ => default.to_string(),
1875    };
1876
1877    format!("Optional.of({})", formatted_value)
1878}
1879
1880fn gen_builder_class(package: &str, typ: &TypeDef) -> String {
1881    let mut body = String::with_capacity(2048);
1882
1883    writeln!(body, "public class {}Builder {{", typ.name).ok();
1884    writeln!(body).ok();
1885
1886    // Generate field declarations with defaults
1887    for field in &typ.fields {
1888        let field_name = safe_java_field_name(&field.name);
1889
1890        // Skip unnamed tuple fields (name is "_0", "_1", "0", "1", etc.) — Java requires named fields
1891        if field.name.starts_with('_') && field.name[1..].chars().all(|c| c.is_ascii_digit())
1892            || field.name.chars().next().is_none_or(|c| c.is_ascii_digit())
1893        {
1894            continue;
1895        }
1896
1897        // Duration maps to primitive `long` in the public record, but in builder
1898        // classes we use boxed `Long` so that `null` can represent "not set".
1899        let field_type = if field.optional {
1900            format!("Optional<{}>", java_boxed_type(&field.ty))
1901        } else if matches!(field.ty, TypeRef::Duration) {
1902            java_boxed_type(&field.ty).to_string()
1903        } else {
1904            java_type(&field.ty).to_string()
1905        };
1906
1907        let default_value = if field.optional {
1908            // For Optional fields, always use Optional.empty() or Optional.of(value)
1909            if let Some(default) = &field.default {
1910                // If there's an explicit default, wrap it in Optional.of()
1911                format_optional_value(&field.ty, default)
1912            } else {
1913                // If no default, use Optional.empty()
1914                "Optional.empty()".to_string()
1915            }
1916        } else {
1917            // For non-Optional fields, use regular defaults
1918            if let Some(default) = &field.default {
1919                default.clone()
1920            } else {
1921                match &field.ty {
1922                    TypeRef::String | TypeRef::Char | TypeRef::Path => "\"\"".to_string(),
1923                    TypeRef::Json => "null".to_string(),
1924                    TypeRef::Bytes => "new byte[0]".to_string(),
1925                    TypeRef::Primitive(p) => match p {
1926                        PrimitiveType::Bool => "false".to_string(),
1927                        PrimitiveType::F32 | PrimitiveType::F64 => "0.0".to_string(),
1928                        _ => "0".to_string(),
1929                    },
1930                    TypeRef::Vec(_) => "List.of()".to_string(),
1931                    TypeRef::Map(_, _) => "Map.of()".to_string(),
1932                    TypeRef::Optional(_) => "Optional.empty()".to_string(),
1933                    TypeRef::Duration => "null".to_string(),
1934                    _ => "null".to_string(),
1935                }
1936            }
1937        };
1938
1939        writeln!(body, "    private {} {} = {};", field_type, field_name, default_value).ok();
1940    }
1941
1942    writeln!(body).ok();
1943
1944    // Generate withXxx() methods
1945    for field in &typ.fields {
1946        // Skip unnamed tuple fields (name is "_0", "_1", "0", "1", etc.) — Java requires named fields
1947        if field.name.starts_with('_') && field.name[1..].chars().all(|c| c.is_ascii_digit())
1948            || field.name.chars().next().is_none_or(|c| c.is_ascii_digit())
1949        {
1950            continue;
1951        }
1952
1953        let field_name = safe_java_field_name(&field.name);
1954        let field_name_pascal = to_class_name(&field.name);
1955        let field_type = if field.optional {
1956            format!("Optional<{}>", java_boxed_type(&field.ty))
1957        } else if matches!(field.ty, TypeRef::Duration) {
1958            java_boxed_type(&field.ty).to_string()
1959        } else {
1960            java_type(&field.ty).to_string()
1961        };
1962
1963        writeln!(
1964            body,
1965            "    public {}Builder with{}({} value) {{",
1966            typ.name, field_name_pascal, field_type
1967        )
1968        .ok();
1969        writeln!(body, "        this.{} = value;", field_name).ok();
1970        writeln!(body, "        return this;").ok();
1971        writeln!(body, "    }}").ok();
1972        writeln!(body).ok();
1973    }
1974
1975    // Generate build() method
1976    writeln!(body, "    public {} build() {{", typ.name).ok();
1977    writeln!(body, "        return new {}(", typ.name).ok();
1978    let non_tuple_fields: Vec<_> = typ
1979        .fields
1980        .iter()
1981        .filter(|f| {
1982            // Include named fields (skip unnamed tuple fields)
1983            !(f.name.starts_with('_') && f.name[1..].chars().all(|c| c.is_ascii_digit())
1984                || f.name.chars().next().is_none_or(|c| c.is_ascii_digit()))
1985        })
1986        .collect();
1987    for (i, field) in non_tuple_fields.iter().enumerate() {
1988        let field_name = safe_java_field_name(&field.name);
1989        let comma = if i < non_tuple_fields.len() - 1 { "," } else { "" };
1990        writeln!(body, "            {}{}", field_name, comma).ok();
1991    }
1992    writeln!(body, "        );").ok();
1993    writeln!(body, "    }}").ok();
1994
1995    writeln!(body, "}}").ok();
1996
1997    // Now assemble with conditional imports based on what's actually used in the body
1998    let mut out = String::with_capacity(body.len() + 512);
1999
2000    writeln!(out, "// DO NOT EDIT - auto-generated by alef").ok();
2001    writeln!(out, "package {};", package).ok();
2002    writeln!(out).ok();
2003
2004    if body.contains("List<") {
2005        writeln!(out, "import java.util.List;").ok();
2006    }
2007    if body.contains("Map<") {
2008        writeln!(out, "import java.util.Map;").ok();
2009    }
2010    if body.contains("Optional<") {
2011        writeln!(out, "import java.util.Optional;").ok();
2012    }
2013
2014    writeln!(out).ok();
2015    out.push_str(&body);
2016
2017    out
2018}