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