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::collections::HashSet;
9use std::fmt::Write;
10use std::path::PathBuf;
11
12const JAVA_OBJECT_METHOD_NAMES: &[&str] = &[
15 "wait",
16 "notify",
17 "notifyAll",
18 "getClass",
19 "hashCode",
20 "equals",
21 "toString",
22 "clone",
23 "finalize",
24];
25
26fn escape_javadoc_line(s: &str) -> String {
32 let mut result = String::with_capacity(s.len());
33 let mut chars = s.chars().peekable();
34 while let Some(ch) = chars.next() {
35 if ch == '`' {
36 let mut code = String::new();
37 for c in chars.by_ref() {
38 if c == '`' {
39 break;
40 }
41 code.push(c);
42 }
43 result.push_str("{@code ");
44 result.push_str(&code);
45 result.push('}');
46 } else if ch == '<' {
47 result.push_str("<");
48 } else if ch == '>' {
49 result.push_str(">");
50 } else if ch == '&' {
51 result.push_str("&");
52 } else if ch == '*' && chars.peek() == Some(&'/') {
53 chars.next();
54 result.push_str("* /");
55 } else if ch == '@' {
56 result.push_str("{@literal @}");
57 } else {
58 result.push(ch);
59 }
60 }
61 result
62}
63
64fn is_tuple_field_name(name: &str) -> bool {
65 let stripped = name.trim_start_matches('_');
66 !stripped.is_empty() && stripped.chars().all(|c| c.is_ascii_digit())
67}
68
69fn safe_java_field_name(name: &str) -> String {
73 let java_name = to_java_name(name);
74 if JAVA_OBJECT_METHOD_NAMES.contains(&java_name.as_str()) {
75 format!("{}Value", java_name)
76 } else {
77 java_name
78 }
79}
80
81pub struct JavaBackend;
82
83impl JavaBackend {
84 fn resolve_main_class(api: &ApiSurface) -> String {
90 let base = to_class_name(&api.crate_name.replace('-', "_"));
91 if base.ends_with("Rs") {
92 base
93 } else {
94 format!("{}Rs", base)
95 }
96 }
97}
98
99impl Backend for JavaBackend {
100 fn name(&self) -> &str {
101 "java"
102 }
103
104 fn language(&self) -> Language {
105 Language::Java
106 }
107
108 fn capabilities(&self) -> Capabilities {
109 Capabilities {
110 supports_async: true,
111 supports_classes: true,
112 supports_enums: true,
113 supports_option: true,
114 supports_result: true,
115 ..Capabilities::default()
116 }
117 }
118
119 fn generate_bindings(&self, api: &ApiSurface, config: &AlefConfig) -> anyhow::Result<Vec<GeneratedFile>> {
120 let package = config.java_package();
121 let prefix = config.ffi_prefix();
122 let main_class = Self::resolve_main_class(api);
123 let package_path = package.replace('.', "/");
124
125 let output_dir = resolve_output_dir(
126 config.output.java.as_ref(),
127 &config.crate_config.name,
128 "packages/java/src/main/java/",
129 );
130
131 let base_path = PathBuf::from(&output_dir).join(&package_path);
132
133 let bridge_param_names: HashSet<String> = config
136 .trait_bridges
137 .iter()
138 .filter_map(|b| b.param_name.clone())
139 .collect();
140 let bridge_type_aliases: HashSet<String> = config
141 .trait_bridges
142 .iter()
143 .filter_map(|b| b.type_alias.clone())
144 .collect();
145 let has_visitor_bridge = !config.trait_bridges.is_empty();
146
147 let mut files = Vec::new();
148
149 let description = config
151 .scaffold
152 .as_ref()
153 .and_then(|s| s.description.as_deref())
154 .unwrap_or("High-performance HTML to Markdown converter.");
155 files.push(GeneratedFile {
156 path: base_path.join("package-info.java"),
157 content: format!(
158 "/**\n * {description}\n */\npackage {package};\n",
159 description = description,
160 package = package,
161 ),
162 generated_header: true,
163 });
164
165 files.push(GeneratedFile {
167 path: base_path.join("NativeLib.java"),
168 content: gen_native_lib(api, config, &package, &prefix, has_visitor_bridge),
169 generated_header: true,
170 });
171
172 files.push(GeneratedFile {
174 path: base_path.join(format!("{}.java", main_class)),
175 content: gen_main_class(
176 api,
177 config,
178 &package,
179 &main_class,
180 &prefix,
181 &bridge_param_names,
182 &bridge_type_aliases,
183 has_visitor_bridge,
184 ),
185 generated_header: true,
186 });
187
188 files.push(GeneratedFile {
190 path: base_path.join(format!("{}Exception.java", main_class)),
191 content: gen_exception_class(&package, &main_class),
192 generated_header: true,
193 });
194
195 let complex_enums: AHashSet<String> = api
199 .enums
200 .iter()
201 .filter(|e| e.serde_tag.is_none() && e.variants.iter().any(|v| !v.fields.is_empty()))
202 .map(|e| e.name.clone())
203 .collect();
204
205 let lang_rename_all = config.serde_rename_all_for_language(Language::Java);
207
208 for typ in api.types.iter().filter(|typ| !typ.is_trait) {
210 if !typ.is_opaque && !typ.fields.is_empty() {
211 if has_visitor_bridge && (typ.name == "NodeContext" || typ.name == "VisitResult") {
213 continue;
214 }
215 files.push(GeneratedFile {
216 path: base_path.join(format!("{}.java", typ.name)),
217 content: gen_record_type(&package, typ, &complex_enums, &lang_rename_all),
218 generated_header: true,
219 });
220 if typ.has_default {
222 files.push(GeneratedFile {
223 path: base_path.join(format!("{}Builder.java", typ.name)),
224 content: gen_builder_class(&package, typ),
225 generated_header: true,
226 });
227 }
228 }
229 }
230
231 let builder_class_names: AHashSet<String> = api
234 .types
235 .iter()
236 .filter(|t| !t.is_opaque && !t.fields.is_empty() && t.has_default)
237 .map(|t| format!("{}Builder", t.name))
238 .collect();
239
240 for typ in api.types.iter().filter(|typ| !typ.is_trait) {
242 if typ.is_opaque && !builder_class_names.contains(&typ.name) {
243 files.push(GeneratedFile {
244 path: base_path.join(format!("{}.java", typ.name)),
245 content: gen_opaque_handle_class(&package, typ, &prefix),
246 generated_header: true,
247 });
248 }
249 }
250
251 for enum_def in &api.enums {
253 if has_visitor_bridge && enum_def.name == "VisitResult" {
255 continue;
256 }
257 files.push(GeneratedFile {
258 path: base_path.join(format!("{}.java", enum_def.name)),
259 content: gen_enum_class(&package, enum_def),
260 generated_header: true,
261 });
262 }
263
264 for error in &api.errors {
266 for (class_name, content) in alef_codegen::error_gen::gen_java_error_types(error, &package) {
267 files.push(GeneratedFile {
268 path: base_path.join(format!("{}.java", class_name)),
269 content,
270 generated_header: true,
271 });
272 }
273 }
274
275 if has_visitor_bridge {
277 for (filename, content) in crate::gen_visitor::gen_visitor_files(&package, &main_class) {
278 files.push(GeneratedFile {
279 path: base_path.join(filename),
280 content,
281 generated_header: false, });
283 }
284 }
285
286 let _adapter_bodies = alef_adapters::build_adapter_bodies(config, Language::Java)?;
288
289 Ok(files)
290 }
291
292 fn generate_public_api(&self, api: &ApiSurface, config: &AlefConfig) -> anyhow::Result<Vec<GeneratedFile>> {
293 let package = config.java_package();
294 let prefix = config.ffi_prefix();
295 let main_class = Self::resolve_main_class(api);
296 let package_path = package.replace('.', "/");
297
298 let output_dir = resolve_output_dir(
299 config.output.java.as_ref(),
300 &config.crate_config.name,
301 "packages/java/src/main/java/",
302 );
303
304 let base_path = PathBuf::from(&output_dir).join(&package_path);
305
306 let bridge_param_names: HashSet<String> = config
308 .trait_bridges
309 .iter()
310 .filter_map(|b| b.param_name.clone())
311 .collect();
312 let bridge_type_aliases: HashSet<String> = config
313 .trait_bridges
314 .iter()
315 .filter_map(|b| b.type_alias.clone())
316 .collect();
317 let has_visitor_bridge = !config.trait_bridges.is_empty();
318
319 let public_class = main_class.trim_end_matches("Rs").to_string();
322 let facade_content = gen_facade_class(
323 api,
324 &package,
325 &public_class,
326 &main_class,
327 &prefix,
328 &bridge_param_names,
329 &bridge_type_aliases,
330 has_visitor_bridge,
331 );
332
333 Ok(vec![GeneratedFile {
334 path: base_path.join(format!("{}.java", public_class)),
335 content: facade_content,
336 generated_header: true,
337 }])
338 }
339
340 fn build_config(&self) -> Option<BuildConfig> {
341 Some(BuildConfig {
342 tool: "mvn",
343 crate_suffix: "",
344 depends_on_ffi: true,
345 post_build: vec![],
346 })
347 }
348}
349
350fn gen_native_lib(
355 api: &ApiSurface,
356 config: &AlefConfig,
357 package: &str,
358 prefix: &str,
359 has_visitor_bridge: bool,
360) -> String {
361 let mut body = String::with_capacity(2048);
363 let lib_name = config.ffi_lib_name();
366
367 writeln!(body, "final class NativeLib {{").ok();
368 writeln!(body, " private static final Linker LINKER = Linker.nativeLinker();").ok();
369 writeln!(body, " private static final SymbolLookup LIB;").ok();
370 writeln!(
371 body,
372 " private static final String NATIVES_RESOURCE_ROOT = \"/natives\";"
373 )
374 .ok();
375 writeln!(
376 body,
377 " private static final Object NATIVE_EXTRACT_LOCK = new Object();"
378 )
379 .ok();
380 writeln!(body, " private static String cachedExtractKey;").ok();
381 writeln!(body, " private static Path cachedExtractDir;").ok();
382 writeln!(body).ok();
383 writeln!(body, " static {{").ok();
384 writeln!(body, " loadNativeLibrary();").ok();
385 writeln!(body, " LIB = SymbolLookup.loaderLookup();").ok();
386 writeln!(body, " }}").ok();
387 writeln!(body).ok();
388 writeln!(body, " private static void loadNativeLibrary() {{").ok();
389 writeln!(
390 body,
391 " String osName = System.getProperty(\"os.name\", \"\").toLowerCase(java.util.Locale.ROOT);"
392 )
393 .ok();
394 writeln!(
395 body,
396 " String osArch = System.getProperty(\"os.arch\", \"\").toLowerCase(java.util.Locale.ROOT);"
397 )
398 .ok();
399 writeln!(body).ok();
400 writeln!(body, " String libName;").ok();
401 writeln!(body, " String libExt;").ok();
402 writeln!(
403 body,
404 " if (osName.contains(\"mac\") || osName.contains(\"darwin\")) {{"
405 )
406 .ok();
407 writeln!(body, " libName = \"lib{}\";", lib_name).ok();
408 writeln!(body, " libExt = \".dylib\";").ok();
409 writeln!(body, " }} else if (osName.contains(\"win\")) {{").ok();
410 writeln!(body, " libName = \"{}\";", lib_name).ok();
411 writeln!(body, " libExt = \".dll\";").ok();
412 writeln!(body, " }} else {{").ok();
413 writeln!(body, " libName = \"lib{}\";", lib_name).ok();
414 writeln!(body, " libExt = \".so\";").ok();
415 writeln!(body, " }}").ok();
416 writeln!(body).ok();
417 writeln!(body, " String nativesRid = resolveNativesRid(osName, osArch);").ok();
418 writeln!(
419 body,
420 " String nativesDir = NATIVES_RESOURCE_ROOT + \"/\" + nativesRid;"
421 )
422 .ok();
423 writeln!(body).ok();
424 writeln!(
425 body,
426 " Path extracted = tryExtractAndLoadFromResources(nativesDir, libName, libExt);"
427 )
428 .ok();
429 writeln!(body, " if (extracted != null) {{").ok();
430 writeln!(body, " return;").ok();
431 writeln!(body, " }}").ok();
432 writeln!(body).ok();
433 writeln!(body, " try {{").ok();
434 writeln!(body, " System.loadLibrary(\"{}\");", lib_name).ok();
435 writeln!(body, " }} catch (UnsatisfiedLinkError e) {{").ok();
436 writeln!(
437 body,
438 " String msg = \"Failed to load {} native library. Expected resource: \" + nativesDir + \"/\" + libName",
439 lib_name
440 ).ok();
441 writeln!(
442 body,
443 " + libExt + \" (RID: \" + nativesRid + \"). \""
444 )
445 .ok();
446 writeln!(
447 body,
448 " + \"Ensure the library is bundled in the JAR under natives/{{os-arch}}/, \""
449 )
450 .ok();
451 writeln!(
452 body,
453 " + \"or place it on the system library path (java.library.path).\";",
454 )
455 .ok();
456 writeln!(
457 body,
458 " UnsatisfiedLinkError out = new UnsatisfiedLinkError(msg + \" Original error: \" + e.getMessage());"
459 )
460 .ok();
461 writeln!(body, " out.initCause(e);").ok();
462 writeln!(body, " throw out;").ok();
463 writeln!(body, " }}").ok();
464 writeln!(body, " }}").ok();
465 writeln!(body).ok();
466 writeln!(
467 body,
468 " private static Path tryExtractAndLoadFromResources(String nativesDir, String libName, String libExt) {{"
469 )
470 .ok();
471 writeln!(
472 body,
473 " String resourcePath = nativesDir + \"/\" + libName + libExt;"
474 )
475 .ok();
476 writeln!(
477 body,
478 " URL resource = NativeLib.class.getResource(resourcePath);"
479 )
480 .ok();
481 writeln!(body, " if (resource == null) {{").ok();
482 writeln!(body, " return null;").ok();
483 writeln!(body, " }}").ok();
484 writeln!(body).ok();
485 writeln!(body, " try {{").ok();
486 writeln!(
487 body,
488 " Path tempDir = extractOrReuseNativeDirectory(nativesDir);"
489 )
490 .ok();
491 writeln!(body, " Path libPath = tempDir.resolve(libName + libExt);").ok();
492 writeln!(body, " if (!Files.exists(libPath)) {{").ok();
493 writeln!(
494 body,
495 " throw new UnsatisfiedLinkError(\"Missing extracted native library: \" + libPath);"
496 )
497 .ok();
498 writeln!(body, " }}").ok();
499 writeln!(body, " System.load(libPath.toAbsolutePath().toString());").ok();
500 writeln!(body, " return libPath;").ok();
501 writeln!(body, " }} catch (Exception e) {{").ok();
502 writeln!(body, " System.err.println(\"[NativeLib] Failed to extract and load native library from resources: \" + e.getMessage());").ok();
503 writeln!(body, " return null;").ok();
504 writeln!(body, " }}").ok();
505 writeln!(body, " }}").ok();
506 writeln!(body).ok();
507 writeln!(
508 body,
509 " private static Path extractOrReuseNativeDirectory(String nativesDir) throws Exception {{"
510 )
511 .ok();
512 writeln!(
513 body,
514 " URL location = NativeLib.class.getProtectionDomain().getCodeSource().getLocation();"
515 )
516 .ok();
517 writeln!(body, " if (location == null) {{").ok();
518 writeln!(
519 body,
520 " throw new IllegalStateException(\"Missing code source location for {} JAR\");",
521 lib_name
522 )
523 .ok();
524 writeln!(body, " }}").ok();
525 writeln!(body).ok();
526 writeln!(body, " Path codePath = Path.of(location.toURI());").ok();
527 writeln!(
528 body,
529 " String key = codePath.toAbsolutePath() + \"::\" + nativesDir;"
530 )
531 .ok();
532 writeln!(body).ok();
533 writeln!(body, " synchronized (NATIVE_EXTRACT_LOCK) {{").ok();
534 writeln!(
535 body,
536 " if (cachedExtractDir != null && key.equals(cachedExtractKey)) {{"
537 )
538 .ok();
539 writeln!(body, " return cachedExtractDir;").ok();
540 writeln!(body, " }}").ok();
541 writeln!(
542 body,
543 " Path tempDir = Files.createTempDirectory(\"{}_native\");",
544 lib_name
545 )
546 .ok();
547 writeln!(body, " tempDir.toFile().deleteOnExit();").ok();
548 writeln!(
549 body,
550 " List<Path> extracted = extractNativeDirectory(codePath, nativesDir, tempDir);"
551 )
552 .ok();
553 writeln!(body, " if (extracted.isEmpty()) {{").ok();
554 writeln!(body, " throw new IllegalStateException(\"No native files extracted from resources dir: \" + nativesDir);").ok();
555 writeln!(body, " }}").ok();
556 writeln!(body, " cachedExtractKey = key;").ok();
557 writeln!(body, " cachedExtractDir = tempDir;").ok();
558 writeln!(body, " return tempDir;").ok();
559 writeln!(body, " }}").ok();
560 writeln!(body, " }}").ok();
561 writeln!(body).ok();
562 writeln!(body, " private static List<Path> extractNativeDirectory(Path codePath, String nativesDir, Path destDir) throws Exception {{").ok();
563 writeln!(
564 body,
565 " if (!Files.exists(destDir) || !Files.isDirectory(destDir)) {{"
566 )
567 .ok();
568 writeln!(
569 body,
570 " throw new IllegalArgumentException(\"Destination directory does not exist: \" + destDir);"
571 )
572 .ok();
573 writeln!(body, " }}").ok();
574 writeln!(body).ok();
575 writeln!(
576 body,
577 " String prefix = nativesDir.startsWith(\"/\") ? nativesDir.substring(1) : nativesDir;"
578 )
579 .ok();
580 writeln!(body, " if (!prefix.endsWith(\"/\")) {{").ok();
581 writeln!(body, " prefix = prefix + \"/\";").ok();
582 writeln!(body, " }}").ok();
583 writeln!(body).ok();
584 writeln!(body, " if (Files.isDirectory(codePath)) {{").ok();
585 writeln!(body, " Path nativesPath = codePath.resolve(prefix);").ok();
586 writeln!(
587 body,
588 " if (!Files.exists(nativesPath) || !Files.isDirectory(nativesPath)) {{"
589 )
590 .ok();
591 writeln!(body, " return List.of();").ok();
592 writeln!(body, " }}").ok();
593 writeln!(body, " return copyDirectory(nativesPath, destDir);").ok();
594 writeln!(body, " }}").ok();
595 writeln!(body).ok();
596 writeln!(body, " List<Path> extracted = new ArrayList<>();").ok();
597 writeln!(body, " try (JarFile jar = new JarFile(codePath.toFile())) {{").ok();
598 writeln!(body, " Enumeration<JarEntry> entries = jar.entries();").ok();
599 writeln!(body, " while (entries.hasMoreElements()) {{").ok();
600 writeln!(body, " JarEntry entry = entries.nextElement();").ok();
601 writeln!(body, " String name = entry.getName();").ok();
602 writeln!(
603 body,
604 " if (!name.startsWith(prefix) || entry.isDirectory()) {{"
605 )
606 .ok();
607 writeln!(body, " continue;").ok();
608 writeln!(body, " }}").ok();
609 writeln!(
610 body,
611 " String relative = name.substring(prefix.length());"
612 )
613 .ok();
614 writeln!(body, " Path out = safeResolve(destDir, relative);").ok();
615 writeln!(body, " Files.createDirectories(out.getParent());").ok();
616 writeln!(body, " try (var in = jar.getInputStream(entry)) {{").ok();
617 writeln!(
618 body,
619 " Files.copy(in, out, StandardCopyOption.REPLACE_EXISTING);"
620 )
621 .ok();
622 writeln!(body, " }}").ok();
623 writeln!(body, " out.toFile().deleteOnExit();").ok();
624 writeln!(body, " extracted.add(out);").ok();
625 writeln!(body, " }}").ok();
626 writeln!(body, " }}").ok();
627 writeln!(body, " return extracted;").ok();
628 writeln!(body, " }}").ok();
629 writeln!(body).ok();
630 writeln!(
631 body,
632 " private static List<Path> copyDirectory(Path srcDir, Path destDir) throws Exception {{"
633 )
634 .ok();
635 writeln!(body, " List<Path> copied = new ArrayList<>();").ok();
636 writeln!(body, " try (var paths = Files.walk(srcDir)) {{").ok();
637 writeln!(body, " for (Path src : (Iterable<Path>) paths::iterator) {{").ok();
638 writeln!(body, " if (Files.isDirectory(src)) {{").ok();
639 writeln!(body, " continue;").ok();
640 writeln!(body, " }}").ok();
641 writeln!(body, " Path relative = srcDir.relativize(src);").ok();
642 writeln!(
643 body,
644 " Path out = safeResolve(destDir, relative.toString());"
645 )
646 .ok();
647 writeln!(body, " Files.createDirectories(out.getParent());").ok();
648 writeln!(
649 body,
650 " Files.copy(src, out, StandardCopyOption.REPLACE_EXISTING);"
651 )
652 .ok();
653 writeln!(body, " out.toFile().deleteOnExit();").ok();
654 writeln!(body, " copied.add(out);").ok();
655 writeln!(body, " }}").ok();
656 writeln!(body, " }}").ok();
657 writeln!(body, " return copied;").ok();
658 writeln!(body, " }}").ok();
659 writeln!(body).ok();
660 writeln!(
661 body,
662 " private static Path safeResolve(Path destDir, String relative) throws Exception {{"
663 )
664 .ok();
665 writeln!(
666 body,
667 " Path normalizedDest = destDir.toAbsolutePath().normalize();"
668 )
669 .ok();
670 writeln!(body, " Path out = normalizedDest.resolve(relative).normalize();").ok();
671 writeln!(body, " if (!out.startsWith(normalizedDest)) {{").ok();
672 writeln!(body, " throw new SecurityException(\"Blocked extracting native file outside destination directory: \" + relative);").ok();
673 writeln!(body, " }}").ok();
674 writeln!(body, " return out;").ok();
675 writeln!(body, " }}").ok();
676 writeln!(body).ok();
677 writeln!(
678 body,
679 " private static String resolveNativesRid(String osName, String osArch) {{"
680 )
681 .ok();
682 writeln!(body, " String arch;").ok();
683 writeln!(
684 body,
685 " if (osArch.contains(\"aarch64\") || osArch.contains(\"arm64\")) {{"
686 )
687 .ok();
688 writeln!(body, " arch = \"arm64\";").ok();
689 writeln!(
690 body,
691 " }} else if (osArch.contains(\"x86_64\") || osArch.contains(\"amd64\")) {{"
692 )
693 .ok();
694 writeln!(body, " arch = \"x86_64\";").ok();
695 writeln!(body, " }} else {{").ok();
696 writeln!(body, " arch = osArch.replaceAll(\"[^a-z0-9_]+\", \"\");").ok();
697 writeln!(body, " }}").ok();
698 writeln!(body).ok();
699 writeln!(body, " String os;").ok();
700 writeln!(
701 body,
702 " if (osName.contains(\"mac\") || osName.contains(\"darwin\")) {{"
703 )
704 .ok();
705 writeln!(body, " os = \"macos\";").ok();
706 writeln!(body, " }} else if (osName.contains(\"win\")) {{").ok();
707 writeln!(body, " os = \"windows\";").ok();
708 writeln!(body, " }} else {{").ok();
709 writeln!(body, " os = \"linux\";").ok();
710 writeln!(body, " }}").ok();
711 writeln!(body).ok();
712 writeln!(body, " return os + \"-\" + arch;").ok();
713 writeln!(body, " }}").ok();
714 writeln!(body).ok();
715
716 for func in &api.functions {
720 let ffi_name = format!("{}_{}", prefix, func.name.to_lowercase());
721 let return_layout = gen_ffi_layout(&func.return_type);
722 let param_layouts: Vec<String> = func.params.iter().map(|p| gen_ffi_layout(&p.ty)).collect();
723
724 let layout_str = gen_function_descriptor(&return_layout, ¶m_layouts);
725
726 let handle_name = format!("{}_{}", prefix.to_uppercase(), func.name.to_uppercase());
727
728 writeln!(
729 body,
730 " static final MethodHandle {} = LINKER.downcallHandle(",
731 handle_name
732 )
733 .ok();
734 writeln!(body, " LIB.find(\"{}\").orElseThrow(),", ffi_name).ok();
735 writeln!(body, " {}", layout_str).ok();
736 writeln!(body, " );").ok();
737 }
738
739 {
741 let free_name = format!("{}_free_string", prefix);
742 let handle_name = format!("{}_FREE_STRING", prefix.to_uppercase());
743 writeln!(body).ok();
744 writeln!(
745 body,
746 " static final MethodHandle {} = LINKER.downcallHandle(",
747 handle_name
748 )
749 .ok();
750 writeln!(body, " LIB.find(\"{}\").orElseThrow(),", free_name).ok();
751 writeln!(body, " FunctionDescriptor.ofVoid(ValueLayout.ADDRESS)").ok();
752 writeln!(body, " );").ok();
753 }
754
755 {
757 writeln!(
758 body,
759 " static final MethodHandle {}_LAST_ERROR_CODE = LINKER.downcallHandle(",
760 prefix.to_uppercase()
761 )
762 .ok();
763 writeln!(body, " LIB.find(\"{}_last_error_code\").orElseThrow(),", prefix).ok();
764 writeln!(body, " FunctionDescriptor.of(ValueLayout.JAVA_INT)").ok();
765 writeln!(body, " );").ok();
766
767 writeln!(
768 body,
769 " static final MethodHandle {}_LAST_ERROR_CONTEXT = LINKER.downcallHandle(",
770 prefix.to_uppercase()
771 )
772 .ok();
773 writeln!(
774 body,
775 " LIB.find(\"{}_last_error_context\").orElseThrow(),",
776 prefix
777 )
778 .ok();
779 writeln!(body, " FunctionDescriptor.of(ValueLayout.ADDRESS)").ok();
780 writeln!(body, " );").ok();
781 }
782
783 let mut emitted_free_handles: AHashSet<String> = AHashSet::new();
786
787 let opaque_type_names: AHashSet<String> = api
789 .types
790 .iter()
791 .filter(|t| t.is_opaque)
792 .map(|t| t.name.clone())
793 .collect();
794
795 for func in &api.functions {
797 if let TypeRef::Named(name) = &func.return_type {
798 let type_snake = name.to_snake_case();
799 let type_upper = type_snake.to_uppercase();
800 let is_opaque = opaque_type_names.contains(name.as_str());
801
802 if is_opaque {
803 } else {
806 let to_json_handle = format!("{}_{}_TO_JSON", prefix.to_uppercase(), type_upper);
810 let to_json_ffi = format!("{}_{}_to_json", prefix, type_snake);
811 writeln!(body).ok();
812 writeln!(
813 body,
814 " static final MethodHandle {} = LINKER.downcallHandle(",
815 to_json_handle
816 )
817 .ok();
818 writeln!(body, " LIB.find(\"{}\").orElseThrow(),", to_json_ffi).ok();
819 writeln!(
820 body,
821 " FunctionDescriptor.of(ValueLayout.ADDRESS, ValueLayout.ADDRESS)"
822 )
823 .ok();
824 writeln!(body, " );").ok();
825 }
826
827 let free_handle = format!("{}_{}_FREE", prefix.to_uppercase(), type_upper);
829 let free_ffi = format!("{}_{}_free", prefix, type_snake);
830 if emitted_free_handles.insert(free_handle.clone()) {
831 writeln!(body).ok();
832 writeln!(
833 body,
834 " static final MethodHandle {} = LINKER.downcallHandle(",
835 free_handle
836 )
837 .ok();
838 writeln!(body, " LIB.find(\"{}\").orElseThrow(),", free_ffi).ok();
839 writeln!(body, " FunctionDescriptor.ofVoid(ValueLayout.ADDRESS)").ok();
840 writeln!(body, " );").ok();
841 }
842 }
843 }
844
845 let mut emitted_from_json_handles: AHashSet<String> = AHashSet::new();
848 for func in &api.functions {
849 for param in &func.params {
850 let inner_name = match ¶m.ty {
852 TypeRef::Named(n) => Some(n.clone()),
853 TypeRef::Optional(inner) => {
854 if let TypeRef::Named(n) = inner.as_ref() {
855 Some(n.clone())
856 } else {
857 None
858 }
859 }
860 _ => None,
861 };
862 if let Some(name) = inner_name {
863 if !opaque_type_names.contains(name.as_str()) {
864 let type_snake = name.to_snake_case();
865 let type_upper = type_snake.to_uppercase();
866
867 let from_json_handle = format!("{}_{}_FROM_JSON", prefix.to_uppercase(), type_upper);
869 let from_json_ffi = format!("{}_{}_from_json", prefix, type_snake);
870 if emitted_from_json_handles.insert(from_json_handle.clone()) {
871 writeln!(body).ok();
872 writeln!(
873 body,
874 " static final MethodHandle {} = LINKER.downcallHandle(",
875 from_json_handle
876 )
877 .ok();
878 writeln!(body, " LIB.find(\"{}\").orElseThrow(),", from_json_ffi).ok();
879 writeln!(
880 body,
881 " FunctionDescriptor.of(ValueLayout.ADDRESS, ValueLayout.ADDRESS)"
882 )
883 .ok();
884 writeln!(body, " );").ok();
885 }
886
887 let free_handle = format!("{}_{}_FREE", prefix.to_uppercase(), type_upper);
889 let free_ffi = format!("{}_{}_free", prefix, type_snake);
890 if emitted_free_handles.insert(free_handle.clone()) {
891 writeln!(body).ok();
892 writeln!(
893 body,
894 " static final MethodHandle {} = LINKER.downcallHandle(",
895 free_handle
896 )
897 .ok();
898 writeln!(body, " LIB.find(\"{}\").orElseThrow(),", free_ffi).ok();
899 writeln!(body, " FunctionDescriptor.ofVoid(ValueLayout.ADDRESS)").ok();
900 writeln!(body, " );").ok();
901 }
902 }
903 }
904 }
905 }
906
907 let builder_class_names: AHashSet<String> = api
910 .types
911 .iter()
912 .filter(|t| !t.is_opaque && !t.fields.is_empty() && t.has_default)
913 .map(|t| format!("{}Builder", t.name))
914 .collect();
915
916 for typ in api.types.iter().filter(|typ| !typ.is_trait) {
918 if typ.is_opaque && !builder_class_names.contains(&typ.name) {
919 let type_snake = typ.name.to_snake_case();
920 let type_upper = type_snake.to_uppercase();
921 let free_handle = format!("{}_{}_FREE", prefix.to_uppercase(), type_upper);
922 let free_ffi = format!("{}_{}_free", prefix, type_snake);
923 if emitted_free_handles.insert(free_handle.clone()) {
924 writeln!(body).ok();
925 writeln!(
926 body,
927 " static final MethodHandle {} = LINKER.downcallHandle(",
928 free_handle
929 )
930 .ok();
931 writeln!(body, " LIB.find(\"{}\").orElseThrow(),", free_ffi).ok();
932 writeln!(body, " FunctionDescriptor.ofVoid(ValueLayout.ADDRESS)").ok();
933 writeln!(body, " );").ok();
934 }
935 }
936 }
937
938 if has_visitor_bridge {
940 body.push_str(&crate::gen_visitor::gen_native_lib_visitor_handles(prefix));
941 }
942
943 writeln!(body, "}}").ok();
944
945 let mut out = String::with_capacity(body.len() + 512);
947
948 writeln!(out, "// DO NOT EDIT - auto-generated by alef").ok();
949 writeln!(out, "package {};", package).ok();
950 writeln!(out).ok();
951 if body.contains("Arena") {
952 writeln!(out, "import java.lang.foreign.Arena;").ok();
953 }
954 if body.contains("FunctionDescriptor") {
955 writeln!(out, "import java.lang.foreign.FunctionDescriptor;").ok();
956 }
957 if body.contains("Linker") {
958 writeln!(out, "import java.lang.foreign.Linker;").ok();
959 }
960 if body.contains("MemorySegment") {
961 writeln!(out, "import java.lang.foreign.MemorySegment;").ok();
962 }
963 if body.contains("SymbolLookup") {
964 writeln!(out, "import java.lang.foreign.SymbolLookup;").ok();
965 }
966 if body.contains("ValueLayout") {
967 writeln!(out, "import java.lang.foreign.ValueLayout;").ok();
968 }
969 if body.contains("MethodHandle") {
970 writeln!(out, "import java.lang.invoke.MethodHandle;").ok();
971 }
972 writeln!(out, "import java.net.URL;").ok();
974 writeln!(out, "import java.nio.file.Files;").ok();
975 writeln!(out, "import java.nio.file.Path;").ok();
976 writeln!(out, "import java.nio.file.StandardCopyOption;").ok();
977 writeln!(out, "import java.util.ArrayList;").ok();
978 writeln!(out, "import java.util.Enumeration;").ok();
979 writeln!(out, "import java.util.List;").ok();
980 writeln!(out, "import java.util.jar.JarEntry;").ok();
981 writeln!(out, "import java.util.jar.JarFile;").ok();
982 writeln!(out).ok();
983
984 out.push_str(&body);
985
986 out
987}
988
989#[allow(clippy::too_many_arguments)]
994fn gen_main_class(
995 api: &ApiSurface,
996 _config: &AlefConfig,
997 package: &str,
998 class_name: &str,
999 prefix: &str,
1000 bridge_param_names: &HashSet<String>,
1001 bridge_type_aliases: &HashSet<String>,
1002 has_visitor_bridge: bool,
1003) -> String {
1004 let opaque_types: AHashSet<String> = api
1006 .types
1007 .iter()
1008 .filter(|t| t.is_opaque)
1009 .map(|t| t.name.clone())
1010 .collect();
1011
1012 let mut body = String::with_capacity(4096);
1014
1015 writeln!(body, "public final class {} {{", class_name).ok();
1016 writeln!(body, " private {}() {{ }}", class_name).ok();
1017 writeln!(body).ok();
1018
1019 for func in &api.functions {
1021 gen_sync_function_method(
1023 &mut body,
1024 func,
1025 prefix,
1026 class_name,
1027 &opaque_types,
1028 bridge_param_names,
1029 bridge_type_aliases,
1030 );
1031 writeln!(body).ok();
1032
1033 if func.is_async {
1035 gen_async_wrapper_method(&mut body, func, bridge_param_names, bridge_type_aliases);
1036 writeln!(body).ok();
1037 }
1038 }
1039
1040 if has_visitor_bridge {
1042 body.push_str(&crate::gen_visitor::gen_convert_with_visitor_method(class_name, prefix));
1043 writeln!(body).ok();
1044 }
1045
1046 gen_helper_methods(&mut body, prefix, class_name);
1048
1049 writeln!(body, "}}").ok();
1050
1051 let mut out = String::with_capacity(body.len() + 512);
1053
1054 writeln!(out, "// DO NOT EDIT - auto-generated by alef").ok();
1055 writeln!(out, "package {};", package).ok();
1056 writeln!(out).ok();
1057 if body.contains("Arena") {
1058 writeln!(out, "import java.lang.foreign.Arena;").ok();
1059 }
1060 if body.contains("FunctionDescriptor") {
1061 writeln!(out, "import java.lang.foreign.FunctionDescriptor;").ok();
1062 }
1063 if body.contains("Linker") {
1064 writeln!(out, "import java.lang.foreign.Linker;").ok();
1065 }
1066 if body.contains("MemorySegment") {
1067 writeln!(out, "import java.lang.foreign.MemorySegment;").ok();
1068 }
1069 if body.contains("SymbolLookup") {
1070 writeln!(out, "import java.lang.foreign.SymbolLookup;").ok();
1071 }
1072 if body.contains("ValueLayout") {
1073 writeln!(out, "import java.lang.foreign.ValueLayout;").ok();
1074 }
1075 if body.contains("List<") {
1076 writeln!(out, "import java.util.List;").ok();
1077 }
1078 if body.contains("Map<") {
1079 writeln!(out, "import java.util.Map;").ok();
1080 }
1081 if body.contains("Optional<") {
1082 writeln!(out, "import java.util.Optional;").ok();
1083 }
1084 if body.contains("HashMap<") || body.contains("new HashMap") {
1085 writeln!(out, "import java.util.HashMap;").ok();
1086 }
1087 if body.contains("CompletableFuture") {
1088 writeln!(out, "import java.util.concurrent.CompletableFuture;").ok();
1089 }
1090 if body.contains("CompletionException") {
1091 writeln!(out, "import java.util.concurrent.CompletionException;").ok();
1092 }
1093 if body.contains(" ObjectMapper") {
1097 writeln!(out, "import com.fasterxml.jackson.databind.ObjectMapper;").ok();
1098 }
1099 writeln!(out).ok();
1100
1101 out.push_str(&body);
1102
1103 out
1104}
1105
1106fn is_bridge_param_java(
1107 param: &alef_core::ir::ParamDef,
1108 bridge_param_names: &HashSet<String>,
1109 bridge_type_aliases: &HashSet<String>,
1110) -> bool {
1111 if bridge_param_names.contains(param.name.as_str()) {
1112 return true;
1113 }
1114 let type_name = match ¶m.ty {
1115 TypeRef::Named(n) => Some(n.as_str()),
1116 TypeRef::Optional(inner) => {
1117 if let TypeRef::Named(n) = inner.as_ref() {
1118 Some(n.as_str())
1119 } else {
1120 None
1121 }
1122 }
1123 _ => None,
1124 };
1125 type_name.is_some_and(|n| bridge_type_aliases.contains(n))
1126}
1127
1128fn gen_sync_function_method(
1129 out: &mut String,
1130 func: &FunctionDef,
1131 prefix: &str,
1132 class_name: &str,
1133 opaque_types: &AHashSet<String>,
1134 bridge_param_names: &HashSet<String>,
1135 bridge_type_aliases: &HashSet<String>,
1136) {
1137 let params: Vec<String> = func
1139 .params
1140 .iter()
1141 .filter(|p| !is_bridge_param_java(p, bridge_param_names, bridge_type_aliases))
1142 .map(|p| {
1143 let ptype = java_type(&p.ty);
1144 format!("final {} {}", ptype, to_java_name(&p.name))
1145 })
1146 .collect();
1147
1148 let return_type = java_type(&func.return_type);
1149
1150 writeln!(
1151 out,
1152 " public static {} {}({}) throws {}Exception {{",
1153 return_type,
1154 to_java_name(&func.name),
1155 params.join(", "),
1156 class_name
1157 )
1158 .ok();
1159
1160 writeln!(out, " try (var arena = Arena.ofConfined()) {{").ok();
1161
1162 let ffi_ptr_params: Vec<(String, String)> = func
1166 .params
1167 .iter()
1168 .filter(|p| !is_bridge_param_java(p, bridge_param_names, bridge_type_aliases))
1169 .filter_map(|p| {
1170 let inner_name = match &p.ty {
1171 TypeRef::Named(n) if !opaque_types.contains(n.as_str()) => Some(n.clone()),
1172 TypeRef::Optional(inner) => {
1173 if let TypeRef::Named(n) = inner.as_ref() {
1174 if !opaque_types.contains(n.as_str()) {
1175 Some(n.clone())
1176 } else {
1177 None
1178 }
1179 } else {
1180 None
1181 }
1182 }
1183 _ => None,
1184 };
1185 inner_name.map(|type_name| {
1186 let cname = "c".to_string() + &to_java_name(&p.name);
1187 let type_snake = type_name.to_snake_case();
1188 let free_handle = format!("NativeLib.{}_{}_FREE", prefix.to_uppercase(), type_snake.to_uppercase());
1189 (cname, free_handle)
1190 })
1191 })
1192 .collect();
1193
1194 for param in &func.params {
1196 if is_bridge_param_java(param, bridge_param_names, bridge_type_aliases) {
1197 continue;
1198 }
1199 marshal_param_to_ffi(out, &to_java_name(¶m.name), ¶m.ty, opaque_types, prefix);
1200 }
1201
1202 let ffi_handle = format!("NativeLib.{}_{}", prefix.to_uppercase(), func.name.to_uppercase());
1204
1205 let call_args: Vec<String> = func
1207 .params
1208 .iter()
1209 .map(|p| {
1210 if is_bridge_param_java(p, bridge_param_names, bridge_type_aliases) {
1211 "MemorySegment.NULL".to_string()
1212 } else {
1213 ffi_param_name(&to_java_name(&p.name), &p.ty, opaque_types)
1214 }
1215 })
1216 .collect();
1217
1218 let emit_ffi_ptr_cleanup = |out: &mut String| {
1220 for (cname, free_handle) in &ffi_ptr_params {
1221 writeln!(out, " if (!{}.equals(MemorySegment.NULL)) {{", cname).ok();
1222 writeln!(out, " {}.invoke({});", free_handle, cname).ok();
1223 writeln!(out, " }}").ok();
1224 }
1225 };
1226
1227 if matches!(func.return_type, TypeRef::Unit) {
1228 writeln!(out, " {}.invoke({});", ffi_handle, call_args.join(", ")).ok();
1229 emit_ffi_ptr_cleanup(out);
1230 writeln!(out, " }} catch (Throwable e) {{").ok();
1231 writeln!(
1232 out,
1233 " throw new {}Exception(\"FFI call failed\", e);",
1234 class_name
1235 )
1236 .ok();
1237 writeln!(out, " }}").ok();
1238 } else if is_ffi_string_return(&func.return_type) {
1239 let free_handle = format!("NativeLib.{}_FREE_STRING", prefix.to_uppercase());
1240 writeln!(
1241 out,
1242 " var resultPtr = (MemorySegment) {}.invoke({});",
1243 ffi_handle,
1244 call_args.join(", ")
1245 )
1246 .ok();
1247 emit_ffi_ptr_cleanup(out);
1248 writeln!(out, " if (resultPtr.equals(MemorySegment.NULL)) {{").ok();
1249 writeln!(out, " checkLastError();").ok();
1250 writeln!(out, " return null;").ok();
1251 writeln!(out, " }}").ok();
1252 writeln!(
1253 out,
1254 " String result = resultPtr.reinterpret(Long.MAX_VALUE).getString(0);"
1255 )
1256 .ok();
1257 writeln!(out, " {}.invoke(resultPtr);", free_handle).ok();
1258 writeln!(out, " return result;").ok();
1259 writeln!(out, " }} catch (Throwable e) {{").ok();
1260 writeln!(
1261 out,
1262 " throw new {}Exception(\"FFI call failed\", e);",
1263 class_name
1264 )
1265 .ok();
1266 writeln!(out, " }}").ok();
1267 } else if matches!(func.return_type, TypeRef::Named(_)) {
1268 let return_type_name = match &func.return_type {
1270 TypeRef::Named(name) => name,
1271 _ => unreachable!(),
1272 };
1273 let is_opaque = opaque_types.contains(return_type_name.as_str());
1274
1275 writeln!(
1276 out,
1277 " var resultPtr = (MemorySegment) {}.invoke({});",
1278 ffi_handle,
1279 call_args.join(", ")
1280 )
1281 .ok();
1282 emit_ffi_ptr_cleanup(out);
1283 writeln!(out, " if (resultPtr.equals(MemorySegment.NULL)) {{").ok();
1284 writeln!(out, " checkLastError();").ok();
1285 writeln!(out, " return null;").ok();
1286 writeln!(out, " }}").ok();
1287
1288 if is_opaque {
1289 writeln!(out, " return new {}(resultPtr);", return_type_name).ok();
1291 } else {
1292 let type_snake = return_type_name.to_snake_case();
1295 let free_handle = format!("NativeLib.{}_{}_FREE", prefix.to_uppercase(), type_snake.to_uppercase());
1296 let to_json_handle = format!(
1297 "NativeLib.{}_{}_TO_JSON",
1298 prefix.to_uppercase(),
1299 type_snake.to_uppercase()
1300 );
1301 writeln!(
1302 out,
1303 " var jsonPtr = (MemorySegment) {}.invoke(resultPtr);",
1304 to_json_handle
1305 )
1306 .ok();
1307 writeln!(out, " {}.invoke(resultPtr);", free_handle).ok();
1308 writeln!(out, " if (jsonPtr.equals(MemorySegment.NULL)) {{").ok();
1309 writeln!(out, " checkLastError();").ok();
1310 writeln!(out, " return null;").ok();
1311 writeln!(out, " }}").ok();
1312 writeln!(
1313 out,
1314 " String json = jsonPtr.reinterpret(Long.MAX_VALUE).getString(0);"
1315 )
1316 .ok();
1317 writeln!(
1318 out,
1319 " NativeLib.{}_FREE_STRING.invoke(jsonPtr);",
1320 prefix.to_uppercase()
1321 )
1322 .ok();
1323 writeln!(
1324 out,
1325 " return createObjectMapper().readValue(json, {}.class);",
1326 return_type_name
1327 )
1328 .ok();
1329 }
1330
1331 writeln!(out, " }} catch (Throwable e) {{").ok();
1332 writeln!(
1333 out,
1334 " throw new {}Exception(\"FFI call failed\", e);",
1335 class_name
1336 )
1337 .ok();
1338 writeln!(out, " }}").ok();
1339 } else if matches!(func.return_type, TypeRef::Vec(_)) {
1340 let free_handle = format!("NativeLib.{}_FREE_STRING", prefix.to_uppercase());
1342 writeln!(
1343 out,
1344 " var resultPtr = (MemorySegment) {}.invoke({});",
1345 ffi_handle,
1346 call_args.join(", ")
1347 )
1348 .ok();
1349 emit_ffi_ptr_cleanup(out);
1350 writeln!(out, " if (resultPtr.equals(MemorySegment.NULL)) {{").ok();
1351 writeln!(out, " return java.util.List.of();").ok();
1352 writeln!(out, " }}").ok();
1353 writeln!(
1354 out,
1355 " String json = resultPtr.reinterpret(Long.MAX_VALUE).getString(0);"
1356 )
1357 .ok();
1358 writeln!(out, " {}.invoke(resultPtr);", free_handle).ok();
1359 let element_type = match &func.return_type {
1361 TypeRef::Vec(inner) => java_type(inner),
1362 _ => unreachable!(),
1363 };
1364 writeln!(
1365 out,
1366 " return createObjectMapper().readValue(json, new com.fasterxml.jackson.core.type.TypeReference<java.util.List<{}>>() {{ }});",
1367 element_type
1368 )
1369 .ok();
1370 writeln!(out, " }} catch (Throwable e) {{").ok();
1371 writeln!(
1372 out,
1373 " throw new {}Exception(\"FFI call failed\", e);",
1374 class_name
1375 )
1376 .ok();
1377 writeln!(out, " }}").ok();
1378 } else {
1379 writeln!(
1380 out,
1381 " var primitiveResult = ({}) {}.invoke({});",
1382 java_ffi_return_cast(&func.return_type),
1383 ffi_handle,
1384 call_args.join(", ")
1385 )
1386 .ok();
1387 emit_ffi_ptr_cleanup(out);
1388 writeln!(out, " return primitiveResult;").ok();
1389 writeln!(out, " }} catch (Throwable e) {{").ok();
1390 writeln!(
1391 out,
1392 " throw new {}Exception(\"FFI call failed\", e);",
1393 class_name
1394 )
1395 .ok();
1396 writeln!(out, " }}").ok();
1397 }
1398
1399 writeln!(out, " }}").ok();
1400}
1401
1402fn gen_async_wrapper_method(
1403 out: &mut String,
1404 func: &FunctionDef,
1405 bridge_param_names: &HashSet<String>,
1406 bridge_type_aliases: &HashSet<String>,
1407) {
1408 let params: Vec<String> = func
1409 .params
1410 .iter()
1411 .filter(|p| !is_bridge_param_java(p, bridge_param_names, bridge_type_aliases))
1412 .map(|p| {
1413 let ptype = java_type(&p.ty);
1414 format!("final {} {}", ptype, to_java_name(&p.name))
1415 })
1416 .collect();
1417
1418 let return_type = match &func.return_type {
1419 TypeRef::Unit => "Void".to_string(),
1420 other => java_boxed_type(other).to_string(),
1421 };
1422
1423 let sync_method_name = to_java_name(&func.name);
1424 let async_method_name = format!("{}Async", sync_method_name);
1425 let param_names: Vec<String> = func
1426 .params
1427 .iter()
1428 .filter(|p| !is_bridge_param_java(p, bridge_param_names, bridge_type_aliases))
1429 .map(|p| to_java_name(&p.name))
1430 .collect();
1431
1432 writeln!(
1433 out,
1434 " public static CompletableFuture<{}> {}({}) {{",
1435 return_type,
1436 async_method_name,
1437 params.join(", ")
1438 )
1439 .ok();
1440 writeln!(out, " return CompletableFuture.supplyAsync(() -> {{").ok();
1441 writeln!(out, " try {{").ok();
1442 writeln!(
1443 out,
1444 " return {}({});",
1445 sync_method_name,
1446 param_names.join(", ")
1447 )
1448 .ok();
1449 writeln!(out, " }} catch (Throwable e) {{").ok();
1450 writeln!(out, " throw new CompletionException(e);").ok();
1451 writeln!(out, " }}").ok();
1452 writeln!(out, " }});").ok();
1453 writeln!(out, " }}").ok();
1454}
1455
1456fn gen_exception_class(package: &str, class_name: &str) -> String {
1461 let mut out = String::with_capacity(512);
1462
1463 writeln!(out, "// DO NOT EDIT - auto-generated by alef").ok();
1464 writeln!(out, "package {};", package).ok();
1465 writeln!(out).ok();
1466
1467 writeln!(out, "/** Exception thrown by {}. */", class_name).ok();
1468 writeln!(out, "public class {}Exception extends Exception {{", class_name).ok();
1469 writeln!(out, " /** The error code. */").ok();
1470 writeln!(out, " private final int code;").ok();
1471 writeln!(out).ok();
1472 writeln!(out, " /** Creates a new {}Exception. */", class_name).ok();
1473 writeln!(
1474 out,
1475 " public {}Exception(final int code, final String message) {{",
1476 class_name
1477 )
1478 .ok();
1479 writeln!(out, " super(message);").ok();
1480 writeln!(out, " this.code = code;").ok();
1481 writeln!(out, " }}").ok();
1482 writeln!(out).ok();
1483 writeln!(out, " /** Creates a new {}Exception with a cause. */", class_name).ok();
1484 writeln!(
1485 out,
1486 " public {}Exception(final String message, final Throwable cause) {{",
1487 class_name
1488 )
1489 .ok();
1490 writeln!(out, " super(message, cause);").ok();
1491 writeln!(out, " this.code = -1;").ok();
1492 writeln!(out, " }}").ok();
1493 writeln!(out).ok();
1494 writeln!(out, " /** Returns the error code. */").ok();
1495 writeln!(out, " public int getCode() {{").ok();
1496 writeln!(out, " return code;").ok();
1497 writeln!(out, " }}").ok();
1498 writeln!(out, "}}").ok();
1499
1500 out
1501}
1502
1503#[allow(clippy::too_many_arguments)]
1508fn gen_facade_class(
1509 api: &ApiSurface,
1510 package: &str,
1511 public_class: &str,
1512 raw_class: &str,
1513 _prefix: &str,
1514 bridge_param_names: &HashSet<String>,
1515 bridge_type_aliases: &HashSet<String>,
1516 has_visitor_bridge: bool,
1517) -> String {
1518 let mut body = String::with_capacity(4096);
1519
1520 writeln!(body, "public final class {} {{", public_class).ok();
1521 writeln!(body, " private {}() {{ }}", public_class).ok();
1522 writeln!(body).ok();
1523
1524 for func in &api.functions {
1526 let params: Vec<String> = func
1528 .params
1529 .iter()
1530 .filter(|p| !is_bridge_param_java(p, bridge_param_names, bridge_type_aliases))
1531 .map(|p| {
1532 let ptype = java_type(&p.ty);
1533 format!("final {} {}", ptype, to_java_name(&p.name))
1534 })
1535 .collect();
1536
1537 let return_type = java_type(&func.return_type);
1538
1539 if !func.doc.is_empty() {
1540 writeln!(body, " /**").ok();
1541 for line in func.doc.lines() {
1542 writeln!(body, " * {}", line).ok();
1543 }
1544 writeln!(body, " */").ok();
1545 }
1546
1547 writeln!(
1548 body,
1549 " public static {} {}({}) throws {}Exception {{",
1550 return_type,
1551 to_java_name(&func.name),
1552 params.join(", "),
1553 raw_class
1554 )
1555 .ok();
1556
1557 for param in &func.params {
1559 if !param.optional && !is_bridge_param_java(param, bridge_param_names, bridge_type_aliases) {
1560 let pname = to_java_name(¶m.name);
1561 writeln!(
1562 body,
1563 " java.util.Objects.requireNonNull({}, \"{} must not be null\");",
1564 pname, pname
1565 )
1566 .ok();
1567 }
1568 }
1569
1570 let call_args: Vec<String> = func
1574 .params
1575 .iter()
1576 .filter(|p| !is_bridge_param_java(p, bridge_param_names, bridge_type_aliases))
1577 .map(|p| to_java_name(&p.name))
1578 .collect();
1579
1580 if matches!(func.return_type, TypeRef::Unit) {
1581 writeln!(
1582 body,
1583 " {}.{}({});",
1584 raw_class,
1585 to_java_name(&func.name),
1586 call_args.join(", ")
1587 )
1588 .ok();
1589 } else {
1590 writeln!(
1591 body,
1592 " return {}.{}({});",
1593 raw_class,
1594 to_java_name(&func.name),
1595 call_args.join(", ")
1596 )
1597 .ok();
1598 }
1599
1600 writeln!(body, " }}").ok();
1601 writeln!(body).ok();
1602
1603 let has_optional = func
1606 .params
1607 .iter()
1608 .any(|p| p.optional && !is_bridge_param_java(p, bridge_param_names, bridge_type_aliases));
1609 if has_optional {
1610 let required_params: Vec<String> = func
1611 .params
1612 .iter()
1613 .filter(|p| !p.optional && !is_bridge_param_java(p, bridge_param_names, bridge_type_aliases))
1614 .map(|p| {
1615 let ptype = java_type(&p.ty);
1616 format!("final {} {}", ptype, to_java_name(&p.name))
1617 })
1618 .collect();
1619
1620 writeln!(
1621 body,
1622 " public static {} {}({}) throws {}Exception {{",
1623 return_type,
1624 to_java_name(&func.name),
1625 required_params.join(", "),
1626 raw_class
1627 )
1628 .ok();
1629
1630 let full_args: Vec<String> = func
1633 .params
1634 .iter()
1635 .filter(|p| !is_bridge_param_java(p, bridge_param_names, bridge_type_aliases))
1636 .map(|p| {
1637 if p.optional {
1638 "null".to_string()
1639 } else {
1640 to_java_name(&p.name)
1641 }
1642 })
1643 .collect();
1644
1645 if matches!(func.return_type, TypeRef::Unit) {
1646 writeln!(
1647 body,
1648 " {}.{}({});",
1649 raw_class,
1650 to_java_name(&func.name),
1651 full_args.join(", ")
1652 )
1653 .ok();
1654 } else {
1655 writeln!(
1656 body,
1657 " return {}.{}({});",
1658 raw_class,
1659 to_java_name(&func.name),
1660 full_args.join(", ")
1661 )
1662 .ok();
1663 }
1664
1665 writeln!(body, " }}").ok();
1666 writeln!(body).ok();
1667 }
1668 }
1669
1670 if has_visitor_bridge {
1672 writeln!(body, " /**").ok();
1673 writeln!(
1674 body,
1675 " * Convert HTML to Markdown, invoking visitor callbacks during processing."
1676 )
1677 .ok();
1678 writeln!(body, " */").ok();
1679 writeln!(
1680 body,
1681 " public static ConversionResult convertWithVisitor(String html, ConversionOptions options, Visitor visitor)"
1682 )
1683 .ok();
1684 writeln!(body, " throws {}Exception {{", raw_class).ok();
1685 writeln!(
1686 body,
1687 " return {}.convertWithVisitor(html, options, visitor);",
1688 raw_class
1689 )
1690 .ok();
1691 writeln!(body, " }}").ok();
1692 writeln!(body).ok();
1693 }
1694
1695 writeln!(body, "}}").ok();
1696
1697 let mut out = String::with_capacity(body.len() + 512);
1699
1700 writeln!(out, "// DO NOT EDIT - auto-generated by alef").ok();
1701 writeln!(out, "package {};", package).ok();
1702
1703 let has_list = body.contains("List<");
1705 let has_map = body.contains("Map<");
1706 let has_optional = body.contains("Optional<");
1707 let has_imports = has_list || has_map || has_optional;
1708
1709 if has_imports {
1710 writeln!(out).ok();
1711 if has_list {
1712 writeln!(out, "import java.util.List;").ok();
1713 }
1714 if has_map {
1715 writeln!(out, "import java.util.Map;").ok();
1716 }
1717 if has_optional {
1718 writeln!(out, "import java.util.Optional;").ok();
1719 }
1720 }
1721
1722 writeln!(out).ok();
1723 out.push_str(&body);
1724
1725 out
1726}
1727
1728fn gen_opaque_handle_class(package: &str, typ: &TypeDef, prefix: &str) -> String {
1733 let mut out = String::with_capacity(1024);
1734 let class_name = &typ.name;
1735 let type_snake = class_name.to_snake_case();
1736
1737 writeln!(out, "// DO NOT EDIT - auto-generated by alef").ok();
1738 writeln!(out, "package {};", package).ok();
1739 writeln!(out).ok();
1740 writeln!(out, "import java.lang.foreign.MemorySegment;").ok();
1741 writeln!(out).ok();
1742
1743 if !typ.doc.is_empty() {
1744 writeln!(out, "/**").ok();
1745 for line in typ.doc.lines() {
1746 writeln!(out, " * {}", line).ok();
1747 }
1748 writeln!(out, " */").ok();
1749 }
1750
1751 writeln!(out, "public class {} implements AutoCloseable {{", class_name).ok();
1752 writeln!(out, " private final MemorySegment handle;").ok();
1753 writeln!(out).ok();
1754 writeln!(out, " {}(MemorySegment handle) {{", class_name).ok();
1755 writeln!(out, " this.handle = handle;").ok();
1756 writeln!(out, " }}").ok();
1757 writeln!(out).ok();
1758 writeln!(out, " MemorySegment handle() {{").ok();
1759 writeln!(out, " return this.handle;").ok();
1760 writeln!(out, " }}").ok();
1761 writeln!(out).ok();
1762 writeln!(out, " @Override").ok();
1763 writeln!(out, " public void close() {{").ok();
1764 writeln!(
1765 out,
1766 " if (handle != null && !handle.equals(MemorySegment.NULL)) {{"
1767 )
1768 .ok();
1769 writeln!(out, " try {{").ok();
1770 writeln!(
1771 out,
1772 " NativeLib.{}_{}_FREE.invoke(handle);",
1773 prefix.to_uppercase(),
1774 type_snake.to_uppercase()
1775 )
1776 .ok();
1777 writeln!(out, " }} catch (Throwable e) {{").ok();
1778 writeln!(
1779 out,
1780 " throw new RuntimeException(\"Failed to free {}: \" + e.getMessage(), e);",
1781 class_name
1782 )
1783 .ok();
1784 writeln!(out, " }}").ok();
1785 writeln!(out, " }}").ok();
1786 writeln!(out, " }}").ok();
1787 writeln!(out, "}}").ok();
1788
1789 out
1790}
1791
1792fn emit_javadoc(out: &mut String, doc: &str, indent: &str) {
1802 if doc.is_empty() {
1803 return;
1804 }
1805 writeln!(out, "{indent}/**").ok();
1806 for line in doc.lines() {
1807 if line.is_empty() {
1808 writeln!(out, "{indent} *").ok();
1809 } else {
1810 let escaped = escape_javadoc_line(line);
1811 writeln!(out, "{indent} * {escaped}").ok();
1812 }
1813 }
1814 writeln!(out, "{indent} */").ok();
1815}
1816
1817const RECORD_LINE_WRAP_THRESHOLD: usize = 100;
1820
1821fn gen_record_type(package: &str, typ: &TypeDef, complex_enums: &AHashSet<String>, lang_rename_all: &str) -> String {
1822 let (field_list, field_docs): (Vec<String>, Vec<String>) = typ
1827 .fields
1828 .iter()
1829 .map(|f| {
1830 let is_complex = matches!(&f.ty, TypeRef::Named(n) if complex_enums.contains(n.as_str()));
1833 let ftype = if is_complex {
1834 "Object".to_string()
1835 } else if f.optional {
1836 format!("Optional<{}>", java_boxed_type(&f.ty))
1837 } else {
1838 java_type(&f.ty).to_string()
1839 };
1840 let jname = safe_java_field_name(&f.name);
1841 let decl = if lang_rename_all == "camelCase" && f.name.contains('_') {
1845 format!("@JsonProperty(\"{}\") {} {}", f.name, ftype, jname)
1846 } else {
1847 format!("{} {}", ftype, jname)
1848 };
1849 (decl, f.doc.clone())
1850 })
1851 .unzip();
1852
1853 let single_line = format!("public record {}({}) {{ }}", typ.name, field_list.join(", "));
1857
1858 let mut record_block = String::new();
1860 emit_javadoc(&mut record_block, &typ.doc, "");
1861 if single_line.len() > RECORD_LINE_WRAP_THRESHOLD && field_list.len() > 1 {
1862 writeln!(record_block, "public record {}(", typ.name).ok();
1863 for (i, (field, doc)) in field_list.iter().zip(field_docs.iter()).enumerate() {
1864 let comma = if i < field_list.len() - 1 { "," } else { "" };
1865 if !doc.is_empty() {
1866 let doc_summary = escape_javadoc_line(doc.lines().next().unwrap_or("").trim());
1868 writeln!(record_block, " /** {doc_summary} */").ok();
1869 }
1870 writeln!(record_block, " {}{}", field, comma).ok();
1871 }
1872 writeln!(record_block, ") {{").ok();
1873 } else {
1874 writeln!(record_block, "public record {}({}) {{", typ.name, field_list.join(", ")).ok();
1875 }
1876
1877 if typ.has_default {
1879 writeln!(record_block, " public static {}Builder builder() {{", typ.name).ok();
1880 writeln!(record_block, " return new {}Builder();", typ.name).ok();
1881 writeln!(record_block, " }}").ok();
1882 }
1883
1884 writeln!(record_block, "}}").ok();
1885
1886 let needs_json_property = field_list.iter().any(|f| f.contains("@JsonProperty("));
1888 let mut out = String::with_capacity(record_block.len() + 512);
1889 writeln!(out, "// DO NOT EDIT - auto-generated by alef").ok();
1890 writeln!(out, "package {};", package).ok();
1891 writeln!(out).ok();
1892 if single_line.contains("List<") {
1893 writeln!(out, "import java.util.List;").ok();
1894 }
1895 if single_line.contains("Map<") {
1896 writeln!(out, "import java.util.Map;").ok();
1897 }
1898 if single_line.contains("Optional<") {
1899 writeln!(out, "import java.util.Optional;").ok();
1900 }
1901 if needs_json_property {
1902 writeln!(out, "import com.fasterxml.jackson.annotation.JsonProperty;").ok();
1903 }
1904 writeln!(out).ok();
1905 write!(out, "{}", record_block).ok();
1906
1907 out
1908}
1909
1910fn java_apply_rename_all(name: &str, rename_all: Option<&str>) -> String {
1916 match rename_all {
1917 Some("snake_case") => name.to_snake_case(),
1918 Some("camelCase") => name.to_lower_camel_case(),
1919 Some("PascalCase") => name.to_pascal_case(),
1920 Some("SCREAMING_SNAKE_CASE") => name.to_snake_case().to_uppercase(),
1921 Some("lowercase") => name.to_lowercase(),
1922 Some("UPPERCASE") => name.to_uppercase(),
1923 _ => name.to_lowercase(),
1924 }
1925}
1926
1927fn gen_enum_class(package: &str, enum_def: &EnumDef) -> String {
1928 let has_data_variants = enum_def.variants.iter().any(|v| !v.fields.is_empty());
1929
1930 if enum_def.serde_tag.is_some() && has_data_variants {
1932 return gen_java_tagged_union(package, enum_def);
1933 }
1934
1935 let mut out = String::with_capacity(1024);
1936
1937 writeln!(out, "// DO NOT EDIT - auto-generated by alef").ok();
1938 writeln!(out, "package {};", package).ok();
1939 writeln!(out).ok();
1940 writeln!(out, "import com.fasterxml.jackson.annotation.JsonCreator;").ok();
1941 writeln!(out, "import com.fasterxml.jackson.annotation.JsonValue;").ok();
1942 writeln!(out).ok();
1943
1944 emit_javadoc(&mut out, &enum_def.doc, "");
1945 writeln!(out, "public enum {} {{", enum_def.name).ok();
1946
1947 for (i, variant) in enum_def.variants.iter().enumerate() {
1948 let comma = if i < enum_def.variants.len() - 1 { "," } else { ";" };
1949 let json_name = variant
1951 .serde_rename
1952 .clone()
1953 .unwrap_or_else(|| java_apply_rename_all(&variant.name, enum_def.serde_rename_all.as_deref()));
1954 if !variant.doc.is_empty() {
1955 let doc_summary = escape_javadoc_line(variant.doc.lines().next().unwrap_or("").trim());
1956 if doc_summary.len() + 11 > 80 {
1958 writeln!(out, " /**").ok();
1959 writeln!(out, " * {doc_summary}").ok();
1960 writeln!(out, " */").ok();
1961 } else {
1962 writeln!(out, " /** {doc_summary} */").ok();
1963 }
1964 }
1965 writeln!(out, " {}(\"{}\"){}", variant.name, json_name, comma).ok();
1966 }
1967
1968 writeln!(out).ok();
1969 writeln!(out, " /** The string value. */").ok();
1970 writeln!(out, " private final String value;").ok();
1971 writeln!(out).ok();
1972 writeln!(out, " {}(final String value) {{", enum_def.name).ok();
1973 writeln!(out, " this.value = value;").ok();
1974 writeln!(out, " }}").ok();
1975 writeln!(out).ok();
1976 writeln!(out, " /** Returns the string value. */").ok();
1977 writeln!(out, " @JsonValue").ok();
1978 writeln!(out, " public String getValue() {{").ok();
1979 writeln!(out, " return value;").ok();
1980 writeln!(out, " }}").ok();
1981 writeln!(out).ok();
1982 writeln!(out, " /** Creates an instance from a string value. */").ok();
1983 writeln!(out, " @JsonCreator").ok();
1984 writeln!(
1985 out,
1986 " public static {} fromValue(final String value) {{",
1987 enum_def.name
1988 )
1989 .ok();
1990 writeln!(out, " for ({} e : values()) {{", enum_def.name).ok();
1991 writeln!(out, " if (e.value.equalsIgnoreCase(value)) {{").ok();
1992 writeln!(out, " return e;").ok();
1993 writeln!(out, " }}").ok();
1994 writeln!(out, " }}").ok();
1995 writeln!(
1996 out,
1997 " throw new IllegalArgumentException(\"Unknown value: \" + value);"
1998 )
1999 .ok();
2000 writeln!(out, " }}").ok();
2001
2002 writeln!(out, "}}").ok();
2003
2004 out
2005}
2006
2007fn gen_java_tagged_union(package: &str, enum_def: &EnumDef) -> String {
2012 let tag_field = enum_def.serde_tag.as_deref().unwrap_or("type");
2013
2014 let variant_names: std::collections::HashSet<&str> = enum_def.variants.iter().map(|v| v.name.as_str()).collect();
2019 let optional_type = if variant_names.contains("Optional") {
2020 "java.util.Optional"
2021 } else {
2022 "Optional"
2023 };
2024
2025 let needs_json_property = enum_def
2027 .variants
2028 .iter()
2029 .any(|v| v.fields.iter().any(|f| !is_tuple_field_name(&f.name)));
2030
2031 let mut out = String::with_capacity(2048);
2032 writeln!(out, "// DO NOT EDIT - auto-generated by alef").ok();
2033 writeln!(out, "package {};", package).ok();
2034 writeln!(out).ok();
2035 if needs_json_property {
2036 writeln!(out, "import com.fasterxml.jackson.annotation.JsonProperty;").ok();
2037 }
2038 writeln!(out, "import com.fasterxml.jackson.annotation.JsonSubTypes;").ok();
2039 writeln!(out, "import com.fasterxml.jackson.annotation.JsonTypeInfo;").ok();
2040
2041 let needs_list = !variant_names.contains("List")
2043 && enum_def
2044 .variants
2045 .iter()
2046 .any(|v| v.fields.iter().any(|f| matches!(&f.ty, TypeRef::Vec(_))));
2047 let needs_map = !variant_names.contains("Map")
2048 && enum_def
2049 .variants
2050 .iter()
2051 .any(|v| v.fields.iter().any(|f| matches!(&f.ty, TypeRef::Map(_, _))));
2052 let needs_optional =
2053 !variant_names.contains("Optional") && enum_def.variants.iter().any(|v| v.fields.iter().any(|f| f.optional));
2054 let needs_unwrapped = enum_def
2057 .variants
2058 .iter()
2059 .any(|v| v.fields.len() == 1 && is_tuple_field_name(&v.fields[0].name));
2060 if needs_list {
2061 writeln!(out, "import java.util.List;").ok();
2062 }
2063 if needs_map {
2064 writeln!(out, "import java.util.Map;").ok();
2065 }
2066 if needs_optional {
2067 writeln!(out, "import java.util.Optional;").ok();
2068 }
2069 if needs_unwrapped {
2070 writeln!(out, "import com.fasterxml.jackson.annotation.JsonUnwrapped;").ok();
2071 }
2072 writeln!(out).ok();
2073
2074 emit_javadoc(&mut out, &enum_def.doc, "");
2075 writeln!(
2077 out,
2078 "@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = \"{tag_field}\", visible = false)"
2079 )
2080 .ok();
2081 writeln!(out, "@JsonSubTypes({{").ok();
2082 for (i, variant) in enum_def.variants.iter().enumerate() {
2083 let discriminator = variant
2084 .serde_rename
2085 .clone()
2086 .unwrap_or_else(|| java_apply_rename_all(&variant.name, enum_def.serde_rename_all.as_deref()));
2087 let comma = if i < enum_def.variants.len() - 1 { "," } else { "" };
2088 writeln!(
2089 out,
2090 " @JsonSubTypes.Type(value = {}.{}.class, name = \"{}\"){}",
2091 enum_def.name, variant.name, discriminator, comma
2092 )
2093 .ok();
2094 }
2095 writeln!(out, "}})").ok();
2096 writeln!(out, "public sealed interface {} {{", enum_def.name).ok();
2097
2098 for variant in &enum_def.variants {
2100 writeln!(out).ok();
2101 if variant.fields.is_empty() {
2102 if !variant.doc.is_empty() {
2104 let doc_summary = escape_javadoc_line(variant.doc.lines().next().unwrap_or("").trim());
2105 writeln!(out, " /** {doc_summary} */").ok();
2106 }
2107 writeln!(out, " record {}() implements {} {{", variant.name, enum_def.name).ok();
2108 writeln!(out, " }}").ok();
2109 } else {
2110 let field_parts: Vec<String> = variant
2112 .fields
2113 .iter()
2114 .map(|f| {
2115 let ftype = if f.optional {
2116 let inner = java_boxed_type(&f.ty);
2117 let inner_str = inner.as_ref();
2118 let inner_qualified = if inner_str.starts_with("List<") && variant_names.contains("List") {
2120 inner_str.replacen("List<", "java.util.List<", 1)
2121 } else if inner_str.starts_with("Map<") && variant_names.contains("Map") {
2122 inner_str.replacen("Map<", "java.util.Map<", 1)
2123 } else {
2124 inner_str.to_string()
2125 };
2126 format!("{optional_type}<{inner_qualified}>")
2127 } else {
2128 let t = java_type(&f.ty);
2129 let t_str = t.as_ref();
2130 if t_str.starts_with("List<") && variant_names.contains("List") {
2131 t_str.replacen("List<", "java.util.List<", 1)
2132 } else if t_str.starts_with("Map<") && variant_names.contains("Map") {
2133 t_str.replacen("Map<", "java.util.Map<", 1)
2134 } else {
2135 t_str.to_string()
2136 }
2137 };
2138 if is_tuple_field_name(&f.name) {
2142 format!("@JsonUnwrapped {ftype} value")
2143 } else {
2144 let json_name = f.name.trim_start_matches('_');
2145 let jname = safe_java_field_name(json_name);
2146 format!("@JsonProperty(\"{json_name}\") {ftype} {jname}")
2147 }
2148 })
2149 .collect();
2150
2151 let single = format!(
2152 " record {}({}) implements {} {{ }}",
2153 variant.name,
2154 field_parts.join(", "),
2155 enum_def.name
2156 );
2157
2158 if !variant.doc.is_empty() {
2159 let doc_summary = escape_javadoc_line(variant.doc.lines().next().unwrap_or("").trim());
2160 writeln!(out, " /** {doc_summary} */").ok();
2161 }
2162 if single.len() > RECORD_LINE_WRAP_THRESHOLD && field_parts.len() > 1 {
2163 writeln!(out, " record {}(", variant.name).ok();
2164 for (i, fp) in field_parts.iter().enumerate() {
2165 let comma = if i < field_parts.len() - 1 { "," } else { "" };
2166 writeln!(out, " {}{}", fp, comma).ok();
2167 }
2168 writeln!(out, " ) implements {} {{", enum_def.name).ok();
2169 writeln!(out, " }}").ok();
2170 } else {
2171 writeln!(
2172 out,
2173 " record {}({}) implements {} {{ }}",
2174 variant.name,
2175 field_parts.join(", "),
2176 enum_def.name
2177 )
2178 .ok();
2179 }
2180 }
2181 }
2182
2183 writeln!(out).ok();
2184 writeln!(out, "}}").ok();
2185 out
2186}
2187
2188fn gen_ffi_layout(ty: &TypeRef) -> String {
2193 match ty {
2194 TypeRef::Primitive(prim) => java_ffi_type(prim).to_string(),
2195 TypeRef::String | TypeRef::Char | TypeRef::Path | TypeRef::Json => "ValueLayout.ADDRESS".to_string(),
2196 TypeRef::Bytes => "ValueLayout.ADDRESS".to_string(),
2197 TypeRef::Optional(inner) => gen_ffi_layout(inner),
2198 TypeRef::Vec(_) => "ValueLayout.ADDRESS".to_string(),
2199 TypeRef::Map(_, _) => "ValueLayout.ADDRESS".to_string(),
2200 TypeRef::Named(_) => "ValueLayout.ADDRESS".to_string(),
2201 TypeRef::Unit => "".to_string(),
2202 TypeRef::Duration => "ValueLayout.JAVA_LONG".to_string(),
2203 }
2204}
2205
2206fn marshal_param_to_ffi(out: &mut String, name: &str, ty: &TypeRef, opaque_types: &AHashSet<String>, prefix: &str) {
2207 match ty {
2208 TypeRef::String | TypeRef::Char | TypeRef::Path | TypeRef::Json => {
2209 let cname = "c".to_string() + name;
2210 writeln!(out, " var {} = arena.allocateFrom({});", cname, name).ok();
2211 }
2212 TypeRef::Named(type_name) => {
2213 let cname = "c".to_string() + name;
2214 if opaque_types.contains(type_name.as_str()) {
2215 writeln!(out, " var {} = {}.handle();", cname, name).ok();
2217 } else {
2218 let type_snake = type_name.to_snake_case();
2221 let from_json_handle = format!(
2222 "NativeLib.{}_{}_FROM_JSON",
2223 prefix.to_uppercase(),
2224 type_snake.to_uppercase()
2225 );
2226 let _free_handle = format!("NativeLib.{}_{}_FREE", prefix.to_uppercase(), type_snake.to_uppercase());
2227 writeln!(
2228 out,
2229 " var {}Json = {} != null ? createObjectMapper().writeValueAsString({}) : null;",
2230 cname, name, name
2231 )
2232 .ok();
2233 writeln!(
2234 out,
2235 " var {}JsonSeg = {}Json != null ? arena.allocateFrom({}Json) : MemorySegment.NULL;",
2236 cname, cname, cname
2237 )
2238 .ok();
2239 writeln!(out, " var {} = {}Json != null", cname, cname).ok();
2240 writeln!(
2241 out,
2242 " ? (MemorySegment) {}.invoke({}JsonSeg)",
2243 from_json_handle, cname
2244 )
2245 .ok();
2246 writeln!(out, " : MemorySegment.NULL;").ok();
2247 }
2248 }
2249 TypeRef::Optional(inner) => {
2250 match inner.as_ref() {
2252 TypeRef::String | TypeRef::Char | TypeRef::Path | TypeRef::Json => {
2253 let cname = "c".to_string() + name;
2254 writeln!(
2255 out,
2256 " var {} = {} != null ? arena.allocateFrom({}) : MemorySegment.NULL;",
2257 cname, name, name
2258 )
2259 .ok();
2260 }
2261 TypeRef::Named(type_name) => {
2262 let cname = "c".to_string() + name;
2263 if opaque_types.contains(type_name.as_str()) {
2264 writeln!(
2265 out,
2266 " var {} = {} != null ? {}.handle() : MemorySegment.NULL;",
2267 cname, name, name
2268 )
2269 .ok();
2270 } else {
2271 let type_snake = type_name.to_snake_case();
2273 let from_json_handle = format!(
2274 "NativeLib.{}_{}_FROM_JSON",
2275 prefix.to_uppercase(),
2276 type_snake.to_uppercase()
2277 );
2278 writeln!(
2279 out,
2280 " var {}Json = {} != null ? createObjectMapper().writeValueAsString({}) : null;",
2281 cname, name, name
2282 )
2283 .ok();
2284 writeln!(out, " var {}JsonSeg = {}Json != null ? arena.allocateFrom({}Json) : MemorySegment.NULL;", cname, cname, cname).ok();
2285 writeln!(out, " var {} = {}Json != null", cname, cname).ok();
2286 writeln!(
2287 out,
2288 " ? (MemorySegment) {}.invoke({}JsonSeg)",
2289 from_json_handle, cname
2290 )
2291 .ok();
2292 writeln!(out, " : MemorySegment.NULL;").ok();
2293 }
2294 }
2295 _ => {
2296 }
2298 }
2299 }
2300 TypeRef::Vec(_) | TypeRef::Map(_, _) => {
2301 let cname = "c".to_string() + name;
2303 writeln!(
2304 out,
2305 " var {}Json = createObjectMapper().writeValueAsString({});",
2306 cname, name
2307 )
2308 .ok();
2309 writeln!(out, " var {} = arena.allocateFrom({}Json);", cname, cname).ok();
2310 }
2311 _ => {
2312 }
2314 }
2315}
2316
2317fn ffi_param_name(name: &str, ty: &TypeRef, _opaque_types: &AHashSet<String>) -> String {
2318 match ty {
2319 TypeRef::String | TypeRef::Char | TypeRef::Path | TypeRef::Json => "c".to_string() + name,
2320 TypeRef::Named(_) => "c".to_string() + name,
2321 TypeRef::Vec(_) | TypeRef::Map(_, _) => "c".to_string() + name,
2322 TypeRef::Optional(inner) => match inner.as_ref() {
2323 TypeRef::String | TypeRef::Char | TypeRef::Path | TypeRef::Json | TypeRef::Named(_) => {
2324 "c".to_string() + name
2325 }
2326 _ => name.to_string(),
2327 },
2328 _ => name.to_string(),
2329 }
2330}
2331
2332fn gen_function_descriptor(return_layout: &str, param_layouts: &[String]) -> String {
2335 if return_layout.is_empty() {
2336 if param_layouts.is_empty() {
2338 "FunctionDescriptor.ofVoid()".to_string()
2339 } else {
2340 format!("FunctionDescriptor.ofVoid({})", param_layouts.join(", "))
2341 }
2342 } else {
2343 if param_layouts.is_empty() {
2345 format!("FunctionDescriptor.of({})", return_layout)
2346 } else {
2347 format!("FunctionDescriptor.of({}, {})", return_layout, param_layouts.join(", "))
2348 }
2349 }
2350}
2351
2352fn is_ffi_string_return(ty: &TypeRef) -> bool {
2355 match ty {
2356 TypeRef::String | TypeRef::Char | TypeRef::Path | TypeRef::Json => true,
2357 TypeRef::Optional(inner) => is_ffi_string_return(inner),
2358 _ => false,
2359 }
2360}
2361
2362fn java_ffi_return_cast(ty: &TypeRef) -> &'static str {
2364 match ty {
2365 TypeRef::Primitive(prim) => match prim {
2366 PrimitiveType::Bool => "boolean",
2367 PrimitiveType::U8 | PrimitiveType::I8 => "byte",
2368 PrimitiveType::U16 | PrimitiveType::I16 => "short",
2369 PrimitiveType::U32 | PrimitiveType::I32 => "int",
2370 PrimitiveType::U64 | PrimitiveType::I64 | PrimitiveType::Usize | PrimitiveType::Isize => "long",
2371 PrimitiveType::F32 => "float",
2372 PrimitiveType::F64 => "double",
2373 },
2374 TypeRef::Bytes | TypeRef::Vec(_) | TypeRef::Map(_, _) | TypeRef::Named(_) => "MemorySegment",
2375 _ => "MemorySegment",
2376 }
2377}
2378
2379fn gen_helper_methods(out: &mut String, prefix: &str, class_name: &str) {
2380 let needs_check_last_error = out.contains("checkLastError()");
2382 let needs_read_cstring = out.contains("readCString(");
2383 let needs_read_bytes = out.contains("readBytes(");
2384 let needs_create_object_mapper = out.contains("createObjectMapper()");
2385
2386 if !needs_check_last_error && !needs_read_cstring && !needs_read_bytes && !needs_create_object_mapper {
2387 return;
2388 }
2389
2390 writeln!(out, " // Helper methods for FFI marshalling").ok();
2391 writeln!(out).ok();
2392
2393 if needs_check_last_error {
2394 writeln!(out, " private static void checkLastError() throws Throwable {{").ok();
2397 writeln!(
2398 out,
2399 " int errCode = (int) NativeLib.{}_LAST_ERROR_CODE.invoke();",
2400 prefix.to_uppercase()
2401 )
2402 .ok();
2403 writeln!(out, " if (errCode != 0) {{").ok();
2404 writeln!(
2405 out,
2406 " var ctxPtr = (MemorySegment) NativeLib.{}_LAST_ERROR_CONTEXT.invoke();",
2407 prefix.to_uppercase()
2408 )
2409 .ok();
2410 writeln!(
2411 out,
2412 " String msg = ctxPtr.reinterpret(Long.MAX_VALUE).getString(0);"
2413 )
2414 .ok();
2415 writeln!(out, " throw new {}Exception(errCode, msg);", class_name).ok();
2416 writeln!(out, " }}").ok();
2417 writeln!(out, " }}").ok();
2418 writeln!(out).ok();
2419 }
2420
2421 if needs_create_object_mapper {
2422 writeln!(
2428 out,
2429 " private static com.fasterxml.jackson.databind.ObjectMapper createObjectMapper() {{"
2430 )
2431 .ok();
2432 writeln!(out, " return new com.fasterxml.jackson.databind.ObjectMapper()").ok();
2433 writeln!(
2434 out,
2435 " .registerModule(new com.fasterxml.jackson.datatype.jdk8.Jdk8Module())"
2436 )
2437 .ok();
2438 writeln!(out, " .findAndRegisterModules()").ok();
2439 writeln!(
2440 out,
2441 " .setSerializationInclusion(com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL)"
2442 )
2443 .ok();
2444 writeln!(
2445 out,
2446 " .configure(com.fasterxml.jackson.databind.MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS, true);"
2447 )
2448 .ok();
2449 writeln!(out, " }}").ok();
2450 writeln!(out).ok();
2451 }
2452
2453 if needs_read_cstring {
2454 writeln!(out, " private static String readCString(MemorySegment ptr) {{").ok();
2455 writeln!(out, " if (ptr == null || ptr.address() == 0) {{").ok();
2456 writeln!(out, " return null;").ok();
2457 writeln!(out, " }}").ok();
2458 writeln!(out, " return ptr.getUtf8String(0);").ok();
2459 writeln!(out, " }}").ok();
2460 writeln!(out).ok();
2461 }
2462
2463 if needs_read_bytes {
2464 writeln!(
2465 out,
2466 " private static byte[] readBytes(MemorySegment ptr, long len) {{"
2467 )
2468 .ok();
2469 writeln!(out, " if (ptr == null || ptr.address() == 0) {{").ok();
2470 writeln!(out, " return new byte[0];").ok();
2471 writeln!(out, " }}").ok();
2472 writeln!(out, " byte[] bytes = new byte[(int) len];").ok();
2473 writeln!(
2474 out,
2475 " MemorySegment.copy(ptr, ValueLayout.JAVA_BYTE.byteSize() * 0, bytes, 0, (int) len);"
2476 )
2477 .ok();
2478 writeln!(out, " return bytes;").ok();
2479 writeln!(out, " }}").ok();
2480 }
2481}
2482
2483fn format_optional_value(ty: &TypeRef, default: &str) -> String {
2490 if default.contains("Optional.") {
2492 return default.to_string();
2493 }
2494
2495 let inner_ty = match ty {
2497 TypeRef::Optional(inner) => inner.as_ref(),
2498 other => other,
2499 };
2500
2501 let formatted_value = match inner_ty {
2503 TypeRef::Primitive(p) => match p {
2504 PrimitiveType::I64 | PrimitiveType::U64 | PrimitiveType::Isize | PrimitiveType::Usize => {
2505 if default.ends_with('L') || default.ends_with('l') {
2507 default.to_string()
2508 } else if default.parse::<i64>().is_ok() {
2509 format!("{}L", default)
2510 } else {
2511 default.to_string()
2512 }
2513 }
2514 PrimitiveType::F32 => {
2515 if default.ends_with('f') || default.ends_with('F') {
2517 default.to_string()
2518 } else if default.parse::<f32>().is_ok() {
2519 format!("{}f", default)
2520 } else {
2521 default.to_string()
2522 }
2523 }
2524 PrimitiveType::F64 => {
2525 default.to_string()
2527 }
2528 _ => default.to_string(),
2529 },
2530 _ => default.to_string(),
2531 };
2532
2533 format!("Optional.of({})", formatted_value)
2534}
2535
2536fn gen_builder_class(package: &str, typ: &TypeDef) -> String {
2537 let mut body = String::with_capacity(2048);
2538
2539 emit_javadoc(&mut body, &typ.doc, "");
2540 writeln!(body, "public class {}Builder {{", typ.name).ok();
2541 writeln!(body).ok();
2542
2543 for field in &typ.fields {
2545 let field_name = safe_java_field_name(&field.name);
2546
2547 if field.name.starts_with('_') && field.name[1..].chars().all(|c| c.is_ascii_digit())
2549 || field.name.chars().next().is_none_or(|c| c.is_ascii_digit())
2550 {
2551 continue;
2552 }
2553
2554 let field_type = if field.optional {
2557 format!("Optional<{}>", java_boxed_type(&field.ty))
2558 } else if matches!(field.ty, TypeRef::Duration) {
2559 java_boxed_type(&field.ty).to_string()
2560 } else {
2561 java_type(&field.ty).to_string()
2562 };
2563
2564 let default_value = if field.optional {
2565 if let Some(default) = &field.default {
2567 format_optional_value(&field.ty, default)
2569 } else {
2570 "Optional.empty()".to_string()
2572 }
2573 } else {
2574 if let Some(default) = &field.default {
2576 default.clone()
2577 } else {
2578 match &field.ty {
2579 TypeRef::String | TypeRef::Char | TypeRef::Path => "\"\"".to_string(),
2580 TypeRef::Json => "null".to_string(),
2581 TypeRef::Bytes => "new byte[0]".to_string(),
2582 TypeRef::Primitive(p) => match p {
2583 PrimitiveType::Bool => "false".to_string(),
2584 PrimitiveType::F32 | PrimitiveType::F64 => "0.0".to_string(),
2585 _ => "0".to_string(),
2586 },
2587 TypeRef::Vec(_) => "List.of()".to_string(),
2588 TypeRef::Map(_, _) => "Map.of()".to_string(),
2589 TypeRef::Optional(_) => "Optional.empty()".to_string(),
2590 TypeRef::Duration => "null".to_string(),
2591 _ => "null".to_string(),
2592 }
2593 }
2594 };
2595
2596 writeln!(body, " private {} {} = {};", field_type, field_name, default_value).ok();
2597 }
2598
2599 writeln!(body).ok();
2600
2601 for field in &typ.fields {
2603 if field.name.starts_with('_') && field.name[1..].chars().all(|c| c.is_ascii_digit())
2605 || field.name.chars().next().is_none_or(|c| c.is_ascii_digit())
2606 {
2607 continue;
2608 }
2609
2610 let field_name = safe_java_field_name(&field.name);
2611 let field_name_pascal = to_class_name(&field.name);
2612 let field_type = if field.optional {
2613 format!("Optional<{}>", java_boxed_type(&field.ty))
2614 } else if matches!(field.ty, TypeRef::Duration) {
2615 java_boxed_type(&field.ty).to_string()
2616 } else {
2617 java_type(&field.ty).to_string()
2618 };
2619
2620 writeln!(body, " /** Sets the {} field. */", field_name).ok();
2621 writeln!(
2622 body,
2623 " public {}Builder with{}(final {} value) {{",
2624 typ.name, field_name_pascal, field_type
2625 )
2626 .ok();
2627 writeln!(body, " this.{} = value;", field_name).ok();
2628 writeln!(body, " return this;").ok();
2629 writeln!(body, " }}").ok();
2630 writeln!(body).ok();
2631 }
2632
2633 writeln!(body, " /** Builds the {} instance. */", typ.name).ok();
2635 writeln!(body, " public {} build() {{", typ.name).ok();
2636 writeln!(body, " return new {}(", typ.name).ok();
2637 let non_tuple_fields: Vec<_> = typ
2638 .fields
2639 .iter()
2640 .filter(|f| {
2641 !(f.name.starts_with('_') && f.name[1..].chars().all(|c| c.is_ascii_digit())
2643 || f.name.chars().next().is_none_or(|c| c.is_ascii_digit()))
2644 })
2645 .collect();
2646 for (i, field) in non_tuple_fields.iter().enumerate() {
2647 let field_name = safe_java_field_name(&field.name);
2648 let comma = if i < non_tuple_fields.len() - 1 { "," } else { "" };
2649 writeln!(body, " {}{}", field_name, comma).ok();
2650 }
2651 writeln!(body, " );").ok();
2652 writeln!(body, " }}").ok();
2653
2654 writeln!(body, "}}").ok();
2655
2656 let mut out = String::with_capacity(body.len() + 512);
2658
2659 writeln!(out, "// DO NOT EDIT - auto-generated by alef").ok();
2660 writeln!(out, "package {};", package).ok();
2661 writeln!(out).ok();
2662
2663 if body.contains("List<") {
2664 writeln!(out, "import java.util.List;").ok();
2665 }
2666 if body.contains("Map<") {
2667 writeln!(out, "import java.util.Map;").ok();
2668 }
2669 if body.contains("Optional<") {
2670 writeln!(out, "import java.util.Optional;").ok();
2671 }
2672
2673 writeln!(out).ok();
2674 out.push_str(&body);
2675
2676 out
2677}