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
11const 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
25fn is_tuple_field_name(name: &str) -> bool {
29 let stripped = name.trim_start_matches('_');
30 !stripped.is_empty() && stripped.chars().all(|c| c.is_ascii_digit())
31}
32
33fn safe_java_field_name(name: &str) -> String {
37 let java_name = to_java_name(name);
38 if JAVA_OBJECT_METHOD_NAMES.contains(&java_name.as_str()) {
39 format!("{}Value", java_name)
40 } else {
41 java_name
42 }
43}
44
45pub struct JavaBackend;
46
47impl JavaBackend {
48 fn resolve_main_class(api: &ApiSurface) -> String {
54 let base = to_class_name(&api.crate_name.replace('-', "_"));
55 if base.ends_with("Rs") {
56 base
57 } else {
58 format!("{}Rs", base)
59 }
60 }
61}
62
63impl Backend for JavaBackend {
64 fn name(&self) -> &str {
65 "java"
66 }
67
68 fn language(&self) -> Language {
69 Language::Java
70 }
71
72 fn capabilities(&self) -> Capabilities {
73 Capabilities {
74 supports_async: true,
75 supports_classes: true,
76 supports_enums: true,
77 supports_option: true,
78 supports_result: true,
79 ..Capabilities::default()
80 }
81 }
82
83 fn generate_bindings(&self, api: &ApiSurface, config: &AlefConfig) -> anyhow::Result<Vec<GeneratedFile>> {
84 let package = config.java_package();
85 let prefix = config.ffi_prefix();
86 let main_class = Self::resolve_main_class(api);
87 let package_path = package.replace('.', "/");
88
89 let output_dir = resolve_output_dir(
90 config.output.java.as_ref(),
91 &config.crate_config.name,
92 "packages/java/src/main/java/",
93 );
94
95 let base_path = PathBuf::from(&output_dir).join(&package_path);
96
97 let mut files = Vec::new();
98
99 let description = config
101 .scaffold
102 .as_ref()
103 .and_then(|s| s.description.as_deref())
104 .unwrap_or("High-performance HTML to Markdown converter.");
105 files.push(GeneratedFile {
106 path: base_path.join("package-info.java"),
107 content: format!(
108 "/**\n * {description}\n */\npackage {package};\n",
109 description = description,
110 package = package,
111 ),
112 generated_header: true,
113 });
114
115 files.push(GeneratedFile {
117 path: base_path.join("NativeLib.java"),
118 content: gen_native_lib(api, config, &package, &prefix),
119 generated_header: true,
120 });
121
122 files.push(GeneratedFile {
124 path: base_path.join(format!("{}.java", main_class)),
125 content: gen_main_class(api, config, &package, &main_class, &prefix),
126 generated_header: true,
127 });
128
129 files.push(GeneratedFile {
131 path: base_path.join(format!("{}Exception.java", main_class)),
132 content: gen_exception_class(&package, &main_class),
133 generated_header: true,
134 });
135
136 let complex_enums: AHashSet<String> = api
140 .enums
141 .iter()
142 .filter(|e| e.serde_tag.is_none() && e.variants.iter().any(|v| !v.fields.is_empty()))
143 .map(|e| e.name.clone())
144 .collect();
145
146 let lang_rename_all = config.serde_rename_all_for_language(Language::Java);
148
149 for typ in api.types.iter().filter(|typ| !typ.is_trait) {
151 if !typ.is_opaque && !typ.fields.is_empty() {
152 files.push(GeneratedFile {
153 path: base_path.join(format!("{}.java", typ.name)),
154 content: gen_record_type(&package, typ, &complex_enums, &lang_rename_all),
155 generated_header: true,
156 });
157 if typ.has_default {
159 files.push(GeneratedFile {
160 path: base_path.join(format!("{}Builder.java", typ.name)),
161 content: gen_builder_class(&package, typ),
162 generated_header: true,
163 });
164 }
165 }
166 }
167
168 let builder_class_names: AHashSet<String> = api
171 .types
172 .iter()
173 .filter(|t| !t.is_opaque && !t.fields.is_empty() && t.has_default)
174 .map(|t| format!("{}Builder", t.name))
175 .collect();
176
177 for typ in api.types.iter().filter(|typ| !typ.is_trait) {
179 if typ.is_opaque && !builder_class_names.contains(&typ.name) {
180 files.push(GeneratedFile {
181 path: base_path.join(format!("{}.java", typ.name)),
182 content: gen_opaque_handle_class(&package, typ, &prefix),
183 generated_header: true,
184 });
185 }
186 }
187
188 for enum_def in &api.enums {
190 files.push(GeneratedFile {
191 path: base_path.join(format!("{}.java", enum_def.name)),
192 content: gen_enum_class(&package, enum_def),
193 generated_header: true,
194 });
195 }
196
197 for error in &api.errors {
199 for (class_name, content) in alef_codegen::error_gen::gen_java_error_types(error, &package) {
200 files.push(GeneratedFile {
201 path: base_path.join(format!("{}.java", class_name)),
202 content,
203 generated_header: true,
204 });
205 }
206 }
207
208 let _adapter_bodies = alef_adapters::build_adapter_bodies(config, Language::Java)?;
210
211 Ok(files)
212 }
213
214 fn generate_public_api(&self, api: &ApiSurface, config: &AlefConfig) -> anyhow::Result<Vec<GeneratedFile>> {
215 let package = config.java_package();
216 let prefix = config.ffi_prefix();
217 let main_class = Self::resolve_main_class(api);
218 let package_path = package.replace('.', "/");
219
220 let output_dir = resolve_output_dir(
221 config.output.java.as_ref(),
222 &config.crate_config.name,
223 "packages/java/src/main/java/",
224 );
225
226 let base_path = PathBuf::from(&output_dir).join(&package_path);
227
228 let public_class = main_class.trim_end_matches("Rs").to_string();
231 let facade_content = gen_facade_class(api, &package, &public_class, &main_class, &prefix);
232
233 Ok(vec![GeneratedFile {
234 path: base_path.join(format!("{}.java", public_class)),
235 content: facade_content,
236 generated_header: true,
237 }])
238 }
239
240 fn build_config(&self) -> Option<BuildConfig> {
241 Some(BuildConfig {
242 tool: "mvn",
243 crate_suffix: "",
244 depends_on_ffi: true,
245 post_build: vec![],
246 })
247 }
248}
249
250fn gen_native_lib(api: &ApiSurface, config: &AlefConfig, package: &str, prefix: &str) -> String {
255 let mut body = String::with_capacity(2048);
257 let lib_name = config.ffi_lib_name();
260
261 writeln!(body, "final class NativeLib {{").ok();
262 writeln!(body, " private static final Linker LINKER = Linker.nativeLinker();").ok();
263 writeln!(body, " private static final SymbolLookup LIB;").ok();
264 writeln!(
265 body,
266 " private static final String NATIVES_RESOURCE_ROOT = \"/natives\";"
267 )
268 .ok();
269 writeln!(
270 body,
271 " private static final Object NATIVE_EXTRACT_LOCK = new Object();"
272 )
273 .ok();
274 writeln!(body, " private static String cachedExtractKey;").ok();
275 writeln!(body, " private static Path cachedExtractDir;").ok();
276 writeln!(body).ok();
277 writeln!(body, " static {{").ok();
278 writeln!(body, " loadNativeLibrary();").ok();
279 writeln!(body, " LIB = SymbolLookup.loaderLookup();").ok();
280 writeln!(body, " }}").ok();
281 writeln!(body).ok();
282 writeln!(body, " private static void loadNativeLibrary() {{").ok();
283 writeln!(
284 body,
285 " String osName = System.getProperty(\"os.name\", \"\").toLowerCase(java.util.Locale.ROOT);"
286 )
287 .ok();
288 writeln!(
289 body,
290 " String osArch = System.getProperty(\"os.arch\", \"\").toLowerCase(java.util.Locale.ROOT);"
291 )
292 .ok();
293 writeln!(body).ok();
294 writeln!(body, " String libName;").ok();
295 writeln!(body, " String libExt;").ok();
296 writeln!(
297 body,
298 " if (osName.contains(\"mac\") || osName.contains(\"darwin\")) {{"
299 )
300 .ok();
301 writeln!(body, " libName = \"lib{}\";", lib_name).ok();
302 writeln!(body, " libExt = \".dylib\";").ok();
303 writeln!(body, " }} else if (osName.contains(\"win\")) {{").ok();
304 writeln!(body, " libName = \"{}\";", lib_name).ok();
305 writeln!(body, " libExt = \".dll\";").ok();
306 writeln!(body, " }} else {{").ok();
307 writeln!(body, " libName = \"lib{}\";", lib_name).ok();
308 writeln!(body, " libExt = \".so\";").ok();
309 writeln!(body, " }}").ok();
310 writeln!(body).ok();
311 writeln!(body, " String nativesRid = resolveNativesRid(osName, osArch);").ok();
312 writeln!(
313 body,
314 " String nativesDir = NATIVES_RESOURCE_ROOT + \"/\" + nativesRid;"
315 )
316 .ok();
317 writeln!(body).ok();
318 writeln!(
319 body,
320 " Path extracted = tryExtractAndLoadFromResources(nativesDir, libName, libExt);"
321 )
322 .ok();
323 writeln!(body, " if (extracted != null) {{").ok();
324 writeln!(body, " return;").ok();
325 writeln!(body, " }}").ok();
326 writeln!(body).ok();
327 writeln!(body, " try {{").ok();
328 writeln!(body, " System.loadLibrary(\"{}\");", lib_name).ok();
329 writeln!(body, " }} catch (UnsatisfiedLinkError e) {{").ok();
330 writeln!(
331 body,
332 " String msg = \"Failed to load {} native library. Expected resource: \" + nativesDir + \"/\" + libName",
333 lib_name
334 ).ok();
335 writeln!(
336 body,
337 " + libExt + \" (RID: \" + nativesRid + \"). \""
338 )
339 .ok();
340 writeln!(
341 body,
342 " + \"Ensure the library is bundled in the JAR under natives/{{os-arch}}/, \""
343 )
344 .ok();
345 writeln!(
346 body,
347 " + \"or place it on the system library path (java.library.path).\";",
348 )
349 .ok();
350 writeln!(
351 body,
352 " UnsatisfiedLinkError out = new UnsatisfiedLinkError(msg + \" Original error: \" + e.getMessage());"
353 )
354 .ok();
355 writeln!(body, " out.initCause(e);").ok();
356 writeln!(body, " throw out;").ok();
357 writeln!(body, " }}").ok();
358 writeln!(body, " }}").ok();
359 writeln!(body).ok();
360 writeln!(
361 body,
362 " private static Path tryExtractAndLoadFromResources(String nativesDir, String libName, String libExt) {{"
363 )
364 .ok();
365 writeln!(
366 body,
367 " String resourcePath = nativesDir + \"/\" + libName + libExt;"
368 )
369 .ok();
370 writeln!(
371 body,
372 " URL resource = NativeLib.class.getResource(resourcePath);"
373 )
374 .ok();
375 writeln!(body, " if (resource == null) {{").ok();
376 writeln!(body, " return null;").ok();
377 writeln!(body, " }}").ok();
378 writeln!(body).ok();
379 writeln!(body, " try {{").ok();
380 writeln!(
381 body,
382 " Path tempDir = extractOrReuseNativeDirectory(nativesDir);"
383 )
384 .ok();
385 writeln!(body, " Path libPath = tempDir.resolve(libName + libExt);").ok();
386 writeln!(body, " if (!Files.exists(libPath)) {{").ok();
387 writeln!(
388 body,
389 " throw new UnsatisfiedLinkError(\"Missing extracted native library: \" + libPath);"
390 )
391 .ok();
392 writeln!(body, " }}").ok();
393 writeln!(body, " System.load(libPath.toAbsolutePath().toString());").ok();
394 writeln!(body, " return libPath;").ok();
395 writeln!(body, " }} catch (Exception e) {{").ok();
396 writeln!(body, " System.err.println(\"[NativeLib] Failed to extract and load native library from resources: \" + e.getMessage());").ok();
397 writeln!(body, " return null;").ok();
398 writeln!(body, " }}").ok();
399 writeln!(body, " }}").ok();
400 writeln!(body).ok();
401 writeln!(
402 body,
403 " private static Path extractOrReuseNativeDirectory(String nativesDir) throws Exception {{"
404 )
405 .ok();
406 writeln!(
407 body,
408 " URL location = NativeLib.class.getProtectionDomain().getCodeSource().getLocation();"
409 )
410 .ok();
411 writeln!(body, " if (location == null) {{").ok();
412 writeln!(
413 body,
414 " throw new IllegalStateException(\"Missing code source location for {} JAR\");",
415 lib_name
416 )
417 .ok();
418 writeln!(body, " }}").ok();
419 writeln!(body).ok();
420 writeln!(body, " Path codePath = Path.of(location.toURI());").ok();
421 writeln!(
422 body,
423 " String key = codePath.toAbsolutePath() + \"::\" + nativesDir;"
424 )
425 .ok();
426 writeln!(body).ok();
427 writeln!(body, " synchronized (NATIVE_EXTRACT_LOCK) {{").ok();
428 writeln!(
429 body,
430 " if (cachedExtractDir != null && key.equals(cachedExtractKey)) {{"
431 )
432 .ok();
433 writeln!(body, " return cachedExtractDir;").ok();
434 writeln!(body, " }}").ok();
435 writeln!(
436 body,
437 " Path tempDir = Files.createTempDirectory(\"{}_native\");",
438 lib_name
439 )
440 .ok();
441 writeln!(body, " tempDir.toFile().deleteOnExit();").ok();
442 writeln!(
443 body,
444 " List<Path> extracted = extractNativeDirectory(codePath, nativesDir, tempDir);"
445 )
446 .ok();
447 writeln!(body, " if (extracted.isEmpty()) {{").ok();
448 writeln!(body, " throw new IllegalStateException(\"No native files extracted from resources dir: \" + nativesDir);").ok();
449 writeln!(body, " }}").ok();
450 writeln!(body, " cachedExtractKey = key;").ok();
451 writeln!(body, " cachedExtractDir = tempDir;").ok();
452 writeln!(body, " return tempDir;").ok();
453 writeln!(body, " }}").ok();
454 writeln!(body, " }}").ok();
455 writeln!(body).ok();
456 writeln!(body, " private static List<Path> extractNativeDirectory(Path codePath, String nativesDir, Path destDir) throws Exception {{").ok();
457 writeln!(
458 body,
459 " if (!Files.exists(destDir) || !Files.isDirectory(destDir)) {{"
460 )
461 .ok();
462 writeln!(
463 body,
464 " throw new IllegalArgumentException(\"Destination directory does not exist: \" + destDir);"
465 )
466 .ok();
467 writeln!(body, " }}").ok();
468 writeln!(body).ok();
469 writeln!(
470 body,
471 " String prefix = nativesDir.startsWith(\"/\") ? nativesDir.substring(1) : nativesDir;"
472 )
473 .ok();
474 writeln!(body, " if (!prefix.endsWith(\"/\")) {{").ok();
475 writeln!(body, " prefix = prefix + \"/\";").ok();
476 writeln!(body, " }}").ok();
477 writeln!(body).ok();
478 writeln!(body, " if (Files.isDirectory(codePath)) {{").ok();
479 writeln!(body, " Path nativesPath = codePath.resolve(prefix);").ok();
480 writeln!(
481 body,
482 " if (!Files.exists(nativesPath) || !Files.isDirectory(nativesPath)) {{"
483 )
484 .ok();
485 writeln!(body, " return List.of();").ok();
486 writeln!(body, " }}").ok();
487 writeln!(body, " return copyDirectory(nativesPath, destDir);").ok();
488 writeln!(body, " }}").ok();
489 writeln!(body).ok();
490 writeln!(body, " List<Path> extracted = new ArrayList<>();").ok();
491 writeln!(body, " try (JarFile jar = new JarFile(codePath.toFile())) {{").ok();
492 writeln!(body, " Enumeration<JarEntry> entries = jar.entries();").ok();
493 writeln!(body, " while (entries.hasMoreElements()) {{").ok();
494 writeln!(body, " JarEntry entry = entries.nextElement();").ok();
495 writeln!(body, " String name = entry.getName();").ok();
496 writeln!(
497 body,
498 " if (!name.startsWith(prefix) || entry.isDirectory()) {{"
499 )
500 .ok();
501 writeln!(body, " continue;").ok();
502 writeln!(body, " }}").ok();
503 writeln!(
504 body,
505 " String relative = name.substring(prefix.length());"
506 )
507 .ok();
508 writeln!(body, " Path out = safeResolve(destDir, relative);").ok();
509 writeln!(body, " Files.createDirectories(out.getParent());").ok();
510 writeln!(body, " try (var in = jar.getInputStream(entry)) {{").ok();
511 writeln!(
512 body,
513 " Files.copy(in, out, StandardCopyOption.REPLACE_EXISTING);"
514 )
515 .ok();
516 writeln!(body, " }}").ok();
517 writeln!(body, " out.toFile().deleteOnExit();").ok();
518 writeln!(body, " extracted.add(out);").ok();
519 writeln!(body, " }}").ok();
520 writeln!(body, " }}").ok();
521 writeln!(body, " return extracted;").ok();
522 writeln!(body, " }}").ok();
523 writeln!(body).ok();
524 writeln!(
525 body,
526 " private static List<Path> copyDirectory(Path srcDir, Path destDir) throws Exception {{"
527 )
528 .ok();
529 writeln!(body, " List<Path> copied = new ArrayList<>();").ok();
530 writeln!(body, " try (var paths = Files.walk(srcDir)) {{").ok();
531 writeln!(body, " for (Path src : (Iterable<Path>) paths::iterator) {{").ok();
532 writeln!(body, " if (Files.isDirectory(src)) {{").ok();
533 writeln!(body, " continue;").ok();
534 writeln!(body, " }}").ok();
535 writeln!(body, " Path relative = srcDir.relativize(src);").ok();
536 writeln!(
537 body,
538 " Path out = safeResolve(destDir, relative.toString());"
539 )
540 .ok();
541 writeln!(body, " Files.createDirectories(out.getParent());").ok();
542 writeln!(
543 body,
544 " Files.copy(src, out, StandardCopyOption.REPLACE_EXISTING);"
545 )
546 .ok();
547 writeln!(body, " out.toFile().deleteOnExit();").ok();
548 writeln!(body, " copied.add(out);").ok();
549 writeln!(body, " }}").ok();
550 writeln!(body, " }}").ok();
551 writeln!(body, " return copied;").ok();
552 writeln!(body, " }}").ok();
553 writeln!(body).ok();
554 writeln!(
555 body,
556 " private static Path safeResolve(Path destDir, String relative) throws Exception {{"
557 )
558 .ok();
559 writeln!(
560 body,
561 " Path normalizedDest = destDir.toAbsolutePath().normalize();"
562 )
563 .ok();
564 writeln!(body, " Path out = normalizedDest.resolve(relative).normalize();").ok();
565 writeln!(body, " if (!out.startsWith(normalizedDest)) {{").ok();
566 writeln!(body, " throw new SecurityException(\"Blocked extracting native file outside destination directory: \" + relative);").ok();
567 writeln!(body, " }}").ok();
568 writeln!(body, " return out;").ok();
569 writeln!(body, " }}").ok();
570 writeln!(body).ok();
571 writeln!(
572 body,
573 " private static String resolveNativesRid(String osName, String osArch) {{"
574 )
575 .ok();
576 writeln!(body, " String arch;").ok();
577 writeln!(
578 body,
579 " if (osArch.contains(\"aarch64\") || osArch.contains(\"arm64\")) {{"
580 )
581 .ok();
582 writeln!(body, " arch = \"arm64\";").ok();
583 writeln!(
584 body,
585 " }} else if (osArch.contains(\"x86_64\") || osArch.contains(\"amd64\")) {{"
586 )
587 .ok();
588 writeln!(body, " arch = \"x86_64\";").ok();
589 writeln!(body, " }} else {{").ok();
590 writeln!(body, " arch = osArch.replaceAll(\"[^a-z0-9_]+\", \"\");").ok();
591 writeln!(body, " }}").ok();
592 writeln!(body).ok();
593 writeln!(body, " String os;").ok();
594 writeln!(
595 body,
596 " if (osName.contains(\"mac\") || osName.contains(\"darwin\")) {{"
597 )
598 .ok();
599 writeln!(body, " os = \"macos\";").ok();
600 writeln!(body, " }} else if (osName.contains(\"win\")) {{").ok();
601 writeln!(body, " os = \"windows\";").ok();
602 writeln!(body, " }} else {{").ok();
603 writeln!(body, " os = \"linux\";").ok();
604 writeln!(body, " }}").ok();
605 writeln!(body).ok();
606 writeln!(body, " return os + \"-\" + arch;").ok();
607 writeln!(body, " }}").ok();
608 writeln!(body).ok();
609
610 for func in &api.functions {
614 let ffi_name = format!("{}_{}", prefix, func.name.to_lowercase());
615 let return_layout = gen_ffi_layout(&func.return_type);
616 let param_layouts: Vec<String> = func.params.iter().map(|p| gen_ffi_layout(&p.ty)).collect();
617
618 let layout_str = gen_function_descriptor(&return_layout, ¶m_layouts);
619
620 let handle_name = format!("{}_{}", prefix.to_uppercase(), func.name.to_uppercase());
621
622 writeln!(
623 body,
624 " static final MethodHandle {} = LINKER.downcallHandle(",
625 handle_name
626 )
627 .ok();
628 writeln!(body, " LIB.find(\"{}\").orElseThrow(),", ffi_name).ok();
629 writeln!(body, " {}", layout_str).ok();
630 writeln!(body, " );").ok();
631 }
632
633 {
635 let free_name = format!("{}_free_string", prefix);
636 let handle_name = format!("{}_FREE_STRING", prefix.to_uppercase());
637 writeln!(body).ok();
638 writeln!(
639 body,
640 " static final MethodHandle {} = LINKER.downcallHandle(",
641 handle_name
642 )
643 .ok();
644 writeln!(body, " LIB.find(\"{}\").orElseThrow(),", free_name).ok();
645 writeln!(body, " FunctionDescriptor.ofVoid(ValueLayout.ADDRESS)").ok();
646 writeln!(body, " );").ok();
647 }
648
649 {
651 writeln!(
652 body,
653 " static final MethodHandle {}_LAST_ERROR_CODE = LINKER.downcallHandle(",
654 prefix.to_uppercase()
655 )
656 .ok();
657 writeln!(body, " LIB.find(\"{}_last_error_code\").orElseThrow(),", prefix).ok();
658 writeln!(body, " FunctionDescriptor.of(ValueLayout.JAVA_INT)").ok();
659 writeln!(body, " );").ok();
660
661 writeln!(
662 body,
663 " static final MethodHandle {}_LAST_ERROR_CONTEXT = LINKER.downcallHandle(",
664 prefix.to_uppercase()
665 )
666 .ok();
667 writeln!(
668 body,
669 " LIB.find(\"{}_last_error_context\").orElseThrow(),",
670 prefix
671 )
672 .ok();
673 writeln!(body, " FunctionDescriptor.of(ValueLayout.ADDRESS)").ok();
674 writeln!(body, " );").ok();
675 }
676
677 let mut emitted_free_handles: AHashSet<String> = AHashSet::new();
680
681 let opaque_type_names: AHashSet<String> = api
683 .types
684 .iter()
685 .filter(|t| t.is_opaque)
686 .map(|t| t.name.clone())
687 .collect();
688
689 for func in &api.functions {
691 if let TypeRef::Named(name) = &func.return_type {
692 let type_snake = name.to_snake_case();
693 let type_upper = type_snake.to_uppercase();
694 let is_opaque = opaque_type_names.contains(name.as_str());
695
696 if is_opaque {
697 } else {
700 let to_json_handle = format!("{}_{}_TO_JSON", prefix.to_uppercase(), type_upper);
704 let to_json_ffi = format!("{}_{}_to_json", prefix, type_snake);
705 writeln!(body).ok();
706 writeln!(
707 body,
708 " static final MethodHandle {} = LINKER.downcallHandle(",
709 to_json_handle
710 )
711 .ok();
712 writeln!(body, " LIB.find(\"{}\").orElseThrow(),", to_json_ffi).ok();
713 writeln!(
714 body,
715 " FunctionDescriptor.of(ValueLayout.ADDRESS, ValueLayout.ADDRESS)"
716 )
717 .ok();
718 writeln!(body, " );").ok();
719 }
720
721 let free_handle = format!("{}_{}_FREE", prefix.to_uppercase(), type_upper);
723 let free_ffi = format!("{}_{}_free", prefix, type_snake);
724 if emitted_free_handles.insert(free_handle.clone()) {
725 writeln!(body).ok();
726 writeln!(
727 body,
728 " static final MethodHandle {} = LINKER.downcallHandle(",
729 free_handle
730 )
731 .ok();
732 writeln!(body, " LIB.find(\"{}\").orElseThrow(),", free_ffi).ok();
733 writeln!(body, " FunctionDescriptor.ofVoid(ValueLayout.ADDRESS)").ok();
734 writeln!(body, " );").ok();
735 }
736 }
737 }
738
739 let mut emitted_from_json_handles: AHashSet<String> = AHashSet::new();
742 for func in &api.functions {
743 for param in &func.params {
744 let inner_name = match ¶m.ty {
746 TypeRef::Named(n) => Some(n.clone()),
747 TypeRef::Optional(inner) => {
748 if let TypeRef::Named(n) = inner.as_ref() {
749 Some(n.clone())
750 } else {
751 None
752 }
753 }
754 _ => None,
755 };
756 if let Some(name) = inner_name {
757 if !opaque_type_names.contains(name.as_str()) {
758 let type_snake = name.to_snake_case();
759 let type_upper = type_snake.to_uppercase();
760
761 let from_json_handle = format!("{}_{}_FROM_JSON", prefix.to_uppercase(), type_upper);
763 let from_json_ffi = format!("{}_{}_from_json", prefix, type_snake);
764 if emitted_from_json_handles.insert(from_json_handle.clone()) {
765 writeln!(body).ok();
766 writeln!(
767 body,
768 " static final MethodHandle {} = LINKER.downcallHandle(",
769 from_json_handle
770 )
771 .ok();
772 writeln!(body, " LIB.find(\"{}\").orElseThrow(),", from_json_ffi).ok();
773 writeln!(
774 body,
775 " FunctionDescriptor.of(ValueLayout.ADDRESS, ValueLayout.ADDRESS)"
776 )
777 .ok();
778 writeln!(body, " );").ok();
779 }
780
781 let free_handle = format!("{}_{}_FREE", prefix.to_uppercase(), type_upper);
783 let free_ffi = format!("{}_{}_free", prefix, type_snake);
784 if emitted_free_handles.insert(free_handle.clone()) {
785 writeln!(body).ok();
786 writeln!(
787 body,
788 " static final MethodHandle {} = LINKER.downcallHandle(",
789 free_handle
790 )
791 .ok();
792 writeln!(body, " LIB.find(\"{}\").orElseThrow(),", free_ffi).ok();
793 writeln!(body, " FunctionDescriptor.ofVoid(ValueLayout.ADDRESS)").ok();
794 writeln!(body, " );").ok();
795 }
796 }
797 }
798 }
799 }
800
801 let builder_class_names: AHashSet<String> = api
804 .types
805 .iter()
806 .filter(|t| !t.is_opaque && !t.fields.is_empty() && t.has_default)
807 .map(|t| format!("{}Builder", t.name))
808 .collect();
809
810 for typ in api.types.iter().filter(|typ| !typ.is_trait) {
812 if typ.is_opaque && !builder_class_names.contains(&typ.name) {
813 let type_snake = typ.name.to_snake_case();
814 let type_upper = type_snake.to_uppercase();
815 let free_handle = format!("{}_{}_FREE", prefix.to_uppercase(), type_upper);
816 let free_ffi = format!("{}_{}_free", prefix, type_snake);
817 if emitted_free_handles.insert(free_handle.clone()) {
818 writeln!(body).ok();
819 writeln!(
820 body,
821 " static final MethodHandle {} = LINKER.downcallHandle(",
822 free_handle
823 )
824 .ok();
825 writeln!(body, " LIB.find(\"{}\").orElseThrow(),", free_ffi).ok();
826 writeln!(body, " FunctionDescriptor.ofVoid(ValueLayout.ADDRESS)").ok();
827 writeln!(body, " );").ok();
828 }
829 }
830 }
831
832 writeln!(body, "}}").ok();
833
834 let mut out = String::with_capacity(body.len() + 512);
836
837 writeln!(out, "// DO NOT EDIT - auto-generated by alef").ok();
838 writeln!(out, "package {};", package).ok();
839 writeln!(out).ok();
840 if body.contains("Arena") {
841 writeln!(out, "import java.lang.foreign.Arena;").ok();
842 }
843 if body.contains("FunctionDescriptor") {
844 writeln!(out, "import java.lang.foreign.FunctionDescriptor;").ok();
845 }
846 if body.contains("Linker") {
847 writeln!(out, "import java.lang.foreign.Linker;").ok();
848 }
849 if body.contains("MemorySegment") {
850 writeln!(out, "import java.lang.foreign.MemorySegment;").ok();
851 }
852 if body.contains("SymbolLookup") {
853 writeln!(out, "import java.lang.foreign.SymbolLookup;").ok();
854 }
855 if body.contains("ValueLayout") {
856 writeln!(out, "import java.lang.foreign.ValueLayout;").ok();
857 }
858 if body.contains("MethodHandle") {
859 writeln!(out, "import java.lang.invoke.MethodHandle;").ok();
860 }
861 writeln!(out, "import java.net.URL;").ok();
863 writeln!(out, "import java.nio.file.Files;").ok();
864 writeln!(out, "import java.nio.file.Path;").ok();
865 writeln!(out, "import java.nio.file.StandardCopyOption;").ok();
866 writeln!(out, "import java.util.ArrayList;").ok();
867 writeln!(out, "import java.util.Enumeration;").ok();
868 writeln!(out, "import java.util.List;").ok();
869 writeln!(out, "import java.util.jar.JarEntry;").ok();
870 writeln!(out, "import java.util.jar.JarFile;").ok();
871 writeln!(out).ok();
872
873 out.push_str(&body);
874
875 out
876}
877
878fn gen_main_class(api: &ApiSurface, _config: &AlefConfig, package: &str, class_name: &str, prefix: &str) -> String {
883 let opaque_types: AHashSet<String> = api
885 .types
886 .iter()
887 .filter(|t| t.is_opaque)
888 .map(|t| t.name.clone())
889 .collect();
890
891 let mut body = String::with_capacity(4096);
893
894 writeln!(body, "public final class {} {{", class_name).ok();
895 writeln!(body, " private {}() {{ }}", class_name).ok();
896 writeln!(body).ok();
897
898 for func in &api.functions {
900 gen_sync_function_method(&mut body, func, prefix, class_name, &opaque_types);
902 writeln!(body).ok();
903
904 if func.is_async {
906 gen_async_wrapper_method(&mut body, func);
907 writeln!(body).ok();
908 }
909 }
910
911 gen_helper_methods(&mut body, prefix, class_name);
913
914 writeln!(body, "}}").ok();
915
916 let mut out = String::with_capacity(body.len() + 512);
918
919 writeln!(out, "// DO NOT EDIT - auto-generated by alef").ok();
920 writeln!(out, "package {};", package).ok();
921 writeln!(out).ok();
922 if body.contains("Arena") {
923 writeln!(out, "import java.lang.foreign.Arena;").ok();
924 }
925 if body.contains("FunctionDescriptor") {
926 writeln!(out, "import java.lang.foreign.FunctionDescriptor;").ok();
927 }
928 if body.contains("Linker") {
929 writeln!(out, "import java.lang.foreign.Linker;").ok();
930 }
931 if body.contains("MemorySegment") {
932 writeln!(out, "import java.lang.foreign.MemorySegment;").ok();
933 }
934 if body.contains("SymbolLookup") {
935 writeln!(out, "import java.lang.foreign.SymbolLookup;").ok();
936 }
937 if body.contains("ValueLayout") {
938 writeln!(out, "import java.lang.foreign.ValueLayout;").ok();
939 }
940 if body.contains("List<") {
941 writeln!(out, "import java.util.List;").ok();
942 }
943 if body.contains("Map<") {
944 writeln!(out, "import java.util.Map;").ok();
945 }
946 if body.contains("Optional<") {
947 writeln!(out, "import java.util.Optional;").ok();
948 }
949 if body.contains("HashMap<") || body.contains("new HashMap") {
950 writeln!(out, "import java.util.HashMap;").ok();
951 }
952 if body.contains("CompletableFuture") {
953 writeln!(out, "import java.util.concurrent.CompletableFuture;").ok();
954 }
955 if body.contains("CompletionException") {
956 writeln!(out, "import java.util.concurrent.CompletionException;").ok();
957 }
958 if body.contains(" ObjectMapper") {
962 writeln!(out, "import com.fasterxml.jackson.databind.ObjectMapper;").ok();
963 }
964 writeln!(out).ok();
965
966 out.push_str(&body);
967
968 out
969}
970
971fn gen_sync_function_method(
972 out: &mut String,
973 func: &FunctionDef,
974 prefix: &str,
975 class_name: &str,
976 opaque_types: &AHashSet<String>,
977) {
978 let params: Vec<String> = func
979 .params
980 .iter()
981 .map(|p| {
982 let ptype = java_type(&p.ty);
983 format!("{} {}", ptype, to_java_name(&p.name))
984 })
985 .collect();
986
987 let return_type = java_type(&func.return_type);
988
989 writeln!(
990 out,
991 " public static {} {}({}) throws {}Exception {{",
992 return_type,
993 to_java_name(&func.name),
994 params.join(", "),
995 class_name
996 )
997 .ok();
998
999 writeln!(out, " try (var arena = Arena.ofConfined()) {{").ok();
1000
1001 let ffi_ptr_params: Vec<(String, String)> = func
1004 .params
1005 .iter()
1006 .filter_map(|p| {
1007 let inner_name = match &p.ty {
1008 TypeRef::Named(n) if !opaque_types.contains(n.as_str()) => Some(n.clone()),
1009 TypeRef::Optional(inner) => {
1010 if let TypeRef::Named(n) = inner.as_ref() {
1011 if !opaque_types.contains(n.as_str()) {
1012 Some(n.clone())
1013 } else {
1014 None
1015 }
1016 } else {
1017 None
1018 }
1019 }
1020 _ => None,
1021 };
1022 inner_name.map(|type_name| {
1023 let cname = "c".to_string() + &to_java_name(&p.name);
1024 let type_snake = type_name.to_snake_case();
1025 let free_handle = format!("NativeLib.{}_{}_FREE", prefix.to_uppercase(), type_snake.to_uppercase());
1026 (cname, free_handle)
1027 })
1028 })
1029 .collect();
1030
1031 for param in &func.params {
1033 marshal_param_to_ffi(out, &to_java_name(¶m.name), ¶m.ty, opaque_types, prefix);
1034 }
1035
1036 let ffi_handle = format!("NativeLib.{}_{}", prefix.to_uppercase(), func.name.to_uppercase());
1038
1039 let call_args: Vec<String> = func
1040 .params
1041 .iter()
1042 .map(|p| ffi_param_name(&to_java_name(&p.name), &p.ty, opaque_types))
1043 .collect();
1044
1045 let emit_ffi_ptr_cleanup = |out: &mut String| {
1047 for (cname, free_handle) in &ffi_ptr_params {
1048 writeln!(out, " if (!{}.equals(MemorySegment.NULL)) {{", cname).ok();
1049 writeln!(out, " {}.invoke({});", free_handle, cname).ok();
1050 writeln!(out, " }}").ok();
1051 }
1052 };
1053
1054 if matches!(func.return_type, TypeRef::Unit) {
1055 writeln!(out, " {}.invoke({});", ffi_handle, call_args.join(", ")).ok();
1056 emit_ffi_ptr_cleanup(out);
1057 writeln!(out, " }} catch (Throwable e) {{").ok();
1058 writeln!(
1059 out,
1060 " throw new {}Exception(\"FFI call failed\", e);",
1061 class_name
1062 )
1063 .ok();
1064 writeln!(out, " }}").ok();
1065 } else if is_ffi_string_return(&func.return_type) {
1066 let free_handle = format!("NativeLib.{}_FREE_STRING", prefix.to_uppercase());
1067 writeln!(
1068 out,
1069 " var resultPtr = (MemorySegment) {}.invoke({});",
1070 ffi_handle,
1071 call_args.join(", ")
1072 )
1073 .ok();
1074 emit_ffi_ptr_cleanup(out);
1075 writeln!(out, " if (resultPtr.equals(MemorySegment.NULL)) {{").ok();
1076 writeln!(out, " checkLastError();").ok();
1077 writeln!(out, " return null;").ok();
1078 writeln!(out, " }}").ok();
1079 writeln!(
1080 out,
1081 " String result = resultPtr.reinterpret(Long.MAX_VALUE).getString(0);"
1082 )
1083 .ok();
1084 writeln!(out, " {}.invoke(resultPtr);", free_handle).ok();
1085 writeln!(out, " return result;").ok();
1086 writeln!(out, " }} catch (Throwable e) {{").ok();
1087 writeln!(
1088 out,
1089 " throw new {}Exception(\"FFI call failed\", e);",
1090 class_name
1091 )
1092 .ok();
1093 writeln!(out, " }}").ok();
1094 } else if matches!(func.return_type, TypeRef::Named(_)) {
1095 let return_type_name = match &func.return_type {
1097 TypeRef::Named(name) => name,
1098 _ => unreachable!(),
1099 };
1100 let is_opaque = opaque_types.contains(return_type_name.as_str());
1101
1102 writeln!(
1103 out,
1104 " var resultPtr = (MemorySegment) {}.invoke({});",
1105 ffi_handle,
1106 call_args.join(", ")
1107 )
1108 .ok();
1109 emit_ffi_ptr_cleanup(out);
1110 writeln!(out, " if (resultPtr.equals(MemorySegment.NULL)) {{").ok();
1111 writeln!(out, " checkLastError();").ok();
1112 writeln!(out, " return null;").ok();
1113 writeln!(out, " }}").ok();
1114
1115 if is_opaque {
1116 writeln!(out, " return new {}(resultPtr);", return_type_name).ok();
1118 } else {
1119 let type_snake = return_type_name.to_snake_case();
1122 let free_handle = format!("NativeLib.{}_{}_FREE", prefix.to_uppercase(), type_snake.to_uppercase());
1123 let to_json_handle = format!(
1124 "NativeLib.{}_{}_TO_JSON",
1125 prefix.to_uppercase(),
1126 type_snake.to_uppercase()
1127 );
1128 writeln!(
1129 out,
1130 " var jsonPtr = (MemorySegment) {}.invoke(resultPtr);",
1131 to_json_handle
1132 )
1133 .ok();
1134 writeln!(out, " {}.invoke(resultPtr);", free_handle).ok();
1135 writeln!(out, " if (jsonPtr.equals(MemorySegment.NULL)) {{").ok();
1136 writeln!(out, " checkLastError();").ok();
1137 writeln!(out, " return null;").ok();
1138 writeln!(out, " }}").ok();
1139 writeln!(
1140 out,
1141 " String json = jsonPtr.reinterpret(Long.MAX_VALUE).getString(0);"
1142 )
1143 .ok();
1144 writeln!(
1145 out,
1146 " NativeLib.{}_FREE_STRING.invoke(jsonPtr);",
1147 prefix.to_uppercase()
1148 )
1149 .ok();
1150 writeln!(
1151 out,
1152 " return createObjectMapper().readValue(json, {}.class);",
1153 return_type_name
1154 )
1155 .ok();
1156 }
1157
1158 writeln!(out, " }} catch (Throwable e) {{").ok();
1159 writeln!(
1160 out,
1161 " throw new {}Exception(\"FFI call failed\", e);",
1162 class_name
1163 )
1164 .ok();
1165 writeln!(out, " }}").ok();
1166 } else if matches!(func.return_type, TypeRef::Vec(_)) {
1167 let free_handle = format!("NativeLib.{}_FREE_STRING", prefix.to_uppercase());
1169 writeln!(
1170 out,
1171 " var resultPtr = (MemorySegment) {}.invoke({});",
1172 ffi_handle,
1173 call_args.join(", ")
1174 )
1175 .ok();
1176 emit_ffi_ptr_cleanup(out);
1177 writeln!(out, " if (resultPtr.equals(MemorySegment.NULL)) {{").ok();
1178 writeln!(out, " return java.util.List.of();").ok();
1179 writeln!(out, " }}").ok();
1180 writeln!(
1181 out,
1182 " String json = resultPtr.reinterpret(Long.MAX_VALUE).getString(0);"
1183 )
1184 .ok();
1185 writeln!(out, " {}.invoke(resultPtr);", free_handle).ok();
1186 let element_type = match &func.return_type {
1188 TypeRef::Vec(inner) => java_type(inner),
1189 _ => unreachable!(),
1190 };
1191 writeln!(
1192 out,
1193 " return createObjectMapper().readValue(json, new com.fasterxml.jackson.core.type.TypeReference<java.util.List<{}>>() {{ }});",
1194 element_type
1195 )
1196 .ok();
1197 writeln!(out, " }} catch (Throwable e) {{").ok();
1198 writeln!(
1199 out,
1200 " throw new {}Exception(\"FFI call failed\", e);",
1201 class_name
1202 )
1203 .ok();
1204 writeln!(out, " }}").ok();
1205 } else {
1206 writeln!(
1207 out,
1208 " var primitiveResult = ({}) {}.invoke({});",
1209 java_ffi_return_cast(&func.return_type),
1210 ffi_handle,
1211 call_args.join(", ")
1212 )
1213 .ok();
1214 emit_ffi_ptr_cleanup(out);
1215 writeln!(out, " return primitiveResult;").ok();
1216 writeln!(out, " }} catch (Throwable e) {{").ok();
1217 writeln!(
1218 out,
1219 " throw new {}Exception(\"FFI call failed\", e);",
1220 class_name
1221 )
1222 .ok();
1223 writeln!(out, " }}").ok();
1224 }
1225
1226 writeln!(out, " }}").ok();
1227}
1228
1229fn gen_async_wrapper_method(out: &mut String, func: &FunctionDef) {
1230 let params: Vec<String> = func
1231 .params
1232 .iter()
1233 .map(|p| {
1234 let ptype = java_type(&p.ty);
1235 format!("{} {}", ptype, to_java_name(&p.name))
1236 })
1237 .collect();
1238
1239 let return_type = match &func.return_type {
1240 TypeRef::Unit => "Void".to_string(),
1241 other => java_boxed_type(other).to_string(),
1242 };
1243
1244 let sync_method_name = to_java_name(&func.name);
1245 let async_method_name = format!("{}Async", sync_method_name);
1246 let param_names: Vec<String> = func.params.iter().map(|p| to_java_name(&p.name)).collect();
1247
1248 writeln!(
1249 out,
1250 " public static CompletableFuture<{}> {}({}) {{",
1251 return_type,
1252 async_method_name,
1253 params.join(", ")
1254 )
1255 .ok();
1256 writeln!(out, " return CompletableFuture.supplyAsync(() -> {{").ok();
1257 writeln!(out, " try {{").ok();
1258 writeln!(
1259 out,
1260 " return {}({});",
1261 sync_method_name,
1262 param_names.join(", ")
1263 )
1264 .ok();
1265 writeln!(out, " }} catch (Throwable e) {{").ok();
1266 writeln!(out, " throw new CompletionException(e);").ok();
1267 writeln!(out, " }}").ok();
1268 writeln!(out, " }});").ok();
1269 writeln!(out, " }}").ok();
1270}
1271
1272fn gen_exception_class(package: &str, class_name: &str) -> String {
1277 let mut out = String::with_capacity(512);
1278
1279 writeln!(out, "// DO NOT EDIT - auto-generated by alef").ok();
1280 writeln!(out, "package {};", package).ok();
1281 writeln!(out).ok();
1282
1283 writeln!(out, "public class {}Exception extends Exception {{", class_name).ok();
1284 writeln!(out, " private final int code;").ok();
1285 writeln!(out).ok();
1286 writeln!(out, " public {}Exception(int code, String message) {{", class_name).ok();
1287 writeln!(out, " super(message);").ok();
1288 writeln!(out, " this.code = code;").ok();
1289 writeln!(out, " }}").ok();
1290 writeln!(out).ok();
1291 writeln!(
1292 out,
1293 " public {}Exception(String message, Throwable cause) {{",
1294 class_name
1295 )
1296 .ok();
1297 writeln!(out, " super(message, cause);").ok();
1298 writeln!(out, " this.code = -1;").ok();
1299 writeln!(out, " }}").ok();
1300 writeln!(out).ok();
1301 writeln!(out, " public int getCode() {{").ok();
1302 writeln!(out, " return code;").ok();
1303 writeln!(out, " }}").ok();
1304 writeln!(out, "}}").ok();
1305
1306 out
1307}
1308
1309fn gen_facade_class(api: &ApiSurface, package: &str, public_class: &str, raw_class: &str, _prefix: &str) -> String {
1314 let mut body = String::with_capacity(4096);
1315
1316 writeln!(body, "public final class {} {{", public_class).ok();
1317 writeln!(body, " private {}() {{ }}", public_class).ok();
1318 writeln!(body).ok();
1319
1320 for func in &api.functions {
1322 let params: Vec<String> = func
1324 .params
1325 .iter()
1326 .map(|p| {
1327 let ptype = java_type(&p.ty);
1328 format!("{} {}", ptype, to_java_name(&p.name))
1329 })
1330 .collect();
1331
1332 let return_type = java_type(&func.return_type);
1333
1334 if !func.doc.is_empty() {
1335 writeln!(body, " /**").ok();
1336 for line in func.doc.lines() {
1337 writeln!(body, " * {}", line).ok();
1338 }
1339 writeln!(body, " */").ok();
1340 }
1341
1342 writeln!(
1343 body,
1344 " public static {} {}({}) throws {}Exception {{",
1345 return_type,
1346 to_java_name(&func.name),
1347 params.join(", "),
1348 raw_class
1349 )
1350 .ok();
1351
1352 for param in &func.params {
1354 if !param.optional {
1355 let pname = to_java_name(¶m.name);
1356 writeln!(
1357 body,
1358 " java.util.Objects.requireNonNull({}, \"{} must not be null\");",
1359 pname, pname
1360 )
1361 .ok();
1362 }
1363 }
1364
1365 let call_args: Vec<String> = func.params.iter().map(|p| to_java_name(&p.name)).collect();
1367
1368 if matches!(func.return_type, TypeRef::Unit) {
1369 writeln!(
1370 body,
1371 " {}.{}({});",
1372 raw_class,
1373 to_java_name(&func.name),
1374 call_args.join(", ")
1375 )
1376 .ok();
1377 } else {
1378 writeln!(
1379 body,
1380 " return {}.{}({});",
1381 raw_class,
1382 to_java_name(&func.name),
1383 call_args.join(", ")
1384 )
1385 .ok();
1386 }
1387
1388 writeln!(body, " }}").ok();
1389 writeln!(body).ok();
1390
1391 let has_optional = func.params.iter().any(|p| p.optional);
1393 if has_optional {
1394 let required_params: Vec<String> = func
1395 .params
1396 .iter()
1397 .filter(|p| !p.optional)
1398 .map(|p| {
1399 let ptype = java_type(&p.ty);
1400 format!("{} {}", ptype, to_java_name(&p.name))
1401 })
1402 .collect();
1403
1404 writeln!(
1405 body,
1406 " public static {} {}({}) throws {}Exception {{",
1407 return_type,
1408 to_java_name(&func.name),
1409 required_params.join(", "),
1410 raw_class
1411 )
1412 .ok();
1413
1414 let full_args: Vec<String> = func
1416 .params
1417 .iter()
1418 .map(|p| {
1419 if p.optional {
1420 "null".to_string()
1421 } else {
1422 to_java_name(&p.name)
1423 }
1424 })
1425 .collect();
1426
1427 if matches!(func.return_type, TypeRef::Unit) {
1428 writeln!(body, " {}({});", to_java_name(&func.name), full_args.join(", ")).ok();
1429 } else {
1430 writeln!(
1431 body,
1432 " return {}({});",
1433 to_java_name(&func.name),
1434 full_args.join(", ")
1435 )
1436 .ok();
1437 }
1438
1439 writeln!(body, " }}").ok();
1440 writeln!(body).ok();
1441 }
1442 }
1443
1444 writeln!(body, "}}").ok();
1445
1446 let mut out = String::with_capacity(body.len() + 512);
1448
1449 writeln!(out, "// DO NOT EDIT - auto-generated by alef").ok();
1450 writeln!(out, "package {};", package).ok();
1451
1452 let has_list = body.contains("List<");
1454 let has_map = body.contains("Map<");
1455 let has_optional = body.contains("Optional<");
1456 let has_imports = has_list || has_map || has_optional;
1457
1458 if has_imports {
1459 writeln!(out).ok();
1460 if has_list {
1461 writeln!(out, "import java.util.List;").ok();
1462 }
1463 if has_map {
1464 writeln!(out, "import java.util.Map;").ok();
1465 }
1466 if has_optional {
1467 writeln!(out, "import java.util.Optional;").ok();
1468 }
1469 }
1470
1471 writeln!(out).ok();
1472 out.push_str(&body);
1473
1474 out
1475}
1476
1477fn gen_opaque_handle_class(package: &str, typ: &TypeDef, prefix: &str) -> String {
1482 let mut out = String::with_capacity(1024);
1483 let class_name = &typ.name;
1484 let type_snake = class_name.to_snake_case();
1485
1486 writeln!(out, "// DO NOT EDIT - auto-generated by alef").ok();
1487 writeln!(out, "package {};", package).ok();
1488 writeln!(out).ok();
1489 writeln!(out, "import java.lang.foreign.MemorySegment;").ok();
1490 writeln!(out).ok();
1491
1492 if !typ.doc.is_empty() {
1493 writeln!(out, "/**").ok();
1494 for line in typ.doc.lines() {
1495 writeln!(out, " * {}", line).ok();
1496 }
1497 writeln!(out, " */").ok();
1498 }
1499
1500 writeln!(out, "public class {} implements AutoCloseable {{", class_name).ok();
1501 writeln!(out, " private final MemorySegment handle;").ok();
1502 writeln!(out).ok();
1503 writeln!(out, " {}(MemorySegment handle) {{", class_name).ok();
1504 writeln!(out, " this.handle = handle;").ok();
1505 writeln!(out, " }}").ok();
1506 writeln!(out).ok();
1507 writeln!(out, " MemorySegment handle() {{").ok();
1508 writeln!(out, " return this.handle;").ok();
1509 writeln!(out, " }}").ok();
1510 writeln!(out).ok();
1511 writeln!(out, " @Override").ok();
1512 writeln!(out, " public void close() {{").ok();
1513 writeln!(
1514 out,
1515 " if (handle != null && !handle.equals(MemorySegment.NULL)) {{"
1516 )
1517 .ok();
1518 writeln!(out, " try {{").ok();
1519 writeln!(
1520 out,
1521 " NativeLib.{}_{}_FREE.invoke(handle);",
1522 prefix.to_uppercase(),
1523 type_snake.to_uppercase()
1524 )
1525 .ok();
1526 writeln!(out, " }} catch (Throwable e) {{").ok();
1527 writeln!(
1528 out,
1529 " throw new RuntimeException(\"Failed to free {}: \" + e.getMessage(), e);",
1530 class_name
1531 )
1532 .ok();
1533 writeln!(out, " }}").ok();
1534 writeln!(out, " }}").ok();
1535 writeln!(out, " }}").ok();
1536 writeln!(out, "}}").ok();
1537
1538 out
1539}
1540
1541const RECORD_LINE_WRAP_THRESHOLD: usize = 100;
1548
1549fn gen_record_type(package: &str, typ: &TypeDef, complex_enums: &AHashSet<String>, lang_rename_all: &str) -> String {
1550 let field_list: Vec<String> = typ
1554 .fields
1555 .iter()
1556 .map(|f| {
1557 let is_complex = matches!(&f.ty, TypeRef::Named(n) if complex_enums.contains(n.as_str()));
1560 let ftype = if is_complex {
1561 "Object".to_string()
1562 } else if f.optional {
1563 format!("Optional<{}>", java_boxed_type(&f.ty))
1564 } else {
1565 java_type(&f.ty).to_string()
1566 };
1567 let jname = safe_java_field_name(&f.name);
1568 if lang_rename_all == "camelCase" && f.name.contains('_') {
1572 format!("@JsonProperty(\"{}\") {} {}", f.name, ftype, jname)
1573 } else {
1574 format!("{} {}", ftype, jname)
1575 }
1576 })
1577 .collect();
1578
1579 let single_line = format!("public record {}({}) {{ }}", typ.name, field_list.join(", "));
1581
1582 let mut record_block = String::new();
1584 if single_line.len() > RECORD_LINE_WRAP_THRESHOLD && field_list.len() > 1 {
1585 writeln!(record_block, "public record {}(", typ.name).ok();
1586 for (i, field) in field_list.iter().enumerate() {
1587 let comma = if i < field_list.len() - 1 { "," } else { "" };
1588 writeln!(record_block, " {}{}", field, comma).ok();
1589 }
1590 writeln!(record_block, ") {{").ok();
1591 } else {
1592 writeln!(record_block, "public record {}({}) {{", typ.name, field_list.join(", ")).ok();
1593 }
1594
1595 if typ.has_default {
1597 writeln!(record_block, " public static {}Builder builder() {{", typ.name).ok();
1598 writeln!(record_block, " return new {}Builder();", typ.name).ok();
1599 writeln!(record_block, " }}").ok();
1600 }
1601
1602 writeln!(record_block, "}}").ok();
1603
1604 let needs_json_property = field_list.iter().any(|f| f.contains("@JsonProperty("));
1606 let mut out = String::with_capacity(record_block.len() + 512);
1607 writeln!(out, "// DO NOT EDIT - auto-generated by alef").ok();
1608 writeln!(out, "package {};", package).ok();
1609 writeln!(out).ok();
1610 if single_line.contains("List<") {
1611 writeln!(out, "import java.util.List;").ok();
1612 }
1613 if single_line.contains("Map<") {
1614 writeln!(out, "import java.util.Map;").ok();
1615 }
1616 if single_line.contains("Optional<") {
1617 writeln!(out, "import java.util.Optional;").ok();
1618 }
1619 if needs_json_property {
1620 writeln!(out, "import com.fasterxml.jackson.annotation.JsonProperty;").ok();
1621 }
1622 writeln!(out).ok();
1623 write!(out, "{}", record_block).ok();
1624
1625 out
1626}
1627
1628fn java_apply_rename_all(name: &str, rename_all: Option<&str>) -> String {
1634 match rename_all {
1635 Some("snake_case") => name.to_snake_case(),
1636 Some("camelCase") => name.to_lower_camel_case(),
1637 Some("PascalCase") => name.to_pascal_case(),
1638 Some("SCREAMING_SNAKE_CASE") => name.to_snake_case().to_uppercase(),
1639 Some("lowercase") => name.to_lowercase(),
1640 Some("UPPERCASE") => name.to_uppercase(),
1641 _ => name.to_lowercase(),
1642 }
1643}
1644
1645fn gen_enum_class(package: &str, enum_def: &EnumDef) -> String {
1646 let has_data_variants = enum_def.variants.iter().any(|v| !v.fields.is_empty());
1647
1648 if enum_def.serde_tag.is_some() && has_data_variants {
1650 return gen_java_tagged_union(package, enum_def);
1651 }
1652
1653 let mut out = String::with_capacity(1024);
1654
1655 writeln!(out, "// DO NOT EDIT - auto-generated by alef").ok();
1656 writeln!(out, "package {};", package).ok();
1657 writeln!(out).ok();
1658 writeln!(out, "import com.fasterxml.jackson.annotation.JsonCreator;").ok();
1659 writeln!(out, "import com.fasterxml.jackson.annotation.JsonValue;").ok();
1660 writeln!(out).ok();
1661
1662 writeln!(out, "public enum {} {{", enum_def.name).ok();
1663
1664 for (i, variant) in enum_def.variants.iter().enumerate() {
1665 let comma = if i < enum_def.variants.len() - 1 { "," } else { ";" };
1666 let json_name = variant
1668 .serde_rename
1669 .clone()
1670 .unwrap_or_else(|| java_apply_rename_all(&variant.name, enum_def.serde_rename_all.as_deref()));
1671 writeln!(out, " {}(\"{}\"){}", variant.name, json_name, comma).ok();
1672 }
1673
1674 writeln!(out).ok();
1675 writeln!(out, " private final String value;").ok();
1676 writeln!(out).ok();
1677 writeln!(out, " {}(String value) {{", enum_def.name).ok();
1678 writeln!(out, " this.value = value;").ok();
1679 writeln!(out, " }}").ok();
1680 writeln!(out).ok();
1681 writeln!(out, " @JsonValue").ok();
1682 writeln!(out, " public String getValue() {{").ok();
1683 writeln!(out, " return value;").ok();
1684 writeln!(out, " }}").ok();
1685 writeln!(out).ok();
1686 writeln!(out, " @JsonCreator").ok();
1687 writeln!(out, " public static {} fromValue(String value) {{", enum_def.name).ok();
1688 writeln!(out, " for ({} e : values()) {{", enum_def.name).ok();
1689 writeln!(out, " if (e.value.equalsIgnoreCase(value)) {{").ok();
1690 writeln!(out, " return e;").ok();
1691 writeln!(out, " }}").ok();
1692 writeln!(out, " }}").ok();
1693 writeln!(
1694 out,
1695 " throw new IllegalArgumentException(\"Unknown value: \" + value);"
1696 )
1697 .ok();
1698 writeln!(out, " }}").ok();
1699
1700 writeln!(out, "}}").ok();
1701
1702 out
1703}
1704
1705fn gen_java_tagged_union(package: &str, enum_def: &EnumDef) -> String {
1710 let tag_field = enum_def.serde_tag.as_deref().unwrap_or("type");
1711
1712 let variant_names: std::collections::HashSet<&str> = enum_def.variants.iter().map(|v| v.name.as_str()).collect();
1717 let optional_type = if variant_names.contains("Optional") {
1718 "java.util.Optional"
1719 } else {
1720 "Optional"
1721 };
1722
1723 let mut out = String::with_capacity(2048);
1724 writeln!(out, "// DO NOT EDIT - auto-generated by alef").ok();
1725 writeln!(out, "package {};", package).ok();
1726 writeln!(out).ok();
1727 writeln!(out, "import com.fasterxml.jackson.annotation.JsonProperty;").ok();
1728 writeln!(out, "import com.fasterxml.jackson.annotation.JsonSubTypes;").ok();
1729 writeln!(out, "import com.fasterxml.jackson.annotation.JsonTypeInfo;").ok();
1730
1731 let needs_list = !variant_names.contains("List")
1733 && enum_def
1734 .variants
1735 .iter()
1736 .any(|v| v.fields.iter().any(|f| matches!(&f.ty, TypeRef::Vec(_))));
1737 let needs_map = !variant_names.contains("Map")
1738 && enum_def
1739 .variants
1740 .iter()
1741 .any(|v| v.fields.iter().any(|f| matches!(&f.ty, TypeRef::Map(_, _))));
1742 let needs_optional =
1743 !variant_names.contains("Optional") && enum_def.variants.iter().any(|v| v.fields.iter().any(|f| f.optional));
1744 let needs_unwrapped = enum_def.variants.iter().any(|v| {
1747 v.fields.len() == 1 && is_tuple_field_name(&v.fields[0].name)
1748 });
1749 if needs_list {
1750 writeln!(out, "import java.util.List;").ok();
1751 }
1752 if needs_map {
1753 writeln!(out, "import java.util.Map;").ok();
1754 }
1755 if needs_optional {
1756 writeln!(out, "import java.util.Optional;").ok();
1757 }
1758 if needs_unwrapped {
1759 writeln!(out, "import com.fasterxml.jackson.annotation.JsonUnwrapped;").ok();
1760 }
1761 writeln!(out).ok();
1762
1763 writeln!(
1765 out,
1766 "@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = \"{tag_field}\", visible = false)"
1767 )
1768 .ok();
1769 writeln!(out, "@JsonSubTypes({{").ok();
1770 for (i, variant) in enum_def.variants.iter().enumerate() {
1771 let discriminator = variant
1772 .serde_rename
1773 .clone()
1774 .unwrap_or_else(|| java_apply_rename_all(&variant.name, enum_def.serde_rename_all.as_deref()));
1775 let comma = if i < enum_def.variants.len() - 1 { "," } else { "" };
1776 writeln!(
1777 out,
1778 " @JsonSubTypes.Type(value = {}.{}.class, name = \"{}\"){}",
1779 enum_def.name, variant.name, discriminator, comma
1780 )
1781 .ok();
1782 }
1783 writeln!(out, "}})").ok();
1784 writeln!(out, "public sealed interface {} {{", enum_def.name).ok();
1785
1786 for variant in &enum_def.variants {
1788 writeln!(out).ok();
1789 if variant.fields.is_empty() {
1790 writeln!(out, " record {}() implements {} {{", variant.name, enum_def.name).ok();
1792 writeln!(out, " }}").ok();
1793 } else {
1794 let field_parts: Vec<String> = variant
1796 .fields
1797 .iter()
1798 .map(|f| {
1799 let ftype = if f.optional {
1800 let inner = java_boxed_type(&f.ty);
1801 let inner_str = inner.as_ref();
1802 let inner_qualified = if inner_str.starts_with("List<") && variant_names.contains("List") {
1804 inner_str.replacen("List<", "java.util.List<", 1)
1805 } else if inner_str.starts_with("Map<") && variant_names.contains("Map") {
1806 inner_str.replacen("Map<", "java.util.Map<", 1)
1807 } else {
1808 inner_str.to_string()
1809 };
1810 format!("{optional_type}<{inner_qualified}>")
1811 } else {
1812 let t = java_type(&f.ty);
1813 let t_str = t.as_ref();
1814 if t_str.starts_with("List<") && variant_names.contains("List") {
1815 t_str.replacen("List<", "java.util.List<", 1)
1816 } else if t_str.starts_with("Map<") && variant_names.contains("Map") {
1817 t_str.replacen("Map<", "java.util.Map<", 1)
1818 } else {
1819 t_str.to_string()
1820 }
1821 };
1822 if is_tuple_field_name(&f.name) {
1826 format!("@JsonUnwrapped {ftype} value")
1827 } else {
1828 let json_name = f.name.trim_start_matches('_');
1829 let jname = safe_java_field_name(json_name);
1830 format!("@JsonProperty(\"{json_name}\") {ftype} {jname}")
1831 }
1832 })
1833 .collect();
1834
1835 let single = format!(
1836 " record {}({}) implements {} {{ }}",
1837 variant.name,
1838 field_parts.join(", "),
1839 enum_def.name
1840 );
1841
1842 if single.len() > RECORD_LINE_WRAP_THRESHOLD && field_parts.len() > 1 {
1843 writeln!(out, " record {}(", variant.name).ok();
1844 for (i, fp) in field_parts.iter().enumerate() {
1845 let comma = if i < field_parts.len() - 1 { "," } else { "" };
1846 writeln!(out, " {}{}", fp, comma).ok();
1847 }
1848 writeln!(out, " ) implements {} {{", enum_def.name).ok();
1849 writeln!(out, " }}").ok();
1850 } else {
1851 writeln!(
1852 out,
1853 " record {}({}) implements {} {{ }}",
1854 variant.name,
1855 field_parts.join(", "),
1856 enum_def.name
1857 )
1858 .ok();
1859 }
1860 }
1861 }
1862
1863 writeln!(out).ok();
1864 writeln!(out, "}}").ok();
1865 out
1866}
1867
1868fn gen_ffi_layout(ty: &TypeRef) -> String {
1873 match ty {
1874 TypeRef::Primitive(prim) => java_ffi_type(prim).to_string(),
1875 TypeRef::String | TypeRef::Char | TypeRef::Path | TypeRef::Json => "ValueLayout.ADDRESS".to_string(),
1876 TypeRef::Bytes => "ValueLayout.ADDRESS".to_string(),
1877 TypeRef::Optional(inner) => gen_ffi_layout(inner),
1878 TypeRef::Vec(_) => "ValueLayout.ADDRESS".to_string(),
1879 TypeRef::Map(_, _) => "ValueLayout.ADDRESS".to_string(),
1880 TypeRef::Named(_) => "ValueLayout.ADDRESS".to_string(),
1881 TypeRef::Unit => "".to_string(),
1882 TypeRef::Duration => "ValueLayout.JAVA_LONG".to_string(),
1883 }
1884}
1885
1886fn marshal_param_to_ffi(out: &mut String, name: &str, ty: &TypeRef, opaque_types: &AHashSet<String>, prefix: &str) {
1887 match ty {
1888 TypeRef::String | TypeRef::Char | TypeRef::Path | TypeRef::Json => {
1889 let cname = "c".to_string() + name;
1890 writeln!(out, " var {} = arena.allocateFrom({});", cname, name).ok();
1891 }
1892 TypeRef::Named(type_name) => {
1893 let cname = "c".to_string() + name;
1894 if opaque_types.contains(type_name.as_str()) {
1895 writeln!(out, " var {} = {}.handle();", cname, name).ok();
1897 } else {
1898 let type_snake = type_name.to_snake_case();
1901 let from_json_handle = format!(
1902 "NativeLib.{}_{}_FROM_JSON",
1903 prefix.to_uppercase(),
1904 type_snake.to_uppercase()
1905 );
1906 let _free_handle = format!("NativeLib.{}_{}_FREE", prefix.to_uppercase(), type_snake.to_uppercase());
1907 writeln!(
1908 out,
1909 " var {}Json = {} != null ? createObjectMapper().writeValueAsString({}) : null;",
1910 cname, name, name
1911 )
1912 .ok();
1913 writeln!(
1914 out,
1915 " var {}JsonSeg = {}Json != null ? arena.allocateFrom({}Json) : MemorySegment.NULL;",
1916 cname, cname, cname
1917 )
1918 .ok();
1919 writeln!(out, " var {} = {}Json != null", cname, cname).ok();
1920 writeln!(
1921 out,
1922 " ? (MemorySegment) {}.invoke({}JsonSeg)",
1923 from_json_handle, cname
1924 )
1925 .ok();
1926 writeln!(out, " : MemorySegment.NULL;").ok();
1927 }
1928 }
1929 TypeRef::Optional(inner) => {
1930 match inner.as_ref() {
1932 TypeRef::String | TypeRef::Char | TypeRef::Path | TypeRef::Json => {
1933 let cname = "c".to_string() + name;
1934 writeln!(
1935 out,
1936 " var {} = {} != null ? arena.allocateFrom({}) : MemorySegment.NULL;",
1937 cname, name, name
1938 )
1939 .ok();
1940 }
1941 TypeRef::Named(type_name) => {
1942 let cname = "c".to_string() + name;
1943 if opaque_types.contains(type_name.as_str()) {
1944 writeln!(
1945 out,
1946 " var {} = {} != null ? {}.handle() : MemorySegment.NULL;",
1947 cname, name, name
1948 )
1949 .ok();
1950 } else {
1951 let type_snake = type_name.to_snake_case();
1953 let from_json_handle = format!(
1954 "NativeLib.{}_{}_FROM_JSON",
1955 prefix.to_uppercase(),
1956 type_snake.to_uppercase()
1957 );
1958 writeln!(
1959 out,
1960 " var {}Json = {} != null ? createObjectMapper().writeValueAsString({}) : null;",
1961 cname, name, name
1962 )
1963 .ok();
1964 writeln!(out, " var {}JsonSeg = {}Json != null ? arena.allocateFrom({}Json) : MemorySegment.NULL;", cname, cname, cname).ok();
1965 writeln!(out, " var {} = {}Json != null", cname, cname).ok();
1966 writeln!(
1967 out,
1968 " ? (MemorySegment) {}.invoke({}JsonSeg)",
1969 from_json_handle, cname
1970 )
1971 .ok();
1972 writeln!(out, " : MemorySegment.NULL;").ok();
1973 }
1974 }
1975 _ => {
1976 }
1978 }
1979 }
1980 TypeRef::Vec(_) | TypeRef::Map(_, _) => {
1981 let cname = "c".to_string() + name;
1983 writeln!(
1984 out,
1985 " var {}Json = createObjectMapper().writeValueAsString({});",
1986 cname, name
1987 )
1988 .ok();
1989 writeln!(out, " var {} = arena.allocateFrom({}Json);", cname, cname).ok();
1990 }
1991 _ => {
1992 }
1994 }
1995}
1996
1997fn ffi_param_name(name: &str, ty: &TypeRef, _opaque_types: &AHashSet<String>) -> String {
1998 match ty {
1999 TypeRef::String | TypeRef::Char | TypeRef::Path | TypeRef::Json => "c".to_string() + name,
2000 TypeRef::Named(_) => "c".to_string() + name,
2001 TypeRef::Vec(_) | TypeRef::Map(_, _) => "c".to_string() + name,
2002 TypeRef::Optional(inner) => match inner.as_ref() {
2003 TypeRef::String | TypeRef::Char | TypeRef::Path | TypeRef::Json | TypeRef::Named(_) => {
2004 "c".to_string() + name
2005 }
2006 _ => name.to_string(),
2007 },
2008 _ => name.to_string(),
2009 }
2010}
2011
2012fn gen_function_descriptor(return_layout: &str, param_layouts: &[String]) -> String {
2015 if return_layout.is_empty() {
2016 if param_layouts.is_empty() {
2018 "FunctionDescriptor.ofVoid()".to_string()
2019 } else {
2020 format!("FunctionDescriptor.ofVoid({})", param_layouts.join(", "))
2021 }
2022 } else {
2023 if param_layouts.is_empty() {
2025 format!("FunctionDescriptor.of({})", return_layout)
2026 } else {
2027 format!("FunctionDescriptor.of({}, {})", return_layout, param_layouts.join(", "))
2028 }
2029 }
2030}
2031
2032fn is_ffi_string_return(ty: &TypeRef) -> bool {
2035 match ty {
2036 TypeRef::String | TypeRef::Char | TypeRef::Path | TypeRef::Json => true,
2037 TypeRef::Optional(inner) => is_ffi_string_return(inner),
2038 _ => false,
2039 }
2040}
2041
2042fn java_ffi_return_cast(ty: &TypeRef) -> &'static str {
2044 match ty {
2045 TypeRef::Primitive(prim) => match prim {
2046 PrimitiveType::Bool => "boolean",
2047 PrimitiveType::U8 | PrimitiveType::I8 => "byte",
2048 PrimitiveType::U16 | PrimitiveType::I16 => "short",
2049 PrimitiveType::U32 | PrimitiveType::I32 => "int",
2050 PrimitiveType::U64 | PrimitiveType::I64 | PrimitiveType::Usize | PrimitiveType::Isize => "long",
2051 PrimitiveType::F32 => "float",
2052 PrimitiveType::F64 => "double",
2053 },
2054 TypeRef::Bytes | TypeRef::Vec(_) | TypeRef::Map(_, _) | TypeRef::Named(_) => "MemorySegment",
2055 _ => "MemorySegment",
2056 }
2057}
2058
2059fn gen_helper_methods(out: &mut String, prefix: &str, class_name: &str) {
2060 let needs_check_last_error = out.contains("checkLastError()");
2062 let needs_read_cstring = out.contains("readCString(");
2063 let needs_read_bytes = out.contains("readBytes(");
2064 let needs_create_object_mapper = out.contains("createObjectMapper()");
2065
2066 if !needs_check_last_error && !needs_read_cstring && !needs_read_bytes && !needs_create_object_mapper {
2067 return;
2068 }
2069
2070 writeln!(out, " // Helper methods for FFI marshalling").ok();
2071 writeln!(out).ok();
2072
2073 if needs_check_last_error {
2074 writeln!(out, " private static void checkLastError() throws Throwable {{").ok();
2077 writeln!(
2078 out,
2079 " int errCode = (int) NativeLib.{}_LAST_ERROR_CODE.invoke();",
2080 prefix.to_uppercase()
2081 )
2082 .ok();
2083 writeln!(out, " if (errCode != 0) {{").ok();
2084 writeln!(
2085 out,
2086 " var ctxPtr = (MemorySegment) NativeLib.{}_LAST_ERROR_CONTEXT.invoke();",
2087 prefix.to_uppercase()
2088 )
2089 .ok();
2090 writeln!(
2091 out,
2092 " String msg = ctxPtr.reinterpret(Long.MAX_VALUE).getString(0);"
2093 )
2094 .ok();
2095 writeln!(out, " throw new {}Exception(errCode, msg);", class_name).ok();
2096 writeln!(out, " }}").ok();
2097 writeln!(out, " }}").ok();
2098 writeln!(out).ok();
2099 }
2100
2101 if needs_create_object_mapper {
2102 writeln!(
2108 out,
2109 " private static com.fasterxml.jackson.databind.ObjectMapper createObjectMapper() {{"
2110 )
2111 .ok();
2112 writeln!(out, " return new com.fasterxml.jackson.databind.ObjectMapper()").ok();
2113 writeln!(
2114 out,
2115 " .registerModule(new com.fasterxml.jackson.datatype.jdk8.Jdk8Module())"
2116 )
2117 .ok();
2118 writeln!(out, " .findAndRegisterModules()").ok();
2119 writeln!(
2120 out,
2121 " .setSerializationInclusion(com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL)"
2122 )
2123 .ok();
2124 writeln!(
2125 out,
2126 " .configure(com.fasterxml.jackson.databind.MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS, true);"
2127 )
2128 .ok();
2129 writeln!(out, " }}").ok();
2130 writeln!(out).ok();
2131 }
2132
2133 if needs_read_cstring {
2134 writeln!(out, " private static String readCString(MemorySegment ptr) {{").ok();
2135 writeln!(out, " if (ptr == null || ptr.address() == 0) {{").ok();
2136 writeln!(out, " return null;").ok();
2137 writeln!(out, " }}").ok();
2138 writeln!(out, " return ptr.getUtf8String(0);").ok();
2139 writeln!(out, " }}").ok();
2140 writeln!(out).ok();
2141 }
2142
2143 if needs_read_bytes {
2144 writeln!(
2145 out,
2146 " private static byte[] readBytes(MemorySegment ptr, long len) {{"
2147 )
2148 .ok();
2149 writeln!(out, " if (ptr == null || ptr.address() == 0) {{").ok();
2150 writeln!(out, " return new byte[0];").ok();
2151 writeln!(out, " }}").ok();
2152 writeln!(out, " byte[] bytes = new byte[(int) len];").ok();
2153 writeln!(
2154 out,
2155 " MemorySegment.copy(ptr, ValueLayout.JAVA_BYTE.byteSize() * 0, bytes, 0, (int) len);"
2156 )
2157 .ok();
2158 writeln!(out, " return bytes;").ok();
2159 writeln!(out, " }}").ok();
2160 }
2161}
2162
2163fn format_optional_value(ty: &TypeRef, default: &str) -> String {
2170 if default.contains("Optional.") {
2172 return default.to_string();
2173 }
2174
2175 let inner_ty = match ty {
2177 TypeRef::Optional(inner) => inner.as_ref(),
2178 other => other,
2179 };
2180
2181 let formatted_value = match inner_ty {
2183 TypeRef::Primitive(p) => match p {
2184 PrimitiveType::I64 | PrimitiveType::U64 | PrimitiveType::Isize | PrimitiveType::Usize => {
2185 if default.ends_with('L') || default.ends_with('l') {
2187 default.to_string()
2188 } else if default.parse::<i64>().is_ok() {
2189 format!("{}L", default)
2190 } else {
2191 default.to_string()
2192 }
2193 }
2194 PrimitiveType::F32 => {
2195 if default.ends_with('f') || default.ends_with('F') {
2197 default.to_string()
2198 } else if default.parse::<f32>().is_ok() {
2199 format!("{}f", default)
2200 } else {
2201 default.to_string()
2202 }
2203 }
2204 PrimitiveType::F64 => {
2205 default.to_string()
2207 }
2208 _ => default.to_string(),
2209 },
2210 _ => default.to_string(),
2211 };
2212
2213 format!("Optional.of({})", formatted_value)
2214}
2215
2216fn gen_builder_class(package: &str, typ: &TypeDef) -> String {
2217 let mut body = String::with_capacity(2048);
2218
2219 writeln!(body, "public class {}Builder {{", typ.name).ok();
2220 writeln!(body).ok();
2221
2222 for field in &typ.fields {
2224 let field_name = safe_java_field_name(&field.name);
2225
2226 if field.name.starts_with('_') && field.name[1..].chars().all(|c| c.is_ascii_digit())
2228 || field.name.chars().next().is_none_or(|c| c.is_ascii_digit())
2229 {
2230 continue;
2231 }
2232
2233 let field_type = if field.optional {
2236 format!("Optional<{}>", java_boxed_type(&field.ty))
2237 } else if matches!(field.ty, TypeRef::Duration) {
2238 java_boxed_type(&field.ty).to_string()
2239 } else {
2240 java_type(&field.ty).to_string()
2241 };
2242
2243 let default_value = if field.optional {
2244 if let Some(default) = &field.default {
2246 format_optional_value(&field.ty, default)
2248 } else {
2249 "Optional.empty()".to_string()
2251 }
2252 } else {
2253 if let Some(default) = &field.default {
2255 default.clone()
2256 } else {
2257 match &field.ty {
2258 TypeRef::String | TypeRef::Char | TypeRef::Path => "\"\"".to_string(),
2259 TypeRef::Json => "null".to_string(),
2260 TypeRef::Bytes => "new byte[0]".to_string(),
2261 TypeRef::Primitive(p) => match p {
2262 PrimitiveType::Bool => "false".to_string(),
2263 PrimitiveType::F32 | PrimitiveType::F64 => "0.0".to_string(),
2264 _ => "0".to_string(),
2265 },
2266 TypeRef::Vec(_) => "List.of()".to_string(),
2267 TypeRef::Map(_, _) => "Map.of()".to_string(),
2268 TypeRef::Optional(_) => "Optional.empty()".to_string(),
2269 TypeRef::Duration => "null".to_string(),
2270 _ => "null".to_string(),
2271 }
2272 }
2273 };
2274
2275 writeln!(body, " private {} {} = {};", field_type, field_name, default_value).ok();
2276 }
2277
2278 writeln!(body).ok();
2279
2280 for field in &typ.fields {
2282 if field.name.starts_with('_') && field.name[1..].chars().all(|c| c.is_ascii_digit())
2284 || field.name.chars().next().is_none_or(|c| c.is_ascii_digit())
2285 {
2286 continue;
2287 }
2288
2289 let field_name = safe_java_field_name(&field.name);
2290 let field_name_pascal = to_class_name(&field.name);
2291 let field_type = if field.optional {
2292 format!("Optional<{}>", java_boxed_type(&field.ty))
2293 } else if matches!(field.ty, TypeRef::Duration) {
2294 java_boxed_type(&field.ty).to_string()
2295 } else {
2296 java_type(&field.ty).to_string()
2297 };
2298
2299 writeln!(
2300 body,
2301 " public {}Builder with{}({} value) {{",
2302 typ.name, field_name_pascal, field_type
2303 )
2304 .ok();
2305 writeln!(body, " this.{} = value;", field_name).ok();
2306 writeln!(body, " return this;").ok();
2307 writeln!(body, " }}").ok();
2308 writeln!(body).ok();
2309 }
2310
2311 writeln!(body, " public {} build() {{", typ.name).ok();
2313 writeln!(body, " return new {}(", typ.name).ok();
2314 let non_tuple_fields: Vec<_> = typ
2315 .fields
2316 .iter()
2317 .filter(|f| {
2318 !(f.name.starts_with('_') && f.name[1..].chars().all(|c| c.is_ascii_digit())
2320 || f.name.chars().next().is_none_or(|c| c.is_ascii_digit()))
2321 })
2322 .collect();
2323 for (i, field) in non_tuple_fields.iter().enumerate() {
2324 let field_name = safe_java_field_name(&field.name);
2325 let comma = if i < non_tuple_fields.len() - 1 { "," } else { "" };
2326 writeln!(body, " {}{}", field_name, comma).ok();
2327 }
2328 writeln!(body, " );").ok();
2329 writeln!(body, " }}").ok();
2330
2331 writeln!(body, "}}").ok();
2332
2333 let mut out = String::with_capacity(body.len() + 512);
2335
2336 writeln!(out, "// DO NOT EDIT - auto-generated by alef").ok();
2337 writeln!(out, "package {};", package).ok();
2338 writeln!(out).ok();
2339
2340 if body.contains("List<") {
2341 writeln!(out, "import java.util.List;").ok();
2342 }
2343 if body.contains("Map<") {
2344 writeln!(out, "import java.util.Map;").ok();
2345 }
2346 if body.contains("Optional<") {
2347 writeln!(out, "import java.util.Optional;").ok();
2348 }
2349
2350 writeln!(out).ok();
2351 out.push_str(&body);
2352
2353 out
2354}