1use std::collections::{BTreeSet, HashMap};
15use std::fmt::Write;
16use zerodds_idl::ast::{
19 Annotation, AnnotationParams, CaseLabel, ConstExpr, ConstrTypeDecl, Declarator, Definition,
20 EnumDef, ExceptDecl, IntegerType, InterfaceDcl, InterfaceDef, Literal, LiteralKind, Member,
21 ScopedName, Specification, StructDcl, StructDef, SwitchTypeSpec, TypeDecl, TypeSpec,
22 TypedefDecl, UnionDcl, UnionDef,
23};
24
25use zerodds_idl::semantics::annotations::PlacementKind;
26
27use crate::JavaGenOptions;
28use crate::annotations::{
29 enum_value_override, has_nested, lower_or_empty, member_annotation_lines, type_annotation_lines,
30};
31use crate::bitset::{emit_bitmask_file, emit_bitset_file};
32use crate::error::JavaGenError;
33use crate::keywords::sanitize_identifier;
34use crate::type_map::{
35 floating_to_java, floating_to_java_boxed, integer_to_java, integer_to_java_boxed, is_unsigned,
36 primitive_to_java, primitive_to_java_boxed,
37};
38use crate::verbatim::emit_verbatim_at;
39
40#[derive(Debug, Clone, PartialEq, Eq)]
42pub struct JavaFile {
43 pub package_path: String,
45 pub class_name: String,
47 pub source: String,
49}
50
51impl JavaFile {
52 #[must_use]
54 pub fn relative_path(&self) -> String {
55 let dir = self.package_path.replace('.', "/");
56 if dir.is_empty() {
57 format!("{}.java", self.class_name)
58 } else {
59 format!("{dir}/{}.java", self.class_name)
60 }
61 }
62}
63
64pub(crate) fn emit_files(
66 spec: &Specification,
67 opts: &JavaGenOptions,
68) -> Result<Vec<JavaFile>, JavaGenError> {
69 detect_inheritance_cycles(spec)?;
70
71 let parent_of = collect_base_chain_index(spec);
74
75 let mut files: Vec<JavaFile> = Vec::new();
76 let pkg = sanitize_package(&opts.root_package);
77 let ctx = EmitCtx { parent_of };
78 walk_definitions(&spec.definitions, &pkg, opts, &mut files, &ctx)?;
79 Ok(files)
80}
81
82#[derive(Debug, Default)]
86pub(crate) struct EmitCtx {
87 pub parent_of: std::collections::HashMap<String, String>,
91}
92
93fn sanitize_package(p: &str) -> String {
94 p.trim_matches('.').to_string()
95}
96
97fn walk_definitions(
99 defs: &[Definition],
100 pkg: &str,
101 opts: &JavaGenOptions,
102 files: &mut Vec<JavaFile>,
103 ctx: &EmitCtx,
104) -> Result<(), JavaGenError> {
105 for d in defs {
106 match d {
107 Definition::Module(m) => {
108 let name = sanitize_identifier(&m.name.text)?.to_lowercase();
109 let sub_pkg = if pkg.is_empty() {
110 name
111 } else {
112 format!("{pkg}.{name}")
113 };
114 walk_definitions(&m.definitions, &sub_pkg, opts, files, ctx)?;
115 }
116 Definition::Type(td) => emit_type_decl_top(td, pkg, opts, files, ctx)?,
117 Definition::Const(c) => {
118 let file = emit_const_holder(c, pkg, opts)?;
119 files.push(file);
120 }
121 Definition::Except(e) => {
122 let file = emit_exception_file(e, pkg, opts)?;
123 files.push(file);
124 }
125 Definition::Interface(InterfaceDcl::Def(iface)) => {
126 if is_service_interface(iface) {
127 emit_service_interface_files(iface, pkg, opts, files)?;
128 } else {
129 files.push(emit_non_service_interface_file(iface, pkg, opts)?);
131 }
132 }
133 Definition::Interface(InterfaceDcl::Forward(_)) => {
134 }
136 Definition::ValueDef(v) => {
137 let value_files = emit_value_type_files(v, pkg, opts)?;
138 files.extend(value_files);
139 }
140 Definition::ValueBox(_) | Definition::ValueForward(_) => {
141 }
143 Definition::TypeId(_)
144 | Definition::TypePrefix(_)
145 | Definition::Import(_)
146 | Definition::Component(_)
147 | Definition::Home(_)
148 | Definition::Event(_)
149 | Definition::Porttype(_)
150 | Definition::Connector(_)
151 | Definition::TemplateModule(_)
152 | Definition::TemplateModuleInst(_) => {
153 return Err(JavaGenError::UnsupportedConstruct {
154 construct: "corba/ccm/template construct".into(),
155 context: None,
156 });
157 }
158 Definition::Annotation(_) => {
159 }
163 Definition::VendorExtension(v) => {
164 return Err(JavaGenError::UnsupportedConstruct {
165 construct: format!("vendor-extension:{}", v.production_name),
166 context: None,
167 });
168 }
169 }
170 }
171 Ok(())
172}
173
174fn emit_type_decl_top(
175 td: &TypeDecl,
176 pkg: &str,
177 opts: &JavaGenOptions,
178 files: &mut Vec<JavaFile>,
179 ctx: &EmitCtx,
180) -> Result<(), JavaGenError> {
181 match td {
182 TypeDecl::Constr(c) => match c {
183 ConstrTypeDecl::Struct(StructDcl::Def(s)) => {
184 files.push(emit_struct_file(s, pkg, opts, ctx)?);
185 if ctx.parent_of.values().any(|p| p == &s.name.text) {
190 files.push(emit_struct_companion_interface(s, pkg, opts)?);
191 }
192 Ok(())
193 }
194 ConstrTypeDecl::Struct(StructDcl::Forward(_)) => {
195 Ok(())
198 }
199 ConstrTypeDecl::Union(UnionDcl::Def(u)) => {
200 files.extend(emit_union_files(u, pkg, opts)?);
201 Ok(())
202 }
203 ConstrTypeDecl::Union(UnionDcl::Forward(_)) => Ok(()),
204 ConstrTypeDecl::Enum(e) => {
205 files.push(emit_enum_file(e, pkg, opts)?);
206 Ok(())
207 }
208 ConstrTypeDecl::Bitset(b) => {
209 files.push(emit_bitset_file(b, pkg, opts)?);
210 Ok(())
211 }
212 ConstrTypeDecl::Bitmask(b) => {
213 files.push(emit_bitmask_file(b, pkg, opts)?);
214 Ok(())
215 }
216 },
217 TypeDecl::Typedef(t) => {
218 files.extend(emit_typedef_files(t, pkg, opts)?);
219 Ok(())
220 }
221 TypeDecl::Native(_) => Ok(()),
224 }
225}
226
227fn emit_struct_file(
232 s: &StructDef,
233 pkg: &str,
234 opts: &JavaGenOptions,
235 ctx: &EmitCtx,
236) -> Result<JavaFile, JavaGenError> {
237 let class = sanitize_identifier(&s.name.text)?;
238 let mut imports = ImportSet::default();
239 let ind = indent_unit(opts);
240
241 for m in &s.members {
243 collect_member_imports(m, &mut imports);
244 }
245
246 let mut body = String::new();
247
248 emit_verbatim_at(&mut body, "", &s.annotations, PlacementKind::BeginFile)?;
251
252 emit_verbatim_at(
254 &mut body,
255 "",
256 &s.annotations,
257 PlacementKind::BeforeDeclaration,
258 )?;
259
260 for line in type_annotation_lines(&s.annotations) {
262 writeln!(body, "{line}").map_err(fmt_err)?;
263 }
264
265 let extends = if let Some(base) = &s.base {
266 let base_str = scoped_to_java(base);
267 format!(" extends {base_str}")
268 } else {
269 String::new()
270 };
271
272 let mut implements: Vec<String> = transitive_ancestors_beyond_base(&s.name.text, ctx)
276 .into_iter()
277 .map(|anc| format!("{anc}Interface"))
278 .collect();
279
280 let lowered_type = lower_or_empty(&s.annotations);
294 if !has_nested(&lowered_type) && s.base.is_none() {
295 implements.push(format!("org.omg.dds.topic.TopicType<{class}>"));
296 }
297 let implements_clause = if implements.is_empty() {
298 String::new()
299 } else {
300 format!(" implements {}", implements.join(", "))
301 };
302
303 writeln!(body, "public class {class}{extends}{implements_clause} {{").map_err(fmt_err)?;
304
305 emit_verbatim_at(
308 &mut body,
309 &ind,
310 &s.annotations,
311 PlacementKind::BeginDeclaration,
312 )?;
313
314 for m in &s.members {
316 emit_member_field(&mut body, m, &ind)?;
317 }
318 writeln!(body).map_err(fmt_err)?;
319
320 writeln!(body, "{ind}public {class}() {{}}").map_err(fmt_err)?;
322 writeln!(body).map_err(fmt_err)?;
323
324 for m in &s.members {
326 emit_member_accessors(&mut body, m, &ind)?;
327 }
328
329 emit_verbatim_at(
332 &mut body,
333 &ind,
334 &s.annotations,
335 PlacementKind::EndDeclaration,
336 )?;
337
338 writeln!(body, "}}").map_err(fmt_err)?;
339
340 emit_verbatim_at(
342 &mut body,
343 "",
344 &s.annotations,
345 PlacementKind::AfterDeclaration,
346 )?;
347 emit_verbatim_at(&mut body, "", &s.annotations, PlacementKind::EndFile)?;
348
349 let source = wrap_compilation_unit(pkg, &imports, &body);
350 Ok(JavaFile {
351 package_path: pkg.to_string(),
352 class_name: class,
353 source,
354 })
355}
356
357fn emit_enum_file(e: &EnumDef, pkg: &str, opts: &JavaGenOptions) -> Result<JavaFile, JavaGenError> {
358 let class = sanitize_identifier(&e.name.text)?;
359 let ind = indent_unit(opts);
360 let mut body = String::new();
361
362 emit_verbatim_at(&mut body, "", &e.annotations, PlacementKind::BeginFile)?;
363 emit_verbatim_at(
364 &mut body,
365 "",
366 &e.annotations,
367 PlacementKind::BeforeDeclaration,
368 )?;
369
370 for line in type_annotation_lines(&e.annotations) {
372 writeln!(body, "{line}").map_err(fmt_err)?;
373 }
374
375 writeln!(body, "public enum {class} {{").map_err(fmt_err)?;
376 emit_verbatim_at(
377 &mut body,
378 &ind,
379 &e.annotations,
380 PlacementKind::BeginDeclaration,
381 )?;
382
383 let count = e.enumerators.len();
384 let mut next_implicit: i64 = 0;
385 for (idx, en) in e.enumerators.iter().enumerate() {
386 let name = sanitize_identifier(&en.name.text)?;
387 let sep = if idx + 1 == count { ';' } else { ',' };
388 let value_lit = match enum_value_override(&en.annotations) {
391 Some(raw) => match raw.parse::<i64>() {
392 Ok(n) => {
393 next_implicit = n + 1;
394 n.to_string()
395 }
396 Err(_) => raw,
397 },
398 None => {
399 let n = next_implicit;
400 next_implicit += 1;
401 n.to_string()
402 }
403 };
404 writeln!(body, "{ind}{name}({value_lit}){sep}").map_err(fmt_err)?;
405 }
406 writeln!(body).map_err(fmt_err)?;
407 writeln!(body, "{ind}private final int value;").map_err(fmt_err)?;
408 writeln!(body, "{ind}{class}(int value) {{ this.value = value; }}").map_err(fmt_err)?;
409 writeln!(body, "{ind}public int value() {{ return value; }}").map_err(fmt_err)?;
410 emit_verbatim_at(
411 &mut body,
412 &ind,
413 &e.annotations,
414 PlacementKind::EndDeclaration,
415 )?;
416 writeln!(body, "}}").map_err(fmt_err)?;
417 emit_verbatim_at(
418 &mut body,
419 "",
420 &e.annotations,
421 PlacementKind::AfterDeclaration,
422 )?;
423 emit_verbatim_at(&mut body, "", &e.annotations, PlacementKind::EndFile)?;
424
425 let source = wrap_compilation_unit(pkg, &ImportSet::default(), &body);
426 Ok(JavaFile {
427 package_path: pkg.to_string(),
428 class_name: class,
429 source,
430 })
431}
432
433fn emit_union_files(
438 u: &UnionDef,
439 pkg: &str,
440 opts: &JavaGenOptions,
441) -> Result<Vec<JavaFile>, JavaGenError> {
442 let class = sanitize_identifier(&u.name.text)?;
443 let ind = indent_unit(opts);
444 let imports = ImportSet::default();
445
446 let _disc_ty = switch_type_to_java(&u.switch_type)?;
447
448 let mut permits: Vec<String> = Vec::new();
450 let mut case_records: Vec<(String, String, String)> = Vec::new(); for c in &u.cases {
452 let cpp_ty = type_for_declarator(&c.element.type_spec, &c.element.declarator)?;
453 let field_name = sanitize_identifier(&c.element.declarator.name().text)?;
454 let record_name = capitalize(&field_name);
456 if !permits.iter().any(|p| p == &record_name) {
457 permits.push(record_name.clone());
458 case_records.push((record_name, cpp_ty, field_name));
459 }
460 }
461 let permits_clause = if permits.is_empty() {
468 String::new()
469 } else {
470 let qualified: Vec<String> = permits.iter().map(|p| format!("{class}.{p}")).collect();
471 format!(" permits {}", qualified.join(", "))
472 };
473
474 let mut body = String::new();
475 emit_verbatim_at(&mut body, "", &u.annotations, PlacementKind::BeginFile)?;
476 emit_verbatim_at(
477 &mut body,
478 "",
479 &u.annotations,
480 PlacementKind::BeforeDeclaration,
481 )?;
482 if opts.java8_compat {
483 writeln!(body, "public abstract class {class} {{").map_err(fmt_err)?;
487 } else {
488 writeln!(body, "public sealed interface {class}{permits_clause} {{").map_err(fmt_err)?;
489 }
490 emit_verbatim_at(
491 &mut body,
492 &ind,
493 &u.annotations,
494 PlacementKind::BeginDeclaration,
495 )?;
496 if opts.java8_compat {
497 writeln!(body, "{ind}private {class}() {{}}").map_err(fmt_err)?;
498 writeln!(body).map_err(fmt_err)?;
499 }
500
501 let mut has_default = false;
504 for c in &u.cases {
505 for label in &c.labels {
506 match label {
507 CaseLabel::Default => {
508 has_default = true;
509 writeln!(
510 body,
511 "{ind}// case default -> {}",
512 c.element.declarator.name().text
513 )
514 .map_err(fmt_err)?;
515 }
516 CaseLabel::Value(expr) => {
517 let val = const_expr_to_java(expr);
518 writeln!(
519 body,
520 "{ind}// case {val} -> {}",
521 c.element.declarator.name().text
522 )
523 .map_err(fmt_err)?;
524 }
525 }
526 }
527 }
528 if !has_default {
529 writeln!(body, "{ind}// no explicit 'default:' branch").map_err(fmt_err)?;
530 }
531 writeln!(body).map_err(fmt_err)?;
532
533 for (record_name, field_ty, field_name) in &case_records {
535 if opts.java8_compat {
536 writeln!(
539 body,
540 "{ind}public static final class {record_name} extends {class} {{",
541 )
542 .map_err(fmt_err)?;
543 writeln!(body, "{ind}{ind}private final {field_ty} {field_name};").map_err(fmt_err)?;
544 writeln!(
545 body,
546 "{ind}{ind}public {record_name}({field_ty} {field_name}) {{ this.{field_name} = {field_name}; }}",
547 )
548 .map_err(fmt_err)?;
549 writeln!(
550 body,
551 "{ind}{ind}public {field_ty} {field_name}() {{ return {field_name}; }}",
552 )
553 .map_err(fmt_err)?;
554 writeln!(body, "{ind}}}").map_err(fmt_err)?;
555 } else {
556 writeln!(
557 body,
558 "{ind}record {record_name}({field_ty} {field_name}) implements {class} {{}}",
559 )
560 .map_err(fmt_err)?;
561 }
562 }
563 emit_verbatim_at(
564 &mut body,
565 &ind,
566 &u.annotations,
567 PlacementKind::EndDeclaration,
568 )?;
569 writeln!(body, "}}").map_err(fmt_err)?;
570 emit_verbatim_at(
571 &mut body,
572 "",
573 &u.annotations,
574 PlacementKind::AfterDeclaration,
575 )?;
576 emit_verbatim_at(&mut body, "", &u.annotations, PlacementKind::EndFile)?;
577
578 let source = wrap_compilation_unit(pkg, &imports, &body);
579 Ok(vec![JavaFile {
580 package_path: pkg.to_string(),
581 class_name: class,
582 source,
583 }])
584}
585
586fn emit_typedef_files(
587 t: &TypedefDecl,
588 pkg: &str,
589 _opts: &JavaGenOptions,
590) -> Result<Vec<JavaFile>, JavaGenError> {
591 let mut out = Vec::new();
594 for decl in &t.declarators {
595 let alias = sanitize_identifier(&decl.name().text)?;
596 let target = type_for_declarator(&t.type_spec, decl)?;
597 let imports = ImportSet::default();
598
599 let mut body = String::new();
600 writeln!(body, "public final class {alias} {{").map_err(fmt_err)?;
601 writeln!(body, " private {target} value;").map_err(fmt_err)?;
602 writeln!(body).map_err(fmt_err)?;
603 writeln!(body, " public {alias}() {{}}").map_err(fmt_err)?;
604 writeln!(
605 body,
606 " public {alias}({target} value) {{ this.value = value; }}",
607 )
608 .map_err(fmt_err)?;
609 writeln!(body).map_err(fmt_err)?;
610 writeln!(body, " public {target} value() {{ return value; }}").map_err(fmt_err)?;
611 writeln!(
612 body,
613 " public void value({target} value) {{ this.value = value; }}",
614 )
615 .map_err(fmt_err)?;
616 writeln!(body, "}}").map_err(fmt_err)?;
617
618 let source = wrap_compilation_unit(pkg, &imports, &body);
619 out.push(JavaFile {
620 package_path: pkg.to_string(),
621 class_name: alias,
622 source,
623 });
624 }
625 Ok(out)
626}
627
628fn emit_exception_file(
629 e: &ExceptDecl,
630 pkg: &str,
631 opts: &JavaGenOptions,
632) -> Result<JavaFile, JavaGenError> {
633 let class = sanitize_identifier(&e.name.text)?;
634 let ind = indent_unit(opts);
635 let mut imports = ImportSet::default();
636 for m in &e.members {
637 collect_member_imports(m, &mut imports);
638 }
639
640 let mut body = String::new();
641 writeln!(body, "public class {class} extends RuntimeException {{").map_err(fmt_err)?;
642 for m in &e.members {
643 emit_member_field(&mut body, m, &ind)?;
644 }
645 writeln!(body).map_err(fmt_err)?;
646 writeln!(body, "{ind}public {class}() {{ super(); }}").map_err(fmt_err)?;
647 writeln!(
648 body,
649 "{ind}public {class}(String message) {{ super(message); }}",
650 )
651 .map_err(fmt_err)?;
652 writeln!(body).map_err(fmt_err)?;
653 for m in &e.members {
654 emit_member_accessors(&mut body, m, &ind)?;
655 }
656 writeln!(body, "}}").map_err(fmt_err)?;
657
658 let source = wrap_compilation_unit(pkg, &imports, &body);
659 Ok(JavaFile {
660 package_path: pkg.to_string(),
661 class_name: class,
662 source,
663 })
664}
665
666fn emit_const_holder(
667 c: &zerodds_idl::ast::ConstDecl,
668 pkg: &str,
669 _opts: &JavaGenOptions,
670) -> Result<JavaFile, JavaGenError> {
671 let name = sanitize_identifier(&c.name.text)?;
674 let class = format!("{name}Constant");
675 let java_ty = const_type_to_java(&c.type_)?;
676 let val = const_expr_to_java(&c.value);
677 let mut body = String::new();
678 writeln!(body, "public final class {class} {{").map_err(fmt_err)?;
679 writeln!(body, " public static final {java_ty} {name} = {val};").map_err(fmt_err)?;
680 writeln!(body, " private {class}() {{}}").map_err(fmt_err)?;
681 writeln!(body, "}}").map_err(fmt_err)?;
682 let source = wrap_compilation_unit(pkg, &ImportSet::default(), &body);
683 Ok(JavaFile {
684 package_path: pkg.to_string(),
685 class_name: class,
686 source,
687 })
688}
689
690fn emit_member_field(out: &mut String, m: &Member, ind: &str) -> Result<(), JavaGenError> {
695 let optional = has_optional_annotation(&m.annotations);
696 let ann_lines = member_annotation_lines(&m.annotations);
697 for decl in &m.declarators {
698 let java_ty = type_for_declarator(&m.type_spec, decl)?;
699 let name = sanitize_identifier(&decl.name().text)?;
700 let final_ty = if optional {
701 format!("java.util.Optional<{}>", boxed_for_optional(&m.type_spec))
702 } else {
703 java_ty
704 };
705 for ann in &ann_lines {
706 writeln!(out, "{ind}{ann}").map_err(fmt_err)?;
707 }
708 if let TypeSpec::Primitive(zerodds_idl::ast::PrimitiveType::Integer(i)) = &m.type_spec {
710 if is_unsigned(*i) {
711 writeln!(
712 out,
713 "{ind}/** unsigned IDL value (Java unsigned-workaround) */"
714 )
715 .map_err(fmt_err)?;
716 }
717 }
718 writeln!(out, "{ind}private {final_ty} {name};").map_err(fmt_err)?;
719 }
720 Ok(())
721}
722
723fn emit_member_accessors(out: &mut String, m: &Member, ind: &str) -> Result<(), JavaGenError> {
724 let optional = has_optional_annotation(&m.annotations);
725 for decl in &m.declarators {
726 let java_ty = type_for_declarator(&m.type_spec, decl)?;
727 let name = sanitize_identifier(&decl.name().text)?;
728 let cap = capitalize(&name);
729 let final_ty = if optional {
730 format!("java.util.Optional<{}>", boxed_for_optional(&m.type_spec))
731 } else {
732 java_ty.clone()
733 };
734 writeln!(
735 out,
736 "{ind}public {final_ty} get{cap}() {{ return {name}; }}"
737 )
738 .map_err(fmt_err)?;
739 writeln!(
740 out,
741 "{ind}public void set{cap}({final_ty} {name}) {{ this.{name} = {name}; }}",
742 )
743 .map_err(fmt_err)?;
744 }
745 Ok(())
746}
747
748fn boxed_for_optional(ts: &TypeSpec) -> String {
749 match ts {
750 TypeSpec::Primitive(p) => primitive_to_java_boxed(*p).to_string(),
751 TypeSpec::Scoped(s) => scoped_to_java(s),
752 TypeSpec::String(_) => "String".into(),
753 TypeSpec::Sequence(s) => {
754 let inner = match &*s.elem {
756 TypeSpec::Primitive(p) => primitive_to_java_boxed(*p).to_string(),
757 TypeSpec::Scoped(sn) => scoped_to_java(sn),
758 TypeSpec::String(_) => "String".into(),
759 _ => "Object".into(),
760 };
761 format!("java.util.List<{inner}>")
762 }
763 _ => "Object".into(),
764 }
765}
766
767pub(crate) fn type_for_declarator(
773 ts: &TypeSpec,
774 decl: &Declarator,
775) -> Result<String, JavaGenError> {
776 let base = typespec_to_java(ts)?;
777 match decl {
778 Declarator::Simple(_) => Ok(base),
779 Declarator::Array(arr) => {
780 let mut suffix = String::new();
781 for _ in &arr.sizes {
782 suffix.push_str("[]");
783 }
784 Ok(format!("{base}{suffix}"))
785 }
786 }
787}
788
789pub(crate) fn typespec_to_java(ts: &TypeSpec) -> Result<String, JavaGenError> {
791 match ts {
792 TypeSpec::Primitive(p) => Ok(primitive_to_java(*p).to_string()),
793 TypeSpec::Scoped(s) => Ok(scoped_to_java(s)),
794 TypeSpec::Sequence(s) => {
795 let inner = match &*s.elem {
796 TypeSpec::Primitive(p) => primitive_to_java_boxed(*p).to_string(),
797 TypeSpec::Scoped(sn) => scoped_to_java(sn),
798 TypeSpec::String(_) => "String".into(),
799 other => typespec_to_java(other)?,
800 };
801 Ok(format!("java.util.List<{inner}>"))
802 }
803 TypeSpec::String(_) => Ok("String".into()),
804 TypeSpec::Map(m) => {
805 let k = match &*m.key {
806 TypeSpec::Primitive(p) => primitive_to_java_boxed(*p).to_string(),
807 TypeSpec::Scoped(sn) => scoped_to_java(sn),
808 TypeSpec::String(_) => "String".into(),
809 other => typespec_to_java(other)?,
810 };
811 let v = match &*m.value {
812 TypeSpec::Primitive(p) => primitive_to_java_boxed(*p).to_string(),
813 TypeSpec::Scoped(sn) => scoped_to_java(sn),
814 TypeSpec::String(_) => "String".into(),
815 other => typespec_to_java(other)?,
816 };
817 Ok(format!("java.util.Map<{k}, {v}>"))
818 }
819 TypeSpec::Fixed(_) => {
820 Ok("java.math.BigDecimal".into())
825 }
826 TypeSpec::Any => {
827 Ok("Object".into())
833 }
834 }
835}
836
837pub(crate) fn switch_type_to_java(s: &SwitchTypeSpec) -> Result<String, JavaGenError> {
838 Ok(match s {
839 SwitchTypeSpec::Integer(i) => integer_to_java(*i).to_string(),
840 SwitchTypeSpec::Char => "char".into(),
841 SwitchTypeSpec::Boolean => "boolean".into(),
842 SwitchTypeSpec::Octet => "byte".into(),
843 SwitchTypeSpec::Scoped(s) => scoped_to_java(s),
844 })
845}
846
847fn const_type_to_java(t: &zerodds_idl::ast::ConstType) -> Result<String, JavaGenError> {
848 Ok(match t {
849 zerodds_idl::ast::ConstType::Integer(i) => integer_to_java(*i).to_string(),
850 zerodds_idl::ast::ConstType::Floating(f) => floating_to_java(*f).to_string(),
851 zerodds_idl::ast::ConstType::Boolean => "boolean".into(),
852 zerodds_idl::ast::ConstType::Char => "char".into(),
853 zerodds_idl::ast::ConstType::WideChar => "char".into(),
854 zerodds_idl::ast::ConstType::Octet => "byte".into(),
855 zerodds_idl::ast::ConstType::String { .. } => "String".into(),
856 zerodds_idl::ast::ConstType::Scoped(s) => scoped_to_java(s),
857 zerodds_idl::ast::ConstType::Fixed => "java.math.BigDecimal".into(),
858 })
859}
860
861fn scoped_to_java(s: &ScopedName) -> String {
862 let parts: Vec<String> = s.parts.iter().map(|p| p.text.clone()).collect();
863 parts.join(".")
864}
865
866#[derive(Debug, Default, Clone)]
871pub(crate) struct ImportSet {
872 #[allow(dead_code)]
873 imports: BTreeSet<&'static str>,
874}
875
876impl ImportSet {
877 #[allow(dead_code)]
878 fn add(&mut self, fqn: &'static str) {
879 self.imports.insert(fqn);
880 }
881}
882
883#[allow(clippy::needless_pass_by_ref_mut)]
886fn collect_member_imports(_m: &Member, _inc: &mut ImportSet) {
887 }
890
891pub(crate) fn wrap_compilation_unit(pkg: &str, _imports: &ImportSet, body: &str) -> String {
896 let mut out = String::new();
897 let _ = writeln!(out, "// Generated by zerodds idl-java. Do not edit.");
898 if !pkg.is_empty() {
899 let _ = writeln!(out, "package {pkg};");
900 let _ = writeln!(out);
901 }
902 out.push_str(body);
906 out
907}
908
909pub(crate) fn const_expr_to_java(e: &ConstExpr) -> String {
915 match e {
916 ConstExpr::Literal(l) => literal_to_java(l),
917 ConstExpr::Scoped(s) => scoped_to_java(s),
918 ConstExpr::Unary { op, operand, .. } => {
919 let prefix = match op {
920 zerodds_idl::ast::UnaryOp::Plus => "+",
921 zerodds_idl::ast::UnaryOp::Minus => "-",
922 zerodds_idl::ast::UnaryOp::BitNot => "~",
923 };
924 format!("{prefix}{}", const_expr_to_java(operand))
925 }
926 ConstExpr::Binary { op, lhs, rhs, .. } => {
927 let opstr = match op {
928 zerodds_idl::ast::BinaryOp::Or => "|",
929 zerodds_idl::ast::BinaryOp::Xor => "^",
930 zerodds_idl::ast::BinaryOp::And => "&",
931 zerodds_idl::ast::BinaryOp::Shl => "<<",
932 zerodds_idl::ast::BinaryOp::Shr => ">>",
933 zerodds_idl::ast::BinaryOp::Add => "+",
934 zerodds_idl::ast::BinaryOp::Sub => "-",
935 zerodds_idl::ast::BinaryOp::Mul => "*",
936 zerodds_idl::ast::BinaryOp::Div => "/",
937 zerodds_idl::ast::BinaryOp::Mod => "%",
938 };
939 format!(
940 "({} {opstr} {})",
941 const_expr_to_java(lhs),
942 const_expr_to_java(rhs)
943 )
944 }
945 }
946}
947
948fn literal_to_java(l: &Literal) -> String {
949 match l.kind {
950 LiteralKind::Boolean
951 | LiteralKind::Integer
952 | LiteralKind::Floating
953 | LiteralKind::Char
954 | LiteralKind::WideChar
955 | LiteralKind::String
956 | LiteralKind::WideString
957 | LiteralKind::Fixed => l.raw.clone(),
958 }
959}
960
961fn has_optional_annotation(anns: &[Annotation]) -> bool {
966 has_named_annotation(anns, "optional")
967}
968
969fn has_named_annotation(anns: &[Annotation], name: &str) -> bool {
970 anns.iter().any(|a| {
971 a.name.parts.last().is_some_and(|p| p.text == name)
972 && matches!(a.params, AnnotationParams::None | AnnotationParams::Empty)
973 })
974}
975
976fn collect_inheritance_edges(
982 defs: &[Definition],
983 parents: &mut HashMap<String, String>,
984 prefix: &str,
985) {
986 for d in defs {
987 match d {
988 Definition::Module(m) => {
989 let new_prefix = if prefix.is_empty() {
990 m.name.text.clone()
991 } else {
992 format!("{prefix}.{}", m.name.text)
993 };
994 collect_inheritance_edges(&m.definitions, parents, &new_prefix);
995 }
996 Definition::Type(TypeDecl::Constr(ConstrTypeDecl::Struct(StructDcl::Def(s)))) => {
997 let key = if prefix.is_empty() {
998 s.name.text.clone()
999 } else {
1000 format!("{prefix}.{}", s.name.text)
1001 };
1002 if let Some(b) = &s.base {
1003 let base_str = b
1004 .parts
1005 .iter()
1006 .map(|p| p.text.clone())
1007 .collect::<Vec<_>>()
1008 .join(".");
1009 parents.insert(key, base_str);
1010 }
1011 }
1012 _ => {}
1013 }
1014 }
1015}
1016
1017fn detect_inheritance_cycles(spec: &Specification) -> Result<(), JavaGenError> {
1018 let mut parents: HashMap<String, String> = HashMap::new();
1019 collect_inheritance_edges(&spec.definitions, &mut parents, "");
1020
1021 for start in parents.keys() {
1022 let mut current = start.clone();
1023 let mut visited: BTreeSet<String> = BTreeSet::new();
1024 visited.insert(current.clone());
1025 while let Some(p) = parents.get(¤t) {
1026 let resolved = parents
1027 .keys()
1028 .find(|k| *k == p || k.ends_with(&format!(".{p}")))
1029 .cloned()
1030 .unwrap_or_else(|| p.clone());
1031 if visited.contains(&resolved) {
1032 return Err(JavaGenError::InheritanceCycle {
1033 type_name: short_name(&resolved),
1034 });
1035 }
1036 visited.insert(resolved.clone());
1037 if resolved == current {
1038 return Err(JavaGenError::InheritanceCycle {
1039 type_name: short_name(&resolved),
1040 });
1041 }
1042 current = resolved;
1043 if !parents.contains_key(¤t) {
1044 break;
1045 }
1046 }
1047 }
1048 Ok(())
1049}
1050
1051fn short_name(s: &str) -> String {
1052 s.rsplit('.').next().unwrap_or(s).to_string()
1053}
1054
1055pub(crate) fn indent_unit(opts: &JavaGenOptions) -> String {
1060 " ".repeat(opts.indent_width)
1061}
1062
1063pub(crate) fn capitalize(s: &str) -> String {
1064 let mut chars = s.chars();
1065 match chars.next() {
1066 Some(c) => c.to_uppercase().collect::<String>() + chars.as_str(),
1067 None => String::new(),
1068 }
1069}
1070
1071pub(crate) fn fmt_err(_: core::fmt::Error) -> JavaGenError {
1072 JavaGenError::Internal("string formatting failed".into())
1073}
1074
1075pub(crate) fn wrap_compilation_unit_default(pkg: &str, body: &str) -> String {
1079 wrap_compilation_unit(pkg, &ImportSet::default(), body)
1080}
1081
1082fn collect_base_chain_index(spec: &Specification) -> std::collections::HashMap<String, String> {
1091 let mut out: std::collections::HashMap<String, String> = std::collections::HashMap::new();
1092 fn visit(defs: &[Definition], out: &mut std::collections::HashMap<String, String>) {
1097 for d in defs {
1098 match d {
1099 Definition::Module(m) => visit(&m.definitions, out),
1100 Definition::Type(TypeDecl::Constr(ConstrTypeDecl::Struct(StructDcl::Def(s)))) => {
1101 if let Some(b) = &s.base {
1102 out.insert(s.name.text.clone(), scoped_to_short(b));
1103 }
1104 }
1105 _ => {}
1106 }
1107 }
1108 }
1109 visit(&spec.definitions, &mut out);
1110 out
1111}
1112
1113fn transitive_ancestors_beyond_base(name: &str, ctx: &EmitCtx) -> Vec<String> {
1117 let mut out: Vec<String> = Vec::new();
1118 let direct = match ctx.parent_of.get(name) {
1119 Some(p) => p.clone(),
1120 None => return out,
1121 };
1122 let mut current = direct;
1123 let mut guard = 0usize;
1124 while let Some(p) = ctx.parent_of.get(¤t) {
1125 if guard > 64 {
1126 break;
1127 }
1128 guard += 1;
1129 out.push(p.clone());
1130 current = p.clone();
1131 }
1132 out
1133}
1134
1135fn scoped_to_short(s: &ScopedName) -> String {
1136 s.parts.last().map(|p| p.text.clone()).unwrap_or_default()
1137}
1138
1139fn emit_struct_companion_interface(
1145 s: &StructDef,
1146 pkg: &str,
1147 opts: &JavaGenOptions,
1148) -> Result<JavaFile, JavaGenError> {
1149 let class = sanitize_identifier(&s.name.text)?;
1150 let interface_name = format!("{class}Interface");
1151 let ind = indent_unit(opts);
1152 let mut body = String::new();
1153 writeln!(
1154 body,
1155 "/** Companion interface for {class}; lets sub-sub-classes \
1156 participate in the {class} contract via `implements`. */",
1157 )
1158 .map_err(fmt_err)?;
1159 writeln!(body, "public interface {interface_name} {{").map_err(fmt_err)?;
1160 for m in &s.members {
1167 let opt = has_optional_annotation(&m.annotations);
1168 for decl in &m.declarators {
1169 let java_ty = type_for_declarator(&m.type_spec, decl)?;
1170 let name = sanitize_identifier(&decl.name().text)?;
1171 let cap = capitalize(&name);
1172 let final_ty = if opt {
1173 format!("java.util.Optional<{}>", boxed_for_optional(&m.type_spec))
1174 } else {
1175 java_ty
1176 };
1177 writeln!(body, "{ind}{final_ty} get{cap}();").map_err(fmt_err)?;
1178 }
1179 }
1180 writeln!(body, "}}").map_err(fmt_err)?;
1181 let source = wrap_compilation_unit_default(pkg, &body);
1182 Ok(JavaFile {
1183 package_path: pkg.to_string(),
1184 class_name: interface_name,
1185 source,
1186 })
1187}
1188
1189#[allow(dead_code)]
1193fn _unused_marker(_i: IntegerType) {
1194 let _ = integer_to_java_boxed;
1195 let _ = floating_to_java_boxed;
1196}
1197
1198fn emit_value_type_files(
1207 v: &zerodds_idl::ast::ValueDef,
1208 pkg: &str,
1209 opts: &JavaGenOptions,
1210) -> Result<Vec<JavaFile>, JavaGenError> {
1211 use zerodds_idl::ast::{Export, StateVisibility, ValueElement};
1212
1213 let class = sanitize_identifier(&v.name.text)?;
1214 let abstract_name = format!("{class}Abstract");
1215 let ind = indent_unit(opts);
1216 let imports = ImportSet::default();
1217
1218 let mut body = String::new();
1220 let extends = match &v.inheritance {
1221 Some(inh) if !inh.bases.is_empty() => {
1222 let base = scoped_to_java(&inh.bases[0]);
1224 format!(" extends {base}Abstract")
1225 }
1226 _ => String::new(),
1227 };
1228 let supports = match &v.inheritance {
1229 Some(inh) if !inh.supports.is_empty() => {
1230 let s: Vec<String> = inh.supports.iter().map(scoped_to_java).collect();
1231 format!(" implements {}", s.join(", "))
1232 }
1233 _ => String::new(),
1234 };
1235
1236 writeln!(
1237 body,
1238 "public abstract class {abstract_name}{extends}{supports} {{"
1239 )
1240 .map_err(fmt_err)?;
1241
1242 for el in &v.elements {
1243 match el {
1244 ValueElement::State(s) => {
1245 let ty = typespec_to_java(&s.type_spec)?;
1246 let visibility = match s.visibility {
1247 StateVisibility::Public => "public",
1248 StateVisibility::Private => "protected",
1249 };
1250 for d in &s.declarators {
1251 let n = sanitize_identifier(&d.name().text)?;
1252 writeln!(body, "{ind}{visibility} abstract {ty} get_{n}();")
1253 .map_err(fmt_err)?;
1254 writeln!(body, "{ind}{visibility} abstract void set_{n}({ty} value);")
1255 .map_err(fmt_err)?;
1256 }
1257 }
1258 ValueElement::Init(i) => {
1259 let params: Vec<String> = i
1260 .params
1261 .iter()
1262 .map(|p| -> Result<String, JavaGenError> {
1263 let ty = typespec_to_java(&p.type_spec)?;
1264 let pname = sanitize_identifier(&p.name.text)?;
1265 Ok(format!("{ty} {pname}"))
1266 })
1267 .collect::<Result<_, _>>()?;
1268 writeln!(
1269 body,
1270 "{ind}public abstract void {}({});",
1271 sanitize_identifier(&i.name.text)?,
1272 params.join(", ")
1273 )
1274 .map_err(fmt_err)?;
1275 }
1276 ValueElement::Export(Export::Op(op)) => {
1277 let ret = match &op.return_type {
1278 None => "void".to_string(),
1279 Some(t) => typespec_to_java(t)?,
1280 };
1281 let params: Vec<String> = op
1282 .params
1283 .iter()
1284 .map(|p| -> Result<String, JavaGenError> {
1285 let ty = typespec_to_java(&p.type_spec)?;
1286 let pname = sanitize_identifier(&p.name.text)?;
1287 Ok(format!("{ty} {pname}"))
1288 })
1289 .collect::<Result<_, _>>()?;
1290 writeln!(
1291 body,
1292 "{ind}public abstract {ret} {}({});",
1293 sanitize_identifier(&op.name.text)?,
1294 params.join(", ")
1295 )
1296 .map_err(fmt_err)?;
1297 }
1298 _ => {}
1299 }
1300 }
1301 writeln!(body, "}}").map_err(fmt_err)?;
1302
1303 let abstract_source = wrap_compilation_unit(pkg, &imports, &body);
1304 let abstract_file = JavaFile {
1305 package_path: pkg.to_string(),
1306 class_name: abstract_name.clone(),
1307 source: abstract_source,
1308 };
1309
1310 let concrete_body = format!(
1312 "public class {class} extends {abstract_name} {{\n{ind}// User implementation here\n}}\n"
1313 );
1314 let concrete_source = wrap_compilation_unit(pkg, &imports, &concrete_body);
1315 let concrete_file = JavaFile {
1316 package_path: pkg.to_string(),
1317 class_name: class,
1318 source: concrete_source,
1319 };
1320
1321 Ok(vec![abstract_file, concrete_file])
1322}
1323
1324fn emit_non_service_interface_file(
1327 iface: &InterfaceDef,
1328 pkg: &str,
1329 opts: &JavaGenOptions,
1330) -> Result<JavaFile, JavaGenError> {
1331 use zerodds_idl::ast::Export;
1332
1333 let class = sanitize_identifier(&iface.name.text)?;
1334 let imports = ImportSet::default();
1335 let ind = indent_unit(opts);
1336 let mut body = String::new();
1337
1338 let extends = if iface.bases.is_empty() {
1339 String::new()
1340 } else {
1341 let bases: Vec<String> = iface.bases.iter().map(scoped_to_java).collect();
1342 format!(" extends {}", bases.join(", "))
1343 };
1344 writeln!(body, "public interface {class}{extends} {{").map_err(fmt_err)?;
1345
1346 for export in &iface.exports {
1347 match export {
1348 Export::Op(op) => {
1349 let ret = match &op.return_type {
1350 None => "void".to_string(),
1351 Some(t) => typespec_to_java(t)?,
1352 };
1353 let params: Vec<String> = op
1354 .params
1355 .iter()
1356 .map(|p| -> Result<String, JavaGenError> {
1357 let ty = typespec_to_java(&p.type_spec)?;
1358 let pname = sanitize_identifier(&p.name.text)?;
1359 Ok(format!("{ty} {pname}"))
1360 })
1361 .collect::<Result<_, _>>()?;
1362 let throws = if op.raises.is_empty() {
1363 String::new()
1364 } else {
1365 let raises: Vec<String> = op.raises.iter().map(scoped_to_java).collect();
1366 format!(" throws {}", raises.join(", "))
1367 };
1368 writeln!(
1369 body,
1370 "{ind}{ret} {}({}){throws};",
1371 sanitize_identifier(&op.name.text)?,
1372 params.join(", ")
1373 )
1374 .map_err(fmt_err)?;
1375 }
1376 Export::Attr(attr) => {
1377 let ty = typespec_to_java(&attr.type_spec)?;
1378 let aname = sanitize_identifier(&attr.name.text)?;
1379 writeln!(body, "{ind}{ty} get_{aname}();").map_err(fmt_err)?;
1380 if !attr.readonly {
1381 writeln!(body, "{ind}void set_{aname}({ty} value);").map_err(fmt_err)?;
1382 }
1383 }
1384 _ => {
1385 }
1387 }
1388 }
1389 writeln!(body, "}}").map_err(fmt_err)?;
1390
1391 let source = wrap_compilation_unit(pkg, &imports, &body);
1392 Ok(JavaFile {
1393 package_path: pkg.to_string(),
1394 class_name: class,
1395 source,
1396 })
1397}
1398
1399fn is_service_interface(iface: &InterfaceDef) -> bool {
1403 iface
1404 .annotations
1405 .iter()
1406 .any(|a| a.name.parts.last().is_some_and(|p| p.text == "service"))
1407}
1408
1409fn emit_service_interface_files(
1413 iface: &InterfaceDef,
1414 pkg: &str,
1415 opts: &JavaGenOptions,
1416 files: &mut Vec<JavaFile>,
1417) -> Result<(), JavaGenError> {
1418 use zerodds_idl::ast::Export;
1419 use zerodds_rpc::annotations::lower_rpc_annotations;
1420 use zerodds_rpc::service_mapping::lower_service;
1421
1422 for export in &iface.exports {
1425 if let Export::Except(e) = export {
1426 files.push(emit_exception_file(e, pkg, opts)?);
1427 }
1428 }
1429
1430 let lowered = lower_rpc_annotations(&iface.annotations);
1432 let svc = lower_service(iface, &lowered).map_err(|e| JavaGenError::Internal(e.to_string()))?;
1433
1434 let svc_files = crate::rpc::emit_service_files(&svc, pkg, opts)?;
1436 files.extend(svc_files);
1437
1438 Ok(())
1439}