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