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) {
191 files.push(emit_struct_companion_interface(s, pkg, opts)?);
192 }
193 Ok(())
194 }
195 ConstrTypeDecl::Struct(StructDcl::Forward(_)) => {
196 Ok(())
199 }
200 ConstrTypeDecl::Union(UnionDcl::Def(u)) => {
201 files.extend(emit_union_files(u, pkg, opts)?);
202 Ok(())
203 }
204 ConstrTypeDecl::Union(UnionDcl::Forward(_)) => Ok(()),
205 ConstrTypeDecl::Enum(e) => {
206 files.push(emit_enum_file(e, pkg, opts)?);
207 Ok(())
208 }
209 ConstrTypeDecl::Bitset(b) => {
210 files.push(emit_bitset_file(b, pkg, opts)?);
211 Ok(())
212 }
213 ConstrTypeDecl::Bitmask(b) => {
214 files.push(emit_bitmask_file(b, pkg, opts)?);
215 Ok(())
216 }
217 },
218 TypeDecl::Typedef(t) => {
219 files.extend(emit_typedef_files(t, pkg, opts)?);
220 Ok(())
221 }
222 }
223}
224
225fn emit_struct_file(
230 s: &StructDef,
231 pkg: &str,
232 opts: &JavaGenOptions,
233 ctx: &EmitCtx,
234) -> Result<JavaFile, JavaGenError> {
235 let class = sanitize_identifier(&s.name.text)?;
236 let mut imports = ImportSet::default();
237 let ind = indent_unit(opts);
238
239 for m in &s.members {
241 collect_member_imports(m, &mut imports);
242 }
243
244 let mut body = String::new();
245
246 emit_verbatim_at(&mut body, "", &s.annotations, PlacementKind::BeginFile)?;
249
250 emit_verbatim_at(
252 &mut body,
253 "",
254 &s.annotations,
255 PlacementKind::BeforeDeclaration,
256 )?;
257
258 for line in type_annotation_lines(&s.annotations) {
260 writeln!(body, "{line}").map_err(fmt_err)?;
261 }
262
263 let extends = if let Some(base) = &s.base {
264 let base_str = scoped_to_java(base);
265 format!(" extends {base_str}")
266 } else {
267 String::new()
268 };
269
270 let mut implements: Vec<String> = transitive_ancestors_beyond_base(&s.name.text, ctx)
275 .into_iter()
276 .map(|anc| format!("{anc}Interface"))
277 .collect();
278
279 let lowered_type = lower_or_empty(&s.annotations);
293 if !has_nested(&lowered_type) && s.base.is_none() {
294 implements.push(format!("org.omg.dds.topic.TopicType<{class}>"));
295 }
296 let implements_clause = if implements.is_empty() {
297 String::new()
298 } else {
299 format!(" implements {}", implements.join(", "))
300 };
301
302 writeln!(body, "public class {class}{extends}{implements_clause} {{").map_err(fmt_err)?;
303
304 emit_verbatim_at(
307 &mut body,
308 &ind,
309 &s.annotations,
310 PlacementKind::BeginDeclaration,
311 )?;
312
313 for m in &s.members {
315 emit_member_field(&mut body, m, &ind)?;
316 }
317 writeln!(body).map_err(fmt_err)?;
318
319 writeln!(body, "{ind}public {class}() {{}}").map_err(fmt_err)?;
321 writeln!(body).map_err(fmt_err)?;
322
323 for m in &s.members {
325 emit_member_accessors(&mut body, m, &ind)?;
326 }
327
328 emit_verbatim_at(
331 &mut body,
332 &ind,
333 &s.annotations,
334 PlacementKind::EndDeclaration,
335 )?;
336
337 writeln!(body, "}}").map_err(fmt_err)?;
338
339 emit_verbatim_at(
341 &mut body,
342 "",
343 &s.annotations,
344 PlacementKind::AfterDeclaration,
345 )?;
346 emit_verbatim_at(&mut body, "", &s.annotations, PlacementKind::EndFile)?;
347
348 let source = wrap_compilation_unit(pkg, &imports, &body);
349 Ok(JavaFile {
350 package_path: pkg.to_string(),
351 class_name: class,
352 source,
353 })
354}
355
356fn emit_enum_file(e: &EnumDef, pkg: &str, opts: &JavaGenOptions) -> Result<JavaFile, JavaGenError> {
357 let class = sanitize_identifier(&e.name.text)?;
358 let ind = indent_unit(opts);
359 let mut body = String::new();
360
361 emit_verbatim_at(&mut body, "", &e.annotations, PlacementKind::BeginFile)?;
362 emit_verbatim_at(
363 &mut body,
364 "",
365 &e.annotations,
366 PlacementKind::BeforeDeclaration,
367 )?;
368
369 for line in type_annotation_lines(&e.annotations) {
371 writeln!(body, "{line}").map_err(fmt_err)?;
372 }
373
374 writeln!(body, "public enum {class} {{").map_err(fmt_err)?;
375 emit_verbatim_at(
376 &mut body,
377 &ind,
378 &e.annotations,
379 PlacementKind::BeginDeclaration,
380 )?;
381
382 let count = e.enumerators.len();
383 let mut next_implicit: i64 = 0;
384 for (idx, en) in e.enumerators.iter().enumerate() {
385 let name = sanitize_identifier(&en.name.text)?;
386 let sep = if idx + 1 == count { ';' } else { ',' };
387 let value_lit = match enum_value_override(&en.annotations) {
390 Some(raw) => match raw.parse::<i64>() {
391 Ok(n) => {
392 next_implicit = n + 1;
393 n.to_string()
394 }
395 Err(_) => raw,
396 },
397 None => {
398 let n = next_implicit;
399 next_implicit += 1;
400 n.to_string()
401 }
402 };
403 writeln!(body, "{ind}{name}({value_lit}){sep}").map_err(fmt_err)?;
404 }
405 writeln!(body).map_err(fmt_err)?;
406 writeln!(body, "{ind}private final int value;").map_err(fmt_err)?;
407 writeln!(body, "{ind}{class}(int value) {{ this.value = value; }}").map_err(fmt_err)?;
408 writeln!(body, "{ind}public int value() {{ return value; }}").map_err(fmt_err)?;
409 emit_verbatim_at(
410 &mut body,
411 &ind,
412 &e.annotations,
413 PlacementKind::EndDeclaration,
414 )?;
415 writeln!(body, "}}").map_err(fmt_err)?;
416 emit_verbatim_at(
417 &mut body,
418 "",
419 &e.annotations,
420 PlacementKind::AfterDeclaration,
421 )?;
422 emit_verbatim_at(&mut body, "", &e.annotations, PlacementKind::EndFile)?;
423
424 let source = wrap_compilation_unit(pkg, &ImportSet::default(), &body);
425 Ok(JavaFile {
426 package_path: pkg.to_string(),
427 class_name: class,
428 source,
429 })
430}
431
432fn emit_union_files(
437 u: &UnionDef,
438 pkg: &str,
439 opts: &JavaGenOptions,
440) -> Result<Vec<JavaFile>, JavaGenError> {
441 let class = sanitize_identifier(&u.name.text)?;
442 let ind = indent_unit(opts);
443 let imports = ImportSet::default();
444
445 let _disc_ty = switch_type_to_java(&u.switch_type)?;
446
447 let mut permits: Vec<String> = Vec::new();
449 let mut case_records: Vec<(String, String, String)> = Vec::new(); for c in &u.cases {
451 let cpp_ty = type_for_declarator(&c.element.type_spec, &c.element.declarator)?;
452 let field_name = sanitize_identifier(&c.element.declarator.name().text)?;
453 let record_name = capitalize(&field_name);
455 if !permits.iter().any(|p| p == &record_name) {
456 permits.push(record_name.clone());
457 case_records.push((record_name, cpp_ty, field_name));
458 }
459 }
460 let permits_clause = if permits.is_empty() {
467 String::new()
468 } else {
469 let qualified: Vec<String> = permits.iter().map(|p| format!("{class}.{p}")).collect();
470 format!(" permits {}", qualified.join(", "))
471 };
472
473 let mut body = String::new();
474 emit_verbatim_at(&mut body, "", &u.annotations, PlacementKind::BeginFile)?;
475 emit_verbatim_at(
476 &mut body,
477 "",
478 &u.annotations,
479 PlacementKind::BeforeDeclaration,
480 )?;
481 writeln!(body, "public sealed interface {class}{permits_clause} {{").map_err(fmt_err)?;
482 emit_verbatim_at(
483 &mut body,
484 &ind,
485 &u.annotations,
486 PlacementKind::BeginDeclaration,
487 )?;
488
489 let mut has_default = false;
492 for c in &u.cases {
493 for label in &c.labels {
494 match label {
495 CaseLabel::Default => {
496 has_default = true;
497 writeln!(
498 body,
499 "{ind}// case default -> {}",
500 c.element.declarator.name().text
501 )
502 .map_err(fmt_err)?;
503 }
504 CaseLabel::Value(expr) => {
505 let val = const_expr_to_java(expr);
506 writeln!(
507 body,
508 "{ind}// case {val} -> {}",
509 c.element.declarator.name().text
510 )
511 .map_err(fmt_err)?;
512 }
513 }
514 }
515 }
516 if !has_default {
517 writeln!(body, "{ind}// no explicit 'default:' branch").map_err(fmt_err)?;
518 }
519 writeln!(body).map_err(fmt_err)?;
520
521 for (record_name, field_ty, field_name) in &case_records {
523 writeln!(
524 body,
525 "{ind}record {record_name}({field_ty} {field_name}) implements {class} {{}}",
526 )
527 .map_err(fmt_err)?;
528 }
529 emit_verbatim_at(
530 &mut body,
531 &ind,
532 &u.annotations,
533 PlacementKind::EndDeclaration,
534 )?;
535 writeln!(body, "}}").map_err(fmt_err)?;
536 emit_verbatim_at(
537 &mut body,
538 "",
539 &u.annotations,
540 PlacementKind::AfterDeclaration,
541 )?;
542 emit_verbatim_at(&mut body, "", &u.annotations, PlacementKind::EndFile)?;
543
544 let source = wrap_compilation_unit(pkg, &imports, &body);
545 Ok(vec![JavaFile {
546 package_path: pkg.to_string(),
547 class_name: class,
548 source,
549 }])
550}
551
552fn emit_typedef_files(
553 t: &TypedefDecl,
554 pkg: &str,
555 _opts: &JavaGenOptions,
556) -> Result<Vec<JavaFile>, JavaGenError> {
557 let mut out = Vec::new();
560 for decl in &t.declarators {
561 let alias = sanitize_identifier(&decl.name().text)?;
562 let target = type_for_declarator(&t.type_spec, decl)?;
563 let imports = ImportSet::default();
564
565 let mut body = String::new();
566 writeln!(body, "public final class {alias} {{").map_err(fmt_err)?;
567 writeln!(body, " private {target} value;").map_err(fmt_err)?;
568 writeln!(body).map_err(fmt_err)?;
569 writeln!(body, " public {alias}() {{}}").map_err(fmt_err)?;
570 writeln!(
571 body,
572 " public {alias}({target} value) {{ this.value = value; }}",
573 )
574 .map_err(fmt_err)?;
575 writeln!(body).map_err(fmt_err)?;
576 writeln!(body, " public {target} value() {{ return value; }}").map_err(fmt_err)?;
577 writeln!(
578 body,
579 " public void value({target} value) {{ this.value = value; }}",
580 )
581 .map_err(fmt_err)?;
582 writeln!(body, "}}").map_err(fmt_err)?;
583
584 let source = wrap_compilation_unit(pkg, &imports, &body);
585 out.push(JavaFile {
586 package_path: pkg.to_string(),
587 class_name: alias,
588 source,
589 });
590 }
591 Ok(out)
592}
593
594fn emit_exception_file(
595 e: &ExceptDecl,
596 pkg: &str,
597 opts: &JavaGenOptions,
598) -> Result<JavaFile, JavaGenError> {
599 let class = sanitize_identifier(&e.name.text)?;
600 let ind = indent_unit(opts);
601 let mut imports = ImportSet::default();
602 for m in &e.members {
603 collect_member_imports(m, &mut imports);
604 }
605
606 let mut body = String::new();
607 writeln!(body, "public class {class} extends RuntimeException {{").map_err(fmt_err)?;
608 for m in &e.members {
609 emit_member_field(&mut body, m, &ind)?;
610 }
611 writeln!(body).map_err(fmt_err)?;
612 writeln!(body, "{ind}public {class}() {{ super(); }}").map_err(fmt_err)?;
613 writeln!(
614 body,
615 "{ind}public {class}(String message) {{ super(message); }}",
616 )
617 .map_err(fmt_err)?;
618 writeln!(body).map_err(fmt_err)?;
619 for m in &e.members {
620 emit_member_accessors(&mut body, m, &ind)?;
621 }
622 writeln!(body, "}}").map_err(fmt_err)?;
623
624 let source = wrap_compilation_unit(pkg, &imports, &body);
625 Ok(JavaFile {
626 package_path: pkg.to_string(),
627 class_name: class,
628 source,
629 })
630}
631
632fn emit_const_holder(
633 c: &zerodds_idl::ast::ConstDecl,
634 pkg: &str,
635 _opts: &JavaGenOptions,
636) -> Result<JavaFile, JavaGenError> {
637 let name = sanitize_identifier(&c.name.text)?;
640 let class = format!("{name}Constant");
641 let java_ty = const_type_to_java(&c.type_)?;
642 let val = const_expr_to_java(&c.value);
643 let mut body = String::new();
644 writeln!(body, "public final class {class} {{").map_err(fmt_err)?;
645 writeln!(body, " public static final {java_ty} {name} = {val};").map_err(fmt_err)?;
646 writeln!(body, " private {class}() {{}}").map_err(fmt_err)?;
647 writeln!(body, "}}").map_err(fmt_err)?;
648 let source = wrap_compilation_unit(pkg, &ImportSet::default(), &body);
649 Ok(JavaFile {
650 package_path: pkg.to_string(),
651 class_name: class,
652 source,
653 })
654}
655
656fn emit_member_field(out: &mut String, m: &Member, ind: &str) -> Result<(), JavaGenError> {
661 let optional = has_optional_annotation(&m.annotations);
662 let ann_lines = member_annotation_lines(&m.annotations);
663 for decl in &m.declarators {
664 let java_ty = type_for_declarator(&m.type_spec, decl)?;
665 let name = sanitize_identifier(&decl.name().text)?;
666 let final_ty = if optional {
667 format!("java.util.Optional<{}>", boxed_for_optional(&m.type_spec))
668 } else {
669 java_ty
670 };
671 for ann in &ann_lines {
672 writeln!(out, "{ind}{ann}").map_err(fmt_err)?;
673 }
674 if let TypeSpec::Primitive(zerodds_idl::ast::PrimitiveType::Integer(i)) = &m.type_spec {
676 if is_unsigned(*i) {
677 writeln!(
678 out,
679 "{ind}/** unsigned IDL value (Java unsigned-workaround) */"
680 )
681 .map_err(fmt_err)?;
682 }
683 }
684 writeln!(out, "{ind}private {final_ty} {name};").map_err(fmt_err)?;
685 }
686 Ok(())
687}
688
689fn emit_member_accessors(out: &mut String, m: &Member, ind: &str) -> Result<(), JavaGenError> {
690 let optional = has_optional_annotation(&m.annotations);
691 for decl in &m.declarators {
692 let java_ty = type_for_declarator(&m.type_spec, decl)?;
693 let name = sanitize_identifier(&decl.name().text)?;
694 let cap = capitalize(&name);
695 let final_ty = if optional {
696 format!("java.util.Optional<{}>", boxed_for_optional(&m.type_spec))
697 } else {
698 java_ty.clone()
699 };
700 writeln!(
701 out,
702 "{ind}public {final_ty} get{cap}() {{ return {name}; }}"
703 )
704 .map_err(fmt_err)?;
705 writeln!(
706 out,
707 "{ind}public void set{cap}({final_ty} {name}) {{ this.{name} = {name}; }}",
708 )
709 .map_err(fmt_err)?;
710 }
711 Ok(())
712}
713
714fn boxed_for_optional(ts: &TypeSpec) -> String {
715 match ts {
716 TypeSpec::Primitive(p) => primitive_to_java_boxed(*p).to_string(),
717 TypeSpec::Scoped(s) => scoped_to_java(s),
718 TypeSpec::String(_) => "String".into(),
719 TypeSpec::Sequence(s) => {
720 let inner = match &*s.elem {
722 TypeSpec::Primitive(p) => primitive_to_java_boxed(*p).to_string(),
723 TypeSpec::Scoped(sn) => scoped_to_java(sn),
724 TypeSpec::String(_) => "String".into(),
725 _ => "Object".into(),
726 };
727 format!("java.util.List<{inner}>")
728 }
729 _ => "Object".into(),
730 }
731}
732
733pub(crate) fn type_for_declarator(
739 ts: &TypeSpec,
740 decl: &Declarator,
741) -> Result<String, JavaGenError> {
742 let base = typespec_to_java(ts)?;
743 match decl {
744 Declarator::Simple(_) => Ok(base),
745 Declarator::Array(arr) => {
746 let mut suffix = String::new();
747 for _ in &arr.sizes {
748 suffix.push_str("[]");
749 }
750 Ok(format!("{base}{suffix}"))
751 }
752 }
753}
754
755pub(crate) fn typespec_to_java(ts: &TypeSpec) -> Result<String, JavaGenError> {
757 match ts {
758 TypeSpec::Primitive(p) => Ok(primitive_to_java(*p).to_string()),
759 TypeSpec::Scoped(s) => Ok(scoped_to_java(s)),
760 TypeSpec::Sequence(s) => {
761 let inner = match &*s.elem {
762 TypeSpec::Primitive(p) => primitive_to_java_boxed(*p).to_string(),
763 TypeSpec::Scoped(sn) => scoped_to_java(sn),
764 TypeSpec::String(_) => "String".into(),
765 other => typespec_to_java(other)?,
766 };
767 Ok(format!("java.util.List<{inner}>"))
768 }
769 TypeSpec::String(_) => Ok("String".into()),
770 TypeSpec::Map(m) => {
771 let k = match &*m.key {
772 TypeSpec::Primitive(p) => primitive_to_java_boxed(*p).to_string(),
773 TypeSpec::Scoped(sn) => scoped_to_java(sn),
774 TypeSpec::String(_) => "String".into(),
775 other => typespec_to_java(other)?,
776 };
777 let v = match &*m.value {
778 TypeSpec::Primitive(p) => primitive_to_java_boxed(*p).to_string(),
779 TypeSpec::Scoped(sn) => scoped_to_java(sn),
780 TypeSpec::String(_) => "String".into(),
781 other => typespec_to_java(other)?,
782 };
783 Ok(format!("java.util.Map<{k}, {v}>"))
784 }
785 TypeSpec::Fixed(_) => {
786 Ok("java.math.BigDecimal".into())
791 }
792 TypeSpec::Any => {
793 Ok("Object".into())
799 }
800 }
801}
802
803pub(crate) fn switch_type_to_java(s: &SwitchTypeSpec) -> Result<String, JavaGenError> {
804 Ok(match s {
805 SwitchTypeSpec::Integer(i) => integer_to_java(*i).to_string(),
806 SwitchTypeSpec::Char => "char".into(),
807 SwitchTypeSpec::Boolean => "boolean".into(),
808 SwitchTypeSpec::Octet => "byte".into(),
809 SwitchTypeSpec::Scoped(s) => scoped_to_java(s),
810 })
811}
812
813fn const_type_to_java(t: &zerodds_idl::ast::ConstType) -> Result<String, JavaGenError> {
814 Ok(match t {
815 zerodds_idl::ast::ConstType::Integer(i) => integer_to_java(*i).to_string(),
816 zerodds_idl::ast::ConstType::Floating(f) => floating_to_java(*f).to_string(),
817 zerodds_idl::ast::ConstType::Boolean => "boolean".into(),
818 zerodds_idl::ast::ConstType::Char => "char".into(),
819 zerodds_idl::ast::ConstType::WideChar => "char".into(),
820 zerodds_idl::ast::ConstType::Octet => "byte".into(),
821 zerodds_idl::ast::ConstType::String { .. } => "String".into(),
822 zerodds_idl::ast::ConstType::Scoped(s) => scoped_to_java(s),
823 zerodds_idl::ast::ConstType::Fixed => "java.math.BigDecimal".into(),
824 })
825}
826
827fn scoped_to_java(s: &ScopedName) -> String {
828 let parts: Vec<String> = s.parts.iter().map(|p| p.text.clone()).collect();
829 parts.join(".")
830}
831
832#[derive(Debug, Default, Clone)]
837pub(crate) struct ImportSet {
838 #[allow(dead_code)]
839 imports: BTreeSet<&'static str>,
840}
841
842impl ImportSet {
843 #[allow(dead_code)]
844 fn add(&mut self, fqn: &'static str) {
845 self.imports.insert(fqn);
846 }
847}
848
849#[allow(clippy::needless_pass_by_ref_mut)]
852fn collect_member_imports(_m: &Member, _inc: &mut ImportSet) {
853 }
856
857pub(crate) fn wrap_compilation_unit(pkg: &str, _imports: &ImportSet, body: &str) -> String {
862 let mut out = String::new();
863 let _ = writeln!(out, "// Generated by zerodds idl-java. Do not edit.");
864 if !pkg.is_empty() {
865 let _ = writeln!(out, "package {pkg};");
866 let _ = writeln!(out);
867 }
868 out.push_str(body);
873 out
874}
875
876pub(crate) fn const_expr_to_java(e: &ConstExpr) -> String {
882 match e {
883 ConstExpr::Literal(l) => literal_to_java(l),
884 ConstExpr::Scoped(s) => scoped_to_java(s),
885 ConstExpr::Unary { op, operand, .. } => {
886 let prefix = match op {
887 zerodds_idl::ast::UnaryOp::Plus => "+",
888 zerodds_idl::ast::UnaryOp::Minus => "-",
889 zerodds_idl::ast::UnaryOp::BitNot => "~",
890 };
891 format!("{prefix}{}", const_expr_to_java(operand))
892 }
893 ConstExpr::Binary { op, lhs, rhs, .. } => {
894 let opstr = match op {
895 zerodds_idl::ast::BinaryOp::Or => "|",
896 zerodds_idl::ast::BinaryOp::Xor => "^",
897 zerodds_idl::ast::BinaryOp::And => "&",
898 zerodds_idl::ast::BinaryOp::Shl => "<<",
899 zerodds_idl::ast::BinaryOp::Shr => ">>",
900 zerodds_idl::ast::BinaryOp::Add => "+",
901 zerodds_idl::ast::BinaryOp::Sub => "-",
902 zerodds_idl::ast::BinaryOp::Mul => "*",
903 zerodds_idl::ast::BinaryOp::Div => "/",
904 zerodds_idl::ast::BinaryOp::Mod => "%",
905 };
906 format!(
907 "({} {opstr} {})",
908 const_expr_to_java(lhs),
909 const_expr_to_java(rhs)
910 )
911 }
912 }
913}
914
915fn literal_to_java(l: &Literal) -> String {
916 match l.kind {
917 LiteralKind::Boolean
918 | LiteralKind::Integer
919 | LiteralKind::Floating
920 | LiteralKind::Char
921 | LiteralKind::WideChar
922 | LiteralKind::String
923 | LiteralKind::WideString
924 | LiteralKind::Fixed => l.raw.clone(),
925 }
926}
927
928fn has_optional_annotation(anns: &[Annotation]) -> bool {
933 has_named_annotation(anns, "optional")
934}
935
936fn has_named_annotation(anns: &[Annotation], name: &str) -> bool {
937 anns.iter().any(|a| {
938 a.name.parts.last().is_some_and(|p| p.text == name)
939 && matches!(a.params, AnnotationParams::None | AnnotationParams::Empty)
940 })
941}
942
943fn collect_inheritance_edges(
949 defs: &[Definition],
950 parents: &mut HashMap<String, String>,
951 prefix: &str,
952) {
953 for d in defs {
954 match d {
955 Definition::Module(m) => {
956 let new_prefix = if prefix.is_empty() {
957 m.name.text.clone()
958 } else {
959 format!("{prefix}.{}", m.name.text)
960 };
961 collect_inheritance_edges(&m.definitions, parents, &new_prefix);
962 }
963 Definition::Type(TypeDecl::Constr(ConstrTypeDecl::Struct(StructDcl::Def(s)))) => {
964 let key = if prefix.is_empty() {
965 s.name.text.clone()
966 } else {
967 format!("{prefix}.{}", s.name.text)
968 };
969 if let Some(b) = &s.base {
970 let base_str = b
971 .parts
972 .iter()
973 .map(|p| p.text.clone())
974 .collect::<Vec<_>>()
975 .join(".");
976 parents.insert(key, base_str);
977 }
978 }
979 _ => {}
980 }
981 }
982}
983
984fn detect_inheritance_cycles(spec: &Specification) -> Result<(), JavaGenError> {
985 let mut parents: HashMap<String, String> = HashMap::new();
986 collect_inheritance_edges(&spec.definitions, &mut parents, "");
987
988 for start in parents.keys() {
989 let mut current = start.clone();
990 let mut visited: BTreeSet<String> = BTreeSet::new();
991 visited.insert(current.clone());
992 while let Some(p) = parents.get(¤t) {
993 let resolved = parents
994 .keys()
995 .find(|k| *k == p || k.ends_with(&format!(".{p}")))
996 .cloned()
997 .unwrap_or_else(|| p.clone());
998 if visited.contains(&resolved) {
999 return Err(JavaGenError::InheritanceCycle {
1000 type_name: short_name(&resolved),
1001 });
1002 }
1003 visited.insert(resolved.clone());
1004 if resolved == current {
1005 return Err(JavaGenError::InheritanceCycle {
1006 type_name: short_name(&resolved),
1007 });
1008 }
1009 current = resolved;
1010 if !parents.contains_key(¤t) {
1011 break;
1012 }
1013 }
1014 }
1015 Ok(())
1016}
1017
1018fn short_name(s: &str) -> String {
1019 s.rsplit('.').next().unwrap_or(s).to_string()
1020}
1021
1022pub(crate) fn indent_unit(opts: &JavaGenOptions) -> String {
1027 " ".repeat(opts.indent_width)
1028}
1029
1030pub(crate) fn capitalize(s: &str) -> String {
1031 let mut chars = s.chars();
1032 match chars.next() {
1033 Some(c) => c.to_uppercase().collect::<String>() + chars.as_str(),
1034 None => String::new(),
1035 }
1036}
1037
1038pub(crate) fn fmt_err(_: core::fmt::Error) -> JavaGenError {
1039 JavaGenError::Internal("string formatting failed".into())
1040}
1041
1042pub(crate) fn wrap_compilation_unit_default(pkg: &str, body: &str) -> String {
1046 wrap_compilation_unit(pkg, &ImportSet::default(), body)
1047}
1048
1049fn collect_base_chain_index(spec: &Specification) -> std::collections::HashMap<String, String> {
1058 let mut out: std::collections::HashMap<String, String> = std::collections::HashMap::new();
1059 fn visit(defs: &[Definition], out: &mut std::collections::HashMap<String, String>) {
1064 for d in defs {
1065 match d {
1066 Definition::Module(m) => visit(&m.definitions, out),
1067 Definition::Type(TypeDecl::Constr(ConstrTypeDecl::Struct(StructDcl::Def(s)))) => {
1068 if let Some(b) = &s.base {
1069 out.insert(s.name.text.clone(), scoped_to_short(b));
1070 }
1071 }
1072 _ => {}
1073 }
1074 }
1075 }
1076 visit(&spec.definitions, &mut out);
1077 out
1078}
1079
1080fn transitive_ancestors_beyond_base(name: &str, ctx: &EmitCtx) -> Vec<String> {
1084 let mut out: Vec<String> = Vec::new();
1085 let direct = match ctx.parent_of.get(name) {
1086 Some(p) => p.clone(),
1087 None => return out,
1088 };
1089 let mut current = direct;
1090 let mut guard = 0usize;
1091 while let Some(p) = ctx.parent_of.get(¤t) {
1092 if guard > 64 {
1093 break;
1094 }
1095 guard += 1;
1096 out.push(p.clone());
1097 current = p.clone();
1098 }
1099 out
1100}
1101
1102fn scoped_to_short(s: &ScopedName) -> String {
1103 s.parts.last().map(|p| p.text.clone()).unwrap_or_default()
1104}
1105
1106fn emit_struct_companion_interface(
1112 s: &StructDef,
1113 pkg: &str,
1114 opts: &JavaGenOptions,
1115) -> Result<JavaFile, JavaGenError> {
1116 let class = sanitize_identifier(&s.name.text)?;
1117 let interface_name = format!("{class}Interface");
1118 let ind = indent_unit(opts);
1119 let mut body = String::new();
1120 writeln!(
1121 body,
1122 "/** Companion interface for {class}; lets sub-sub-classes \
1123 participate in the {class} contract via `implements`. */",
1124 )
1125 .map_err(fmt_err)?;
1126 writeln!(body, "public interface {interface_name} {{").map_err(fmt_err)?;
1127 for m in &s.members {
1135 let opt = has_optional_annotation(&m.annotations);
1136 for decl in &m.declarators {
1137 let java_ty = type_for_declarator(&m.type_spec, decl)?;
1138 let name = sanitize_identifier(&decl.name().text)?;
1139 let cap = capitalize(&name);
1140 let final_ty = if opt {
1141 format!("java.util.Optional<{}>", boxed_for_optional(&m.type_spec))
1142 } else {
1143 java_ty
1144 };
1145 writeln!(body, "{ind}{final_ty} get{cap}();").map_err(fmt_err)?;
1146 }
1147 }
1148 writeln!(body, "}}").map_err(fmt_err)?;
1149 let source = wrap_compilation_unit_default(pkg, &body);
1150 Ok(JavaFile {
1151 package_path: pkg.to_string(),
1152 class_name: interface_name,
1153 source,
1154 })
1155}
1156
1157#[allow(dead_code)]
1161fn _unused_marker(_i: IntegerType) {
1162 let _ = integer_to_java_boxed;
1163 let _ = floating_to_java_boxed;
1164}
1165
1166fn emit_value_type_files(
1175 v: &zerodds_idl::ast::ValueDef,
1176 pkg: &str,
1177 opts: &JavaGenOptions,
1178) -> Result<Vec<JavaFile>, JavaGenError> {
1179 use zerodds_idl::ast::{Export, StateVisibility, ValueElement};
1180
1181 let class = sanitize_identifier(&v.name.text)?;
1182 let abstract_name = format!("{class}Abstract");
1183 let ind = indent_unit(opts);
1184 let imports = ImportSet::default();
1185
1186 let mut body = String::new();
1188 let extends = match &v.inheritance {
1189 Some(inh) if !inh.bases.is_empty() => {
1190 let base = scoped_to_java(&inh.bases[0]);
1192 format!(" extends {base}Abstract")
1193 }
1194 _ => String::new(),
1195 };
1196 let supports = match &v.inheritance {
1197 Some(inh) if !inh.supports.is_empty() => {
1198 let s: Vec<String> = inh.supports.iter().map(scoped_to_java).collect();
1199 format!(" implements {}", s.join(", "))
1200 }
1201 _ => String::new(),
1202 };
1203
1204 writeln!(
1205 body,
1206 "public abstract class {abstract_name}{extends}{supports} {{"
1207 )
1208 .map_err(fmt_err)?;
1209
1210 for el in &v.elements {
1211 match el {
1212 ValueElement::State(s) => {
1213 let ty = typespec_to_java(&s.type_spec)?;
1214 let visibility = match s.visibility {
1215 StateVisibility::Public => "public",
1216 StateVisibility::Private => "protected",
1217 };
1218 for d in &s.declarators {
1219 let n = sanitize_identifier(&d.name().text)?;
1220 writeln!(body, "{ind}{visibility} abstract {ty} get_{n}();")
1221 .map_err(fmt_err)?;
1222 writeln!(body, "{ind}{visibility} abstract void set_{n}({ty} value);")
1223 .map_err(fmt_err)?;
1224 }
1225 }
1226 ValueElement::Init(i) => {
1227 let params: Vec<String> = i
1228 .params
1229 .iter()
1230 .map(|p| -> Result<String, JavaGenError> {
1231 let ty = typespec_to_java(&p.type_spec)?;
1232 let pname = sanitize_identifier(&p.name.text)?;
1233 Ok(format!("{ty} {pname}"))
1234 })
1235 .collect::<Result<_, _>>()?;
1236 writeln!(
1237 body,
1238 "{ind}public abstract void {}({});",
1239 sanitize_identifier(&i.name.text)?,
1240 params.join(", ")
1241 )
1242 .map_err(fmt_err)?;
1243 }
1244 ValueElement::Export(Export::Op(op)) => {
1245 let ret = match &op.return_type {
1246 None => "void".to_string(),
1247 Some(t) => typespec_to_java(t)?,
1248 };
1249 let params: Vec<String> = op
1250 .params
1251 .iter()
1252 .map(|p| -> Result<String, JavaGenError> {
1253 let ty = typespec_to_java(&p.type_spec)?;
1254 let pname = sanitize_identifier(&p.name.text)?;
1255 Ok(format!("{ty} {pname}"))
1256 })
1257 .collect::<Result<_, _>>()?;
1258 writeln!(
1259 body,
1260 "{ind}public abstract {ret} {}({});",
1261 sanitize_identifier(&op.name.text)?,
1262 params.join(", ")
1263 )
1264 .map_err(fmt_err)?;
1265 }
1266 _ => {}
1267 }
1268 }
1269 writeln!(body, "}}").map_err(fmt_err)?;
1270
1271 let abstract_source = wrap_compilation_unit(pkg, &imports, &body);
1272 let abstract_file = JavaFile {
1273 package_path: pkg.to_string(),
1274 class_name: abstract_name.clone(),
1275 source: abstract_source,
1276 };
1277
1278 let concrete_body = format!(
1280 "public class {class} extends {abstract_name} {{\n{ind}// User-Implementation hier\n}}\n"
1281 );
1282 let concrete_source = wrap_compilation_unit(pkg, &imports, &concrete_body);
1283 let concrete_file = JavaFile {
1284 package_path: pkg.to_string(),
1285 class_name: class,
1286 source: concrete_source,
1287 };
1288
1289 Ok(vec![abstract_file, concrete_file])
1290}
1291
1292fn emit_non_service_interface_file(
1295 iface: &InterfaceDef,
1296 pkg: &str,
1297 opts: &JavaGenOptions,
1298) -> Result<JavaFile, JavaGenError> {
1299 use zerodds_idl::ast::Export;
1300
1301 let class = sanitize_identifier(&iface.name.text)?;
1302 let imports = ImportSet::default();
1303 let ind = indent_unit(opts);
1304 let mut body = String::new();
1305
1306 let extends = if iface.bases.is_empty() {
1307 String::new()
1308 } else {
1309 let bases: Vec<String> = iface.bases.iter().map(scoped_to_java).collect();
1310 format!(" extends {}", bases.join(", "))
1311 };
1312 writeln!(body, "public interface {class}{extends} {{").map_err(fmt_err)?;
1313
1314 for export in &iface.exports {
1315 match export {
1316 Export::Op(op) => {
1317 let ret = match &op.return_type {
1318 None => "void".to_string(),
1319 Some(t) => typespec_to_java(t)?,
1320 };
1321 let params: Vec<String> = op
1322 .params
1323 .iter()
1324 .map(|p| -> Result<String, JavaGenError> {
1325 let ty = typespec_to_java(&p.type_spec)?;
1326 let pname = sanitize_identifier(&p.name.text)?;
1327 Ok(format!("{ty} {pname}"))
1328 })
1329 .collect::<Result<_, _>>()?;
1330 let throws = if op.raises.is_empty() {
1331 String::new()
1332 } else {
1333 let raises: Vec<String> = op.raises.iter().map(scoped_to_java).collect();
1334 format!(" throws {}", raises.join(", "))
1335 };
1336 writeln!(
1337 body,
1338 "{ind}{ret} {}({}){throws};",
1339 sanitize_identifier(&op.name.text)?,
1340 params.join(", ")
1341 )
1342 .map_err(fmt_err)?;
1343 }
1344 Export::Attr(attr) => {
1345 let ty = typespec_to_java(&attr.type_spec)?;
1346 let aname = sanitize_identifier(&attr.name.text)?;
1347 writeln!(body, "{ind}{ty} get_{aname}();").map_err(fmt_err)?;
1348 if !attr.readonly {
1349 writeln!(body, "{ind}void set_{aname}({ty} value);").map_err(fmt_err)?;
1350 }
1351 }
1352 _ => {
1353 }
1355 }
1356 }
1357 writeln!(body, "}}").map_err(fmt_err)?;
1358
1359 let source = wrap_compilation_unit(pkg, &imports, &body);
1360 Ok(JavaFile {
1361 package_path: pkg.to_string(),
1362 class_name: class,
1363 source,
1364 })
1365}
1366
1367fn is_service_interface(iface: &InterfaceDef) -> bool {
1371 iface
1372 .annotations
1373 .iter()
1374 .any(|a| a.name.parts.last().is_some_and(|p| p.text == "service"))
1375}
1376
1377fn emit_service_interface_files(
1381 iface: &InterfaceDef,
1382 pkg: &str,
1383 opts: &JavaGenOptions,
1384 files: &mut Vec<JavaFile>,
1385) -> Result<(), JavaGenError> {
1386 use zerodds_idl::ast::Export;
1387 use zerodds_rpc::annotations::lower_rpc_annotations;
1388 use zerodds_rpc::service_mapping::lower_service;
1389
1390 for export in &iface.exports {
1393 if let Export::Except(e) = export {
1394 files.push(emit_exception_file(e, pkg, opts)?);
1395 }
1396 }
1397
1398 let lowered = lower_rpc_annotations(&iface.annotations);
1400 let svc = lower_service(iface, &lowered).map_err(|e| JavaGenError::Internal(e.to_string()))?;
1401
1402 let svc_files = crate::rpc::emit_service_files(&svc, pkg, opts)?;
1404 files.extend(svc_files);
1405
1406 Ok(())
1407}