1use crate::adapter::c::ordered_types;
202use crate::adapter::constant;
203use crate::adapter::cst::{MaterialiseError, no_spelling_collision};
204use crate::apply::Applied;
205use crate::verbatim;
206use seesaw_tgg::ident::GhostId;
207use zerodds_ast::{Ast, CharWidth, ModuleNode, PrimitiveKind, TypeKind, TypeNode};
208use zerodds_ast_core::{Id, NodeId};
209use zerodds_lang_cst::common::{Doc, Ident, Literal, Path};
210use zerodds_lang_cst::java;
211
212pub const OUTER: &str = "ZdTypes";
219
220const FLOAT128: &[&str] = &["org", "zerodds", "wire", "Float128"];
229
230const STRING: &[&str] = &["java", "lang", "String"];
232
233const BIG_DECIMAL: &[&str] = &["java", "math", "BigDecimal"];
235
236const COLLECTION: &[&str] = &["java", "util", "Collection"];
238
239pub const ARRAY_LIST: &[&str] = &["java", "util", "ArrayList"];
241
242const ENTRY_PREFIX: &str = "ZdEntry_";
244pub const MAP_PREFIX: &str = "ZdMap";
246
247const RESERVED_TOP_LEVEL: &[&str] = &[OUTER, "java", "org"];
254
255#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
279pub enum JavaMapping {
280 Omg,
282 #[default]
284 Psm,
285}
286
287impl JavaMapping {
288 #[must_use]
290 pub const fn as_str(self) -> &'static str {
291 match self {
292 Self::Omg => "omg",
293 Self::Psm => "psm",
294 }
295 }
296
297 #[must_use]
299 pub fn from_name(name: &str) -> Option<Self> {
300 match name {
301 "omg" => Some(Self::Omg),
302 "psm" => Some(Self::Psm),
303 _ => None,
304 }
305 }
306}
307
308std::thread_local! {
309 static MAPPING: std::cell::Cell<JavaMapping> =
310 const { std::cell::Cell::new(JavaMapping::Psm) };
311}
312
313#[must_use]
324pub fn mapping() -> JavaMapping {
325 MAPPING.with(std::cell::Cell::get)
326}
327
328pub fn with_mapping<R>(mapping: JavaMapping, body: impl FnOnce() -> R) -> R {
331 struct Restore(JavaMapping);
332 impl Drop for Restore {
333 fn drop(&mut self) {
334 MAPPING.with(|cell| cell.set(self.0));
335 }
336 }
337 let _restore = Restore(MAPPING.with(|cell| cell.replace(mapping)));
338 body()
339}
340
341#[must_use]
348pub fn getter_name(field: &str) -> String {
349 format!("get{}", property(field))
350}
351
352#[must_use]
354pub fn setter_name(field: &str) -> String {
355 format!("set{}", property(field))
356}
357
358#[must_use]
360pub fn boolean_getter_name(field: &str) -> String {
361 format!("is{}", property(field))
362}
363
364fn property(field: &str) -> String {
365 if field == "_d" {
366 return "Discriminator".to_string();
367 }
368 let mut chars = field.chars();
369 let mut out = String::new();
370 if let Some(first) = chars.next() {
371 out.extend(first.to_uppercase());
372 }
373 out.push_str(chars.as_str());
374 if out == "Class" {
375 out.push('_');
376 }
377 out
378}
379
380pub const SEQUENCES: &str = "Sequences";
385
386fn is_sequences(receiver: &java::Expr) -> bool {
387 matches!(receiver, java::Expr::Path(path)
388 if matches!(path.segments(), [only] if only.as_str() == SEQUENCES))
389}
390
391fn sequences_call(name: &str, args: Vec<java::Expr>) -> java::Expr {
392 java::Expr::MethodCall {
393 receiver: Some(Box::new(java::Expr::Path(Path::new(&[SEQUENCES])))),
394 name: Ident::new(name),
395 args,
396 }
397}
398
399#[must_use]
402pub fn sequence_view(value: java::Expr) -> java::Expr {
403 sequences_call("view", vec![value])
404}
405
406#[must_use]
409pub fn index(value: java::Expr, at: java::Expr) -> java::Expr {
410 if let java::Expr::MethodCall {
411 receiver: Some(receiver),
412 name,
413 args,
414 } = &value
415 && name.as_str() == "view"
416 && is_sequences(receiver)
417 && let [inner] = args.as_slice()
418 {
419 return sequences_call("at", vec![inner.clone(), at]);
420 }
421 java::Expr::Index {
422 value: Box::new(value),
423 index: Box::new(at),
424 }
425}
426
427#[must_use]
429pub fn sequences_count(value: java::Expr) -> java::Expr {
430 sequences_call("count", vec![value])
431}
432
433#[must_use]
436pub fn sequences_first(value: java::Expr, count: java::Expr) -> java::Expr {
437 sequences_call("first", vec![value, count])
438}
439
440#[must_use]
443pub fn sequences_alloc(value: java::Expr, count: java::Expr) -> java::Expr {
444 sequences_call("alloc", vec![value, count])
445}
446
447#[must_use]
450pub fn sequence_slot(sequence: java::Expr, at: java::Expr) -> java::Expr {
451 sequences_call("slot", vec![sequence, at])
452}
453
454#[must_use]
457pub fn accessor(base: java::Expr, field: &str) -> java::Expr {
458 java::Expr::MethodCall {
459 receiver: Some(Box::new(base)),
460 name: Ident::new(&getter_name(field)),
461 args: Vec::new(),
462 }
463}
464
465#[must_use]
473pub fn assign(place: java::Expr, value: java::Expr) -> java::Statement {
474 if mapping() == JavaMapping::Psm {
475 if let java::Expr::MethodCall {
476 receiver: Some(receiver),
477 name,
478 args,
479 } = &place
480 {
481 if args.is_empty() && name.as_str().starts_with("get") {
482 return java::Statement::Expr(java::Expr::MethodCall {
483 receiver: Some(receiver.clone()),
484 name: Ident::new(&format!("set{}", &name.as_str()["get".len()..])),
485 args: vec![value],
486 });
487 }
488 if name.as_str() == "slot"
489 && is_sequences(receiver)
490 && let [sequence, at] = args.as_slice()
491 {
492 return java::Statement::Expr(sequences_call(
493 "put",
494 vec![sequence.clone(), at.clone(), value],
495 ));
496 }
497 }
498 }
499 java::Statement::Expr(java::Expr::Binary {
500 op: "=",
501 lhs: Box::new(place),
502 rhs: Box::new(value),
503 })
504}
505
506#[must_use]
510pub fn assignment(statement: java::Statement) -> java::Statement {
511 match statement {
512 java::Statement::Expr(java::Expr::Binary { op: "=", lhs, rhs }) => assign(*lhs, *rhs),
513 other => other,
514 }
515}
516
517pub fn java_items(ast: &Ast, applied: &Applied) -> Result<Vec<java::TypeDecl>, MaterialiseError> {
527 let mut structural: Vec<java::Member> = Vec::new();
532 let mut emitted: Vec<String> = Vec::new();
533 let mut items: Vec<Pending> = Vec::new();
534
535 for id in ordered_types(ast) {
540 let start = items.len();
541 declare(ast, applied, id, &mut items, &mut structural, &mut emitted)?;
542 annotate_declared(ast, id, start, &mut items)?;
543 weave_verbatim(ast, id, start, &mut items);
544 }
545
546 for (module, member) in annotation_types(ast)? {
549 items.push(Pending { module, member });
550 }
551
552 for (id, node) in ast.constants() {
556 if ghost_created_for(applied, node.node_id, "JavaConst").is_none() {
557 continue;
558 }
559 let value = ast
564 .eval_const(node.expr)
565 .map_err(|_| MaterialiseError::Unevaluated {
566 construct: "constant's value".to_string(),
567 at: node.name.clone(),
568 })?;
569 let type_ = match node.type_.resolved() {
570 Some(target) => java_member_type(ast, target, &node.name)?,
571 None => java::Type::Primitive(java::Primitive::Int),
572 };
573 let value = match value {
574 zerodds_ast::ConstValue::Integer(number)
580 if (128..=255).contains(&number)
581 && node
582 .type_
583 .resolved()
584 .is_some_and(|target| is_octet(ast, target, 0)) =>
585 {
586 java::Expr::Cast {
587 type_: Box::new(java::Type::Primitive(java::Primitive::Byte)),
588 value: Box::new(java::Expr::Literal(Literal::Int(format!("0x{number:02X}")))),
589 }
590 }
591 value => constant_expr(&type_, &value, &node.name)?,
592 };
593 items.push(Pending {
594 module: node.module,
595 member: java::Member::Field(java::Field {
596 doc: Doc::none(),
597 annotations: user_annotations(ast, zerodds_ast::AnyId::Const(id, node.node_id))?,
598 modifiers: vec![
599 java::Modifier::Public,
600 java::Modifier::Static,
601 java::Modifier::Final,
602 ],
603 type_,
604 name: Ident::new(&java::escape_keyword(&node.name)),
605 value: Some(value),
606 }),
607 });
608 }
609
610 for (id, node) in ast.exceptions() {
611 if ghost_created_for(applied, node.node_id, "JavaError").is_none() {
612 continue;
613 }
614 let _ = id;
615 let mut members = Vec::new();
616 for member in &node.members {
617 let Ok(member_node) = ast.member(*member) else {
618 return Err(MaterialiseError::Unevaluated {
619 construct: "exception member the model listed".to_string(),
620 at: node.name.clone(),
621 });
622 };
623 let Some(target) = member_node.type_.resolved() else {
624 return Err(MaterialiseError::Unevaluated {
625 construct: "exception member type".to_string(),
626 at: member_node.name.clone(),
627 });
628 };
629 members.push(field(
630 java_member_type(ast, target, &member_node.name)?,
631 &member_node.name,
632 Doc::none(),
633 ));
634 }
635 items.push(Pending {
636 module: node.module,
637 member: data_class(
638 &java::escape_keyword(&node.name),
639 members,
640 Doc::lines(&[
641 "An exception, as the structure its members make.",
642 "",
643 "A data holder and not a java.lang.Throwable: a Throwable is",
644 "Serializable, which under -Xlint:all -Werror demands a",
645 "serialVersionUID, and nothing about the wire form decides what",
646 "that number should be.",
647 ]),
648 None,
649 true,
650 )?,
651 });
652 }
653
654 let mut root = Scope::default();
655 for item in items {
656 insert(ast, &mut root, item)?;
657 }
658 let mut members = structural;
659 members.extend(scope_members(ast, root, 0)?);
660 check_names(&members, &[OUTER.to_string()], 0)?;
661
662 let (begin_file, end_file) = verbatim::file_slots(ast, verbatim::aliases::JAVA);
667 let mut decls: Vec<java::TypeDecl> = begin_file
668 .into_iter()
669 .map(java::TypeDecl::Verbatim)
670 .collect();
671 decls.push(java::TypeDecl::Class(java::Class {
672 doc: Doc::lines(&[
673 "The Java shape of one IDL file.",
674 "",
675 "Every declaration nests here: Java allows one public top-level type",
676 "per file, and one file is what a generated probe compiles.",
677 ]),
678 annotations: Vec::new(),
679 modifiers: vec![java::Modifier::Public, java::Modifier::Final],
680 name: Ident::new(OUTER),
681 generics: Vec::new(),
682 extends: None,
683 implements: Vec::new(),
684 members,
685 }));
686 decls.extend(end_file.into_iter().map(java::TypeDecl::Verbatim));
687 Ok(decls)
688}
689
690const GROUP_SUFFIX: &str = "Group";
695
696fn element_type(
699 ast: &Ast,
700 member: &zerodds_ast::AnnotationMemberNode,
701 owner: &str,
702) -> Result<java::Type, MaterialiseError> {
703 let at = format!("@{owner}.{}", member.name);
704 let Some(target) = member.type_.resolved() else {
705 return Err(MaterialiseError::Unevaluated {
706 construct: "annotation member type".to_string(),
707 at,
708 });
709 };
710 let type_ = java_member_type(ast, target, &at)?;
711 let allowed = match &type_ {
712 java::Type::Primitive(_) => true,
713 java::Type::Named { path, .. } => {
714 let segments: Vec<&str> = path.segments().iter().map(Ident::as_str).collect();
715 segments == STRING
716 }
717 _ => false,
718 };
719 if !allowed {
720 return Err(MaterialiseError::NoLanguageForm {
721 construct: format!(
722 "an annotation member of the type {}, which a Java annotation element cannot have",
723 zerodds_lang_render::java::type_(&type_)
724 ),
725 language: "Java",
726 at,
727 });
728 }
729 Ok(type_)
730}
731
732fn annotation_path(
735 ast: &Ast,
736 def: Id<zerodds_ast::AnnotationDefNode>,
737 group: bool,
738) -> Result<Path, MaterialiseError> {
739 let node = ast
740 .annotation_def(def)
741 .map_err(|_| MaterialiseError::Unevaluated {
742 construct: "annotation declaration".to_string(),
743 at: OUTER.to_string(),
744 })?;
745 let mut segments = vec![OUTER.to_string()];
746 for (_, name) in module_chain(ast, node.module)? {
747 segments.push(name);
748 }
749 let mut name = java::escape_keyword(&node.name);
750 if group {
751 name.push_str(GROUP_SUFFIX);
752 }
753 segments.push(name);
754 let refs: Vec<&str> = segments.iter().map(String::as_str).collect();
755 Ok(Path::new(&refs))
756}
757
758fn annotation_types(ast: &Ast) -> Result<Vec<(Id<ModuleNode>, java::Member)>, MaterialiseError> {
771 let mut out = Vec::new();
772 for (_, def) in ast.annotation_defs() {
773 let in_builtin = ast
774 .module(def.module)
775 .is_ok_and(|module| module.name == zerodds_ast::node::annotation::BUILTIN_MODULE_NAME);
776 if in_builtin {
777 continue;
778 }
779 let name = java::escape_keyword(&def.name);
780 let mut elements = Vec::new();
781 for member_id in &def.members {
782 let member =
783 ast.annotation_member(*member_id)
784 .map_err(|_| MaterialiseError::Unevaluated {
785 construct: "annotation member".to_string(),
786 at: def.name.clone(),
787 })?;
788 let type_ = element_type(ast, member, &def.name)?;
789 let default = match member.default {
790 Some(expr) => {
791 let at = format!("@{}.{}", def.name, member.name);
792 let value =
793 ast.eval_const(expr)
794 .map_err(|_| MaterialiseError::Unevaluated {
795 construct: "annotation member default".to_string(),
796 at: at.clone(),
797 })?;
798 Some(constant_expr(&type_, &value, &at)?)
799 }
800 None => None,
801 };
802 elements.push(java::AnnotationElement {
803 doc: Doc::none(),
804 type_,
805 name: Ident::new(&java::escape_keyword(&member.name)),
806 default,
807 });
808 }
809 out.push((
810 def.module,
811 java::Member::Nested(Box::new(java::TypeDecl::AnnotationType(
812 java::AnnotationType {
813 doc: Doc::line("A user-defined annotation type (XTypes 1.3 7.5.1.2.6.1)."),
814 annotations: Vec::new(),
815 modifiers: vec![java::Modifier::Public],
816 name: Ident::new(&name),
817 elements,
818 },
819 ))),
820 ));
821 out.push((
822 def.module,
823 java::Member::Nested(Box::new(java::TypeDecl::AnnotationType(
824 java::AnnotationType {
825 doc: Doc::line(
826 "Several instances of the annotation on one element (XTypes 1.3 \
827 7.5.1.2.6.1).",
828 ),
829 annotations: Vec::new(),
830 modifiers: vec![java::Modifier::Public],
831 name: Ident::new(&format!("{name}{GROUP_SUFFIX}")),
832 elements: vec![java::AnnotationElement {
833 doc: Doc::none(),
834 type_: java::Type::Array(Box::new(java::Type::named(Path::single(&name)))),
835 name: Ident::new("value"),
836 default: None,
837 }],
838 },
839 ))),
840 ));
841 }
842 Ok(out)
843}
844
845fn is_builtin_annotation(ast: &Ast, def: Id<zerodds_ast::AnnotationDefNode>) -> bool {
847 ast.annotation_def(def)
848 .ok()
849 .and_then(|node| ast.module(node.module).ok())
850 .is_some_and(|module| module.name == zerodds_ast::node::annotation::BUILTIN_MODULE_NAME)
851}
852
853fn applied_annotation(
855 ast: &Ast,
856 applied: &zerodds_ast::AppliedAnnotation,
857 def: Id<zerodds_ast::AnnotationDefNode>,
858) -> Result<java::Annotation, MaterialiseError> {
859 let node = ast
860 .annotation_def(def)
861 .map_err(|_| MaterialiseError::Unevaluated {
862 construct: "annotation declaration".to_string(),
863 at: applied.name.clone(),
864 })?;
865 let mut args = Vec::new();
866 for argument in &applied.arguments {
867 let member = argument
868 .name
869 .as_deref()
870 .and_then(|written| {
871 node.members.iter().find_map(|id| {
872 ast.annotation_member(*id)
873 .ok()
874 .filter(|m| m.name.eq_ignore_ascii_case(written))
875 })
876 })
877 .or_else(|| {
878 node.members.iter().find_map(|id| {
879 ast.annotation_member(*id)
880 .ok()
881 .filter(|m| m.is_value_member)
882 })
883 })
884 .ok_or_else(|| MaterialiseError::Unevaluated {
885 construct: "annotation argument naming no member".to_string(),
886 at: applied.name.clone(),
887 })?;
888 let at = format!("@{}.{}", node.name, member.name);
889 let type_ = element_type(ast, member, &node.name)?;
890 let value = ast
891 .eval_const(argument.value)
892 .map_err(|_| MaterialiseError::Unevaluated {
893 construct: "annotation argument".to_string(),
894 at: at.clone(),
895 })?;
896 args.push(java::AnnotationArg::Pair {
897 name: Ident::new(&java::escape_keyword(&member.name)),
898 value: constant_expr(&type_, &value, &at)?,
899 });
900 }
901 Ok(java::Annotation {
902 path: annotation_path(ast, def, false)?,
903 args,
904 })
905}
906
907fn user_annotations(
919 ast: &Ast,
920 any: zerodds_ast::AnyId,
921) -> Result<Vec<java::Annotation>, MaterialiseError> {
922 let facets = ast
923 .facets(any)
924 .ok_or_else(|| MaterialiseError::Unevaluated {
925 construct: "the annotations of an element the model listed".to_string(),
926 at: OUTER.to_string(),
927 })?;
928 let mut seen: Vec<Id<zerodds_ast::AnnotationDefNode>> = Vec::new();
929 let mut out = Vec::new();
930 for applied in &facets.annotations {
931 let Some(def) = applied.definition.resolved() else {
932 continue;
933 };
934 if is_builtin_annotation(ast, def) {
938 continue;
939 }
940 if seen.contains(&def) {
941 continue;
942 }
943 seen.push(def);
944 let same: Vec<&zerodds_ast::AppliedAnnotation> = facets
945 .annotations
946 .iter()
947 .filter(|other| other.definition.resolved() == Some(def))
948 .collect();
949 if let [only] = same.as_slice() {
950 out.push(applied_annotation(ast, only, def)?);
951 continue;
952 }
953 let mut instances = Vec::new();
954 for each in same {
955 instances.push(java::Expr::Annotation(applied_annotation(ast, each, def)?));
956 }
957 out.push(java::Annotation {
958 path: annotation_path(ast, def, true)?,
959 args: vec![java::AnnotationArg::Value(java::Expr::ArrayInit(instances))],
960 });
961 }
962 Ok(out)
963}
964
965fn annotate_declared(
968 ast: &Ast,
969 id: Id<TypeNode>,
970 start: usize,
971 items: &mut [Pending],
972) -> Result<(), MaterialiseError> {
973 let lost = |construct: &str, at: &str| MaterialiseError::Unevaluated {
974 construct: construct.to_string(),
975 at: at.to_string(),
976 };
977 let node = ast
978 .type_(id)
979 .map_err(|_| lost("a type the model listed", OUTER))?;
980 let type_name = java::escape_keyword(&node.name);
981 let type_annotations = user_annotations(ast, zerodds_ast::AnyId::Type(id, node.node_id))?;
982 let mut by_field: Vec<(String, Vec<java::Annotation>)> = Vec::new();
984 let mut push_field = |name: String, annotations: Vec<java::Annotation>| {
985 if !annotations.is_empty() {
986 by_field.push((name, annotations));
987 }
988 };
989 match &node.kind {
990 TypeKind::Structure { .. } | TypeKind::Valuetype { .. } => {
991 for member in ast.all_members_of(id) {
992 let member_node = ast
993 .member(member)
994 .map_err(|_| lost("a member the model listed", &node.name))?;
995 push_field(
996 java::escape_keyword(&member_node.name),
997 user_annotations(ast, zerodds_ast::AnyId::Member(member, member_node.node_id))?,
998 );
999 }
1000 }
1001 TypeKind::Union { .. } => {
1002 for case in ast.cases_of(id) {
1003 let case_node = ast
1004 .case(*case)
1005 .map_err(|_| lost("a union case the model listed", &node.name))?;
1006 let member_node = ast
1007 .member(case_node.member)
1008 .map_err(|_| lost("the member of a union case", &node.name))?;
1009 push_field(
1010 java::escape_keyword(&member_node.name),
1011 user_annotations(
1012 ast,
1013 zerodds_ast::AnyId::Member(case_node.member, member_node.node_id),
1014 )?,
1015 );
1016 }
1017 }
1018 TypeKind::Enumeration { enumerators } => {
1019 for enumerator in enumerators {
1020 let enum_node = ast
1021 .enumerator(*enumerator)
1022 .map_err(|_| lost("an enumerator the model listed", &node.name))?;
1023 push_field(
1024 format!("_{}", enum_node.name),
1025 user_annotations(
1026 ast,
1027 zerodds_ast::AnyId::Enumerator(*enumerator, enum_node.node_id),
1028 )?,
1029 );
1030 }
1031 }
1032 TypeKind::Bitmask { bits } => {
1033 for bit in bits {
1034 let bit_node = ast
1035 .bit_value(*bit)
1036 .map_err(|_| lost("a bitmask bit the model listed", &node.name))?;
1037 push_field(
1038 java::escape_keyword(&bit_node.name),
1039 user_annotations(ast, zerodds_ast::AnyId::BitValue(*bit, bit_node.node_id))?,
1040 );
1041 }
1042 }
1043 TypeKind::Bitset { .. } => {
1044 for bitfield in &ast.all_bitfields_of(id) {
1045 let field_node = ast
1046 .bitfield(*bitfield)
1047 .map_err(|_| lost("a bitset field the model listed", &node.name))?;
1048 if let Some(name) = field_node.name.as_ref() {
1049 push_field(
1050 java::escape_keyword(name),
1051 user_annotations(
1052 ast,
1053 zerodds_ast::AnyId::Bitfield(*bitfield, field_node.node_id),
1054 )?,
1055 );
1056 }
1057 }
1058 }
1059 TypeKind::Primitive(_)
1061 | TypeKind::String { .. }
1062 | TypeKind::Fixed { .. }
1063 | TypeKind::Sequence { .. }
1064 | TypeKind::Array { .. }
1065 | TypeKind::Map { .. }
1066 | TypeKind::Alias { .. }
1067 | TypeKind::ValueBox { .. }
1068 | TypeKind::Native
1069 | TypeKind::Any
1070 | TypeKind::ValueBase
1071 | TypeKind::ObjectRef { .. }
1072 | TypeKind::AnnotationType { .. } => {}
1073 }
1074 for pending in items.iter_mut().skip(start) {
1075 let java::Member::Nested(decl) = &mut pending.member else {
1076 continue;
1077 };
1078 let java::TypeDecl::Class(class) = decl.as_mut() else {
1079 continue;
1080 };
1081 if class.name.as_str() != type_name {
1082 continue;
1083 }
1084 class.annotations.extend(type_annotations.iter().cloned());
1085 for member in &mut class.members {
1086 let java::Member::Field(field) = member else {
1087 continue;
1088 };
1089 for (name, annotations) in &by_field {
1090 if field.name.as_str() == name {
1091 field.annotations.extend(annotations.iter().cloned());
1092 }
1093 }
1094 }
1095 }
1096 Ok(())
1097}
1098
1099fn weave_verbatim(ast: &Ast, id: Id<TypeNode>, start: usize, items: &mut Vec<Pending>) {
1110 let slots = verbatim::declaration_slots(ast, id, verbatim::aliases::JAVA);
1111 if slots.before.is_empty()
1112 && slots.begin.is_empty()
1113 && slots.end.is_empty()
1114 && slots.after.is_empty()
1115 {
1116 return;
1117 }
1118 let Ok(node) = ast.type_(id) else {
1119 return;
1120 };
1121 let name = java::escape_keyword(&node.name);
1122 let mut declared = items.split_off(start);
1123 let mut begin = slots.begin;
1124 let mut end = slots.end;
1125 let body = declared
1126 .iter_mut()
1127 .find_map(|pending| match &mut pending.member {
1128 java::Member::Nested(decl) => match decl.as_mut() {
1129 java::TypeDecl::Class(class) if class.name.as_str() == name => Some(class),
1130 _ => None,
1131 },
1132 _ => None,
1133 });
1134 if let Some(class) = body {
1135 for text in begin.drain(..).rev() {
1136 class.members.insert(0, java::Member::Verbatim(text));
1137 }
1138 class
1139 .members
1140 .extend(end.drain(..).map(java::Member::Verbatim));
1141 }
1142 for text in slots.before.into_iter().chain(begin) {
1143 items.push(Pending {
1144 module: node.module,
1145 member: java::Member::Verbatim(text),
1146 });
1147 }
1148 items.append(&mut declared);
1149 for text in end.into_iter().chain(slots.after) {
1150 items.push(Pending {
1151 module: node.module,
1152 member: java::Member::Verbatim(text),
1153 });
1154 }
1155}
1156
1157struct Pending {
1159 module: Id<ModuleNode>,
1161 member: java::Member,
1163}
1164
1165#[derive(Default)]
1174struct Scope {
1175 members: Vec<java::Member>,
1177 children: Vec<(Id<ModuleNode>, String, Scope)>,
1179}
1180
1181const MAX_MODULE_DEPTH: usize = 64;
1184
1185const MAX_TYPE_DEPTH: usize = 64;
1191
1192fn insert(ast: &Ast, root: &mut Scope, item: Pending) -> Result<(), MaterialiseError> {
1198 let chain = module_chain(ast, item.module)?;
1199 let mut scope = root;
1200 for (id, name) in chain {
1201 let at = match scope.children.iter().position(|(seen, _, _)| *seen == id) {
1202 Some(at) => at,
1203 None => {
1204 scope.children.push((id, name, Scope::default()));
1205 scope.children.len() - 1
1206 }
1207 };
1208 scope = &mut scope
1209 .children
1210 .get_mut(at)
1211 .ok_or_else(|| MaterialiseError::NoModule {
1212 at: module_handle(item.module),
1213 })?
1214 .2;
1215 }
1216 scope.members.push(item.member);
1217 Ok(())
1218}
1219
1220fn scope_members(
1228 ast: &Ast,
1229 scope: Scope,
1230 depth: usize,
1231) -> Result<Vec<java::Member>, MaterialiseError> {
1232 if depth > MAX_MODULE_DEPTH {
1233 return Err(MaterialiseError::NoModule {
1234 at: format!("the nesting runs past {MAX_MODULE_DEPTH}"),
1235 });
1236 }
1237 let mut out = scope.members;
1238 for (module, name, child) in scope.children {
1239 let mut class = nested_class(
1240 &name,
1241 scope_members(ast, child, depth + 1)?,
1242 Doc::line("An IDL module, as the class its declarations nest in."),
1243 );
1244 let node = ast.module(module).map_err(|_| MaterialiseError::NoModule {
1245 at: module_handle(module),
1246 })?;
1247 let annotations = user_annotations(ast, zerodds_ast::AnyId::Module(module, node.node_id))?;
1248 if let java::Member::Nested(decl) = &mut class
1249 && let java::TypeDecl::Class(inner) = decl.as_mut()
1250 {
1251 inner.annotations = annotations;
1252 }
1253 out.push(class);
1254 }
1255 Ok(out)
1256}
1257
1258fn module_chain(
1270 ast: &Ast,
1271 module: Id<ModuleNode>,
1272) -> Result<Vec<(Id<ModuleNode>, String)>, MaterialiseError> {
1273 let mut out = Vec::new();
1274 let mut current = Some(module);
1275 let mut depth = 0;
1276 while let Some(id) = current {
1277 if depth > MAX_MODULE_DEPTH {
1278 return Err(MaterialiseError::NoModule {
1279 at: format!(
1280 "{} — the parent chain runs past {MAX_MODULE_DEPTH}",
1281 module_handle(module)
1282 ),
1283 });
1284 }
1285 let node = ast.module(id).map_err(|_| MaterialiseError::NoModule {
1286 at: module_handle(id),
1287 })?;
1288 if node.parent.is_none() {
1290 break;
1291 }
1292 out.push((id, java::escape_keyword(&node.name)));
1293 current = node.parent;
1294 depth += 1;
1295 }
1296 out.reverse();
1297 Ok(out)
1298}
1299
1300fn module_handle(module: Id<ModuleNode>) -> String {
1303 format!("#{}@{}", module.slot(), module.generation())
1304}
1305
1306fn check_names(
1315 members: &[java::Member],
1316 enclosing: &[String],
1317 depth: usize,
1318) -> Result<(), MaterialiseError> {
1319 if depth > MAX_MODULE_DEPTH {
1320 return Err(MaterialiseError::NoModule {
1321 at: format!("the nesting runs past {MAX_MODULE_DEPTH}"),
1322 });
1323 }
1324 let mut seen: Vec<String> = Vec::new();
1325 for member in members {
1326 let java::Member::Nested(decl) = member else {
1327 continue;
1328 };
1329 let name = decl_name(decl).to_string();
1330 if name.is_empty() {
1332 continue;
1333 }
1334 if enclosing.contains(&name) {
1335 return Err(MaterialiseError::NoLanguageForm {
1336 construct: format!(
1337 "a declaration named `{name}` inside a class of that name \
1338 (JLS 8.1: a nested class may not share the simple name of an \
1339 enclosing one)"
1340 ),
1341 language: "Java",
1342 at: name,
1343 });
1344 }
1345 if depth == 0 && RESERVED_TOP_LEVEL.contains(&name.as_str()) {
1346 return Err(MaterialiseError::NoLanguageForm {
1347 construct: format!(
1348 "a declaration named `{name}`, which would capture the first \
1349 segment of every qualified reference this file writes \
1350 (JLS 6.5.2)"
1351 ),
1352 language: "Java",
1353 at: name,
1354 });
1355 }
1356 if seen.contains(&name) {
1357 return Err(MaterialiseError::NoLanguageForm {
1358 construct: format!("a second declaration named `{name}` in one scope"),
1359 language: "Java",
1360 at: name,
1361 });
1362 }
1363 let mut inner: Vec<String> = enclosing.to_vec();
1364 inner.push(name.clone());
1365 seen.push(name);
1366 if let java::TypeDecl::Class(class) = decl.as_ref() {
1367 check_names(&class.members, &inner, depth + 1)?;
1368 }
1369 }
1370 Ok(())
1371}
1372
1373fn decl_name(decl: &java::TypeDecl) -> &str {
1375 match decl {
1376 java::TypeDecl::Class(node) => node.name.as_str(),
1377 java::TypeDecl::Interface(node) => node.name.as_str(),
1378 java::TypeDecl::Enum(node) => node.name.as_str(),
1379 java::TypeDecl::Record(node) => node.name.as_str(),
1380 java::TypeDecl::AnnotationType(node) => node.name.as_str(),
1381 java::TypeDecl::Verbatim(_) => "",
1384 }
1385}
1386
1387#[allow(clippy::too_many_lines)]
1389fn declare(
1390 ast: &Ast,
1391 applied: &Applied,
1392 id: Id<TypeNode>,
1393 items: &mut Vec<Pending>,
1394 structural: &mut Vec<java::Member>,
1395 emitted: &mut Vec<String>,
1396) -> Result<(), MaterialiseError> {
1397 let Ok(node) = ast.type_(id) else {
1398 return Ok(());
1399 };
1400 match &node.kind {
1401 TypeKind::Primitive(_)
1404 | TypeKind::String { .. }
1405 | TypeKind::Fixed { .. }
1406 | TypeKind::Sequence { .. } => {}
1407
1408 TypeKind::Array { dims, .. } => {
1417 for dim in dims {
1418 ast.eval_integral(*dim)
1419 .map_err(|_| MaterialiseError::Unevaluated {
1420 construct: "array length".to_string(),
1421 at: named(&node.name, node.node_id),
1422 })?;
1423 }
1424 }
1425
1426 TypeKind::Map { key, value, .. } => {
1427 let (Some(key), Some(value)) = (key.resolved(), value.resolved()) else {
1428 return Err(MaterialiseError::Unevaluated {
1429 construct: "map key or value".to_string(),
1430 at: named(&node.name, node.node_id),
1431 });
1432 };
1433 let name = map_entry_name(ast, id)?;
1434 let at = named(&node.name, node.node_id);
1435 if !emitted.contains(&name) {
1436 emitted.push(name.clone());
1437 structural.push(nested_class(
1438 &name,
1439 vec![
1440 field(java_member_type(ast, key, &at)?, "key", Doc::none()),
1441 field(java_member_type(ast, value, &at)?, "value", Doc::none()),
1442 ],
1443 Doc::line(
1444 "One entry of a map. XTypes 1.3 7.4.1.1.4 defines a map as a \
1445 sequence of these, and the rules write the two halves with no \
1446 framing between them. The codec reads and writes the entries \
1447 of a map in this form, in the order of the map.",
1448 ),
1449 ));
1450 }
1451 if map_key_supported(ast, key) {
1452 let class = map_class_name(ast, id)?;
1453 if !emitted.contains(&class) {
1454 emitted.push(class.clone());
1455 structural.push(map_class(ast, id, key, value, &class, &name, &at)?);
1456 }
1457 }
1458 }
1459
1460 TypeKind::Alias { aliased } => {
1463 if node.name.is_empty() {
1464 return Ok(());
1465 }
1466 if ghost_created_for(applied, node.node_id, "JavaAlias").is_none() {
1467 return Ok(());
1468 }
1469 let Some(target) = aliased.resolved() else {
1470 return Err(MaterialiseError::Unevaluated {
1471 construct: "typedef target".to_string(),
1472 at: node.name.clone(),
1473 });
1474 };
1475 let spelling =
1476 zerodds_lang_render::java::type_(&java_member_type(ast, target, &node.name)?);
1477 items.push(Pending {
1478 module: node.module,
1479 member: java::Member::Comment(Doc::line(&format!(
1480 "typedef {} {}; — Java has no alias, so every use takes {spelling}.",
1481 spelling, node.name
1482 ))),
1483 });
1484 }
1485
1486 TypeKind::Enumeration { enumerators } => {
1487 if ghost_created_for(applied, node.node_id, "JavaEnum").is_none() {
1488 return Ok(());
1489 }
1490 let mut listed: Vec<(String, i128)> = Vec::new();
1491 for enumerator in enumerators {
1492 let Ok(enum_node) = ast.enumerator(*enumerator) else {
1493 return Err(MaterialiseError::Unevaluated {
1494 construct: "enumerator the model listed".to_string(),
1495 at: node.name.clone(),
1496 });
1497 };
1498 if ghost_created_for(applied, enum_node.node_id, "JavaEnumerator").is_none() {
1499 continue;
1500 }
1501 let value = ast.effective_enumerator_value(*enumerator).ok_or_else(|| {
1502 MaterialiseError::Unevaluated {
1503 construct: "enumerator value".to_string(),
1504 at: enum_node.name.clone(),
1505 }
1506 })?;
1507 let value = in_range(
1508 value,
1509 i128::from(i32::MIN),
1510 i128::from(u32::MAX),
1511 "int",
1512 &enum_node.name,
1513 )?;
1514 let literal = match i32::try_from(value) {
1520 Ok(fits) => i128::from(fits),
1521 Err(_) => value - (i128::from(u32::MAX) + 1),
1522 };
1523 listed.push((java::escape_keyword(&enum_node.name), literal));
1524 }
1525 let class_name = java::escape_keyword(&node.name);
1526 items.push(Pending {
1527 module: node.module,
1528 member: nested_class(
1529 &class_name,
1530 vec![java::Member::Verbatim(enumeration_class_body(
1531 &class_name,
1532 &listed,
1533 ))],
1534 Doc::line(
1535 "An enumeration, as CORBA 3.3 part 3 §1.5.9 maps it: one instance \
1536 per enumerator, the int constants `_NAME`, `value()` and \
1537 `from_int`.",
1538 ),
1539 ),
1540 });
1541 }
1542
1543 TypeKind::Bitmask { bits } => {
1544 if ghost_created_for(applied, node.node_id, "JavaBitmask").is_none() {
1545 return Ok(());
1546 }
1547 let bound = ast.effective_bit_bound(id);
1548 let holder = unsigned_holder(bound);
1549 let wide = holder == java::Type::Primitive(java::Primitive::Long);
1550 let mut members = Vec::new();
1551 let mut flags: Vec<(String, java::Expr)> = Vec::new();
1552 for bit in bits {
1553 let Ok(bit_node) = ast.bit_value(*bit) else {
1554 return Err(MaterialiseError::Unevaluated {
1555 construct: "bitmask bit the model listed".to_string(),
1556 at: node.name.clone(),
1557 });
1558 };
1559 if ghost_created_for(applied, bit_node.node_id, "JavaBitConst").is_none() {
1560 continue;
1561 }
1562 let position = ast.effective_bit_position(*bit).ok_or_else(|| {
1563 MaterialiseError::Unevaluated {
1564 construct: "bit position".to_string(),
1565 at: bit_node.name.clone(),
1566 }
1567 })?;
1568 let flag_value = java::Expr::Binary {
1571 op: "<<",
1572 lhs: Box::new(java::Expr::Literal(Literal::Int(
1573 if wide { "1L" } else { "1" }.to_string(),
1574 ))),
1575 rhs: Box::new(java::Expr::Literal(Literal::int(i128::from(position)))),
1576 };
1577 flags.push((bit_node.name.clone(), flag_value.clone()));
1578 members.push(constant(holder.clone(), &bit_node.name, flag_value));
1579 }
1580 items.push(Pending {
1586 module: node.module,
1587 member: type_safe_enumeration(
1588 &format!("{}Bits", java::escape_keyword(&node.name)),
1589 holder.clone(),
1590 flags,
1591 Doc::line(
1592 "The enumeration of the flags of a bitmask (XTypes 1.3 \
1593 7.5.1.2.2): {@code _<label>} is the flag's value, \
1594 {@code 1 << position}.",
1595 ),
1596 ),
1597 });
1598 items.push(Pending {
1599 module: node.module,
1600 member: nested_class(
1601 &java::escape_keyword(&node.name),
1602 members,
1603 Doc::line(
1604 "A bitmask, as the constants its bits name. A member of this \
1605 type takes the unsigned holder Table 33 of XTypes 1.3 \
1606 7.4.1.1.3.2 gives for its bound, mapped through the unsigned \
1607 rule this file states.",
1608 ),
1609 ),
1610 });
1611 }
1612
1613 TypeKind::Bitset { .. } => {
1614 if ghost_created_for(applied, node.node_id, "JavaBitset").is_none() {
1615 return Ok(());
1616 }
1617 let mut members = Vec::new();
1618 for bitfield in &ast.all_bitfields_of(id) {
1621 let Ok(field_node) = ast.bitfield(*bitfield) else {
1622 return Err(MaterialiseError::Unevaluated {
1623 construct: "bitset field the model listed".to_string(),
1624 at: node.name.clone(),
1625 });
1626 };
1627 let Some(field_name) = field_node.name.as_ref() else {
1630 continue;
1631 };
1632 members.push(field(
1637 unsigned_holder(field_node.bits),
1638 field_name,
1639 Doc::none(),
1640 ));
1641 }
1642 items.push(Pending {
1643 module: node.module,
1644 member: data_class(
1645 &java::escape_keyword(&node.name),
1646 members,
1647 Doc::line(
1648 "A bitset. The fields are separate integers here; the packing \
1649 into the anchor is the codec's.",
1650 ),
1651 None,
1652 true,
1653 )?,
1654 });
1655 }
1656
1657 TypeKind::Structure { .. } | TypeKind::Valuetype { .. } => {
1658 let created = if matches!(node.kind, TypeKind::Valuetype { .. }) {
1659 "JavaValue"
1660 } else {
1661 "JavaStruct"
1662 };
1663 if ghost_created_for(applied, node.node_id, created).is_none() {
1664 return Ok(());
1665 }
1666 let base = match &node.kind {
1677 TypeKind::Structure {
1678 base: Some(base), ..
1679 } => base.resolved(),
1680 _ => None,
1681 };
1682 let own: Vec<_> = ast.members_of(id).to_vec();
1683 let mut members = Vec::new();
1684 let mut spelled = Vec::new();
1685 for member in ast.all_members_of(id) {
1686 let Ok(member_node) = ast.member(member) else {
1687 return Err(MaterialiseError::Unevaluated {
1688 construct: "member the model listed".to_string(),
1689 at: node.name.clone(),
1690 });
1691 };
1692 let Some(target) = member_node.type_.resolved() else {
1693 return Err(MaterialiseError::Unevaluated {
1694 construct: "member type".to_string(),
1695 at: member_node.name.clone(),
1696 });
1697 };
1698 let type_ = java_member_type(ast, target, &member_node.name)?;
1699 spelled.push((
1700 java::escape_keyword(&member_node.name),
1701 member_node.name.clone(),
1702 ));
1703 if base.is_some() && !own.contains(&member) {
1704 continue;
1705 }
1706 if member_node.facets.is_optional() {
1707 members.push(field(
1708 boxed(type_),
1709 &member_node.name,
1710 Doc::line(
1711 "An @optional member: null when absent. In a @mutable type \
1712 an absent member is not written at all; outside one its \
1713 absence is written — a zero is_present byte under XCDR2 \
1714 (rule 20) or a parameter header declaring a length of zero \
1715 under XCDR1 (7.4.1.2).",
1716 ),
1717 ));
1718 } else if super::annotated_external(member_node) {
1719 members.push(field(
1726 boxed(type_),
1727 &member_node.name,
1728 Doc::line(
1729 "An @external member: an object reference, so a primitive \
1730 is held as its java.lang box (XTypes 1.3 7.5.1.2.3.4).",
1731 ),
1732 ));
1733 } else {
1734 members.push(field(type_, &member_node.name, Doc::none()));
1735 }
1736 }
1737 no_spelling_collision(
1740 spelled
1741 .iter()
1742 .map(|(field, declared)| (field.as_str(), declared.as_str())),
1743 "Java",
1744 &node.name,
1745 )?;
1746 let extends = match base {
1747 Some(base) => Some(java::Type::named(qualified(ast, base)?)),
1748 None => None,
1749 };
1750 items.push(Pending {
1751 module: node.module,
1752 member: data_class(
1753 &java::escape_keyword(&node.name),
1754 members,
1755 Doc::none(),
1756 extends,
1757 !has_derived(ast, id),
1758 )?,
1759 });
1760 }
1761
1762 TypeKind::Union { discriminator, .. } => {
1763 if ghost_created_for(applied, node.node_id, "JavaUnion").is_none() {
1764 return Ok(());
1765 }
1766 let Some(disc) = discriminator.resolved() else {
1767 return Err(MaterialiseError::Unevaluated {
1768 construct: "union discriminator".to_string(),
1769 at: node.name.clone(),
1770 });
1771 };
1772 let mut members = vec![java::Member::Field(java::Field {
1776 doc: Doc::line("The discriminator. Which case field is live is what this says."),
1777 annotations: Vec::new(),
1778 modifiers: vec![java::Modifier::Public],
1779 type_: java_member_type(ast, disc, &node.name)?,
1780 name: Ident::new("_d"),
1781 value: None,
1782 })];
1783 let mut accessed: Vec<UnionAccess> = Vec::new();
1784 for case in ast.cases_of(id) {
1785 let Ok(case_node) = ast.case(*case) else {
1786 return Err(MaterialiseError::Unevaluated {
1787 construct: "union case the model listed".to_string(),
1788 at: node.name.clone(),
1789 });
1790 };
1791 if ghost_created_for(applied, case_node.node_id, "JavaCase").is_none() {
1792 continue;
1793 }
1794 let Ok(member) = ast.member(case_node.member) else {
1795 return Err(MaterialiseError::Unevaluated {
1796 construct: "member behind a union case the model listed".to_string(),
1797 at: node.name.clone(),
1798 });
1799 };
1800 let Some(target) = member.type_.resolved() else {
1801 return Err(MaterialiseError::Unevaluated {
1802 construct: "union case type".to_string(),
1803 at: member.name.clone(),
1804 });
1805 };
1806 let branch = java_member_type(ast, target, &member.name)?;
1807 let case_type = if super::annotated_external(member) {
1810 boxed(branch)
1811 } else {
1812 branch
1813 };
1814 let mut labels = Vec::new();
1815 for candidate in &case_node.labels {
1816 if let zerodds_ast::CaseLabel::Value(expr) = candidate {
1817 let value = ast.eval_integral(*expr).map_err(|_| {
1818 MaterialiseError::Unevaluated {
1819 construct: "union case label".to_string(),
1820 at: node.name.clone(),
1821 }
1822 })?;
1823 labels.push(java_label_text(ast, disc, value));
1824 }
1825 }
1826 accessed.push(UnionAccess {
1827 name: java::escape_keyword(&member.name),
1828 type_: zerodds_lang_render::java::type_(&case_type),
1829 labels,
1830 });
1831 members.push(field(case_type, &member.name, Doc::none()));
1832 }
1833 if mapping() != JavaMapping::Psm {
1836 let disc_type =
1837 zerodds_lang_render::java::type_(&java_member_type(ast, disc, &node.name)?);
1838 members.push(java::Member::Verbatim(union_accessors(
1839 &disc_type, &accessed,
1840 )));
1841 }
1842 items.push(Pending {
1843 module: node.module,
1844 member: data_class(
1845 &java::escape_keyword(&node.name),
1846 members,
1847 Doc::line("A union: the discriminator, and one field per case."),
1848 None,
1849 true,
1850 )?,
1851 });
1852 }
1853
1854 TypeKind::Native => {
1857 if ghost_created_for(applied, node.node_id, "JavaNative").is_none() {
1858 return Ok(());
1859 }
1860 let spelling = zerodds_lang_render::java::type_(&native_type(ast, id));
1861 items.push(Pending {
1862 module: node.module,
1863 member: java::Member::Comment(Doc::line(&format!(
1864 "native {}; — every use takes {spelling}.",
1865 node.name
1866 ))),
1867 });
1868 }
1869
1870 TypeKind::ValueBox { .. } => {}
1873
1874 TypeKind::ObjectRef { .. }
1883 | TypeKind::AnnotationType { .. }
1884 | TypeKind::Any
1885 | TypeKind::ValueBase => {}
1886 }
1887 Ok(())
1888}
1889
1890pub fn java_member_type(
1905 ast: &Ast,
1906 target: Id<TypeNode>,
1907 at: &str,
1908) -> Result<java::Type, MaterialiseError> {
1909 type_at(ast, target, at, 0)
1910}
1911
1912fn is_octet(ast: &Ast, id: Id<TypeNode>, depth: usize) -> bool {
1917 if depth > MAX_TYPE_DEPTH {
1918 return false;
1919 }
1920 match ast.type_(id).map(|node| &node.kind) {
1921 Ok(TypeKind::Primitive(PrimitiveKind::Octet)) => true,
1922 Ok(TypeKind::Alias { aliased }) => aliased
1923 .resolved()
1924 .is_some_and(|target| is_octet(ast, target, depth + 1)),
1925 _ => false,
1926 }
1927}
1928
1929fn type_at(
1935 ast: &Ast,
1936 id: Id<TypeNode>,
1937 at: &str,
1938 depth: usize,
1939) -> Result<java::Type, MaterialiseError> {
1940 if depth > MAX_TYPE_DEPTH {
1941 return Err(MaterialiseError::Unevaluated {
1942 construct: format!("type, whose Java spelling nests past {MAX_TYPE_DEPTH}"),
1943 at: at.to_string(),
1944 });
1945 }
1946 let node = ast.type_(id).map_err(|_| MaterialiseError::Unevaluated {
1947 construct: "type".to_string(),
1948 at: at.to_string(),
1949 })?;
1950 let refused = match &node.kind {
1951 TypeKind::Any => Some("any"),
1952 TypeKind::ValueBase => Some("ValueBase"),
1953 TypeKind::ObjectRef { .. } => Some("Object"),
1956 TypeKind::AnnotationType { .. } => Some("annotation type"),
1959 _ => None,
1960 };
1961 if let Some(construct) = refused {
1962 return Err(MaterialiseError::NoLanguageForm {
1963 construct: construct.to_string(),
1964 language: "Java",
1965 at: at.to_string(),
1966 });
1967 }
1968 Ok(match &node.kind {
1969 TypeKind::Primitive(PrimitiveKind::Float128) => java::Type::named(Path::new(FLOAT128)),
1973 TypeKind::Primitive(kind) => java::Type::Primitive(java_primitive(*kind)),
1974 TypeKind::String { .. } => java::Type::named(Path::new(STRING)),
1978 TypeKind::Fixed { .. } => java::Type::named(Path::new(BIG_DECIMAL)),
1979 TypeKind::Sequence { element, bound } => {
1980 let Some(element) = element.resolved() else {
1981 return Err(MaterialiseError::Unevaluated {
1982 construct: "sequence element".to_string(),
1983 at: at.to_string(),
1984 });
1985 };
1986 let element = type_at(ast, element, at, depth + 1)?;
1987 if mapping() == JavaMapping::Psm && matches!(bound, zerodds_ast::Bound::Unbounded) {
1990 java::Type::generic(Path::new(COLLECTION), vec![boxed(element)])
1991 } else {
1992 java::Type::Array(Box::new(element))
1993 }
1994 }
1995 TypeKind::Array { element, dims } => {
1996 let Some(element) = element.resolved() else {
1997 return Err(MaterialiseError::Unevaluated {
1998 construct: "array element".to_string(),
1999 at: at.to_string(),
2000 });
2001 };
2002 let mut out = type_at(ast, element, at, depth + 1)?;
2003 for _ in dims {
2008 out = java::Type::Array(Box::new(out));
2009 }
2010 out
2011 }
2012 TypeKind::Map { key, .. }
2018 if key
2019 .resolved()
2020 .is_some_and(|key| map_key_supported(ast, key)) =>
2021 {
2022 java::Type::named(Path::new(&[OUTER, &map_class_name(ast, id)?]))
2023 }
2024 TypeKind::Map { .. } => java::Type::Array(Box::new(java::Type::named(Path::new(&[
2025 OUTER,
2026 &map_entry_name(ast, id)?,
2027 ])))),
2028 TypeKind::Alias { aliased } => {
2030 let Some(target) = aliased.resolved() else {
2031 return Err(MaterialiseError::Unevaluated {
2032 construct: "typedef target".to_string(),
2033 at: at.to_string(),
2034 });
2035 };
2036 type_at(ast, target, at, depth + 1)?
2037 }
2038 TypeKind::Enumeration { .. } => java::Type::named(qualified(ast, id)?),
2041 TypeKind::Bitmask { .. } => unsigned_holder(ast.effective_bit_bound(id)),
2042 TypeKind::Structure { .. }
2043 | TypeKind::Union { .. }
2044 | TypeKind::Bitset { .. }
2045 | TypeKind::Valuetype { .. } => java::Type::named(qualified(ast, id)?),
2046 TypeKind::Native => native_type(ast, id),
2047 TypeKind::ValueBox { boxed: inner } => {
2050 let Some(target) = inner.resolved() else {
2051 return Err(MaterialiseError::Unevaluated {
2052 construct: "value box target".to_string(),
2053 at: at.to_string(),
2054 });
2055 };
2056 boxed(type_at(ast, target, at, depth + 1)?)
2057 }
2058 TypeKind::Any
2059 | TypeKind::ValueBase
2060 | TypeKind::ObjectRef { .. }
2061 | TypeKind::AnnotationType { .. } => {
2062 return Err(MaterialiseError::NoLanguageForm {
2066 construct: node.kind.as_str().to_string(),
2067 language: "Java",
2068 at: at.to_string(),
2069 });
2070 }
2071 })
2072}
2073
2074#[must_use]
2092pub const fn java_primitive(kind: PrimitiveKind) -> java::Primitive {
2093 match kind {
2094 PrimitiveKind::Boolean => java::Primitive::Boolean,
2095 PrimitiveKind::Char | PrimitiveKind::WChar => java::Primitive::Char,
2096 PrimitiveKind::Int8 => java::Primitive::Byte,
2097 PrimitiveKind::Octet => java::Primitive::Byte,
2099 PrimitiveKind::UInt8 => java::Primitive::Int,
2101 PrimitiveKind::Int16 => java::Primitive::Short,
2102 PrimitiveKind::UInt16 => java::Primitive::Int,
2104 PrimitiveKind::Int32 => java::Primitive::Int,
2105 PrimitiveKind::UInt32 => java::Primitive::Long,
2107 PrimitiveKind::Int64 => java::Primitive::Long,
2108 PrimitiveKind::UInt64 => java::Primitive::Long,
2110 PrimitiveKind::Float32 => java::Primitive::Float,
2111 PrimitiveKind::Float64 | PrimitiveKind::Float128 => java::Primitive::Double,
2112 }
2113}
2114
2115#[must_use]
2124pub fn unsigned_holder(bits: u16) -> java::Type {
2125 if bits <= 16 {
2126 java::Type::Primitive(java::Primitive::Int)
2127 } else {
2128 java::Type::Primitive(java::Primitive::Long)
2129 }
2130}
2131
2132fn boxed(type_: java::Type) -> java::Type {
2138 match type_ {
2139 java::Type::Primitive(primitive) => {
2140 java::Type::named(Path::new(&["java", "lang", primitive.boxed()]))
2141 }
2142 other => other,
2143 }
2144}
2145
2146fn native_type(ast: &Ast, id: Id<TypeNode>) -> java::Type {
2152 let mapping = ast
2153 .any_of_type(id)
2154 .ok()
2155 .and_then(|any| ast.facets(any))
2156 .and_then(|facets| facets.corba.as_ref())
2157 .and_then(|corba| corba.native_mapping.clone());
2158 match mapping {
2159 Some(spelling) => java::Type::named(Path::new(&spelling.split('.').collect::<Vec<&str>>())),
2160 None => java::Type::named(Path::new(&["java", "lang", "Object"])),
2161 }
2162}
2163
2164fn qualified(ast: &Ast, id: Id<TypeNode>) -> Result<Path, MaterialiseError> {
2175 let node = ast.type_(id).map_err(|_| MaterialiseError::Unevaluated {
2176 construct: "type".to_string(),
2177 at: OUTER.to_string(),
2178 })?;
2179 let mut segments = vec![OUTER.to_string()];
2180 for (_, name) in module_chain(ast, node.module)? {
2181 segments.push(name);
2182 }
2183 segments.push(java::escape_keyword(&node.name));
2184 Ok(Path::new(
2185 &segments.iter().map(String::as_str).collect::<Vec<&str>>(),
2186 ))
2187}
2188
2189pub fn map_entry_name(ast: &Ast, id: Id<TypeNode>) -> Result<String, MaterialiseError> {
2194 let Ok(node) = ast.type_(id) else {
2195 return Err(MaterialiseError::Unevaluated {
2196 construct: "map".to_string(),
2197 at: OUTER.to_string(),
2198 });
2199 };
2200 let TypeKind::Map { key, value, .. } = &node.kind else {
2201 return Err(MaterialiseError::Unevaluated {
2202 construct: "map".to_string(),
2203 at: named(&node.name, node.node_id),
2204 });
2205 };
2206 let (Some(key), Some(value)) = (key.resolved(), value.resolved()) else {
2207 return Err(MaterialiseError::Unevaluated {
2208 construct: "map key or value".to_string(),
2209 at: named(&node.name, node.node_id),
2210 });
2211 };
2212 Ok(format!(
2213 "{ENTRY_PREFIX}{}_{}",
2214 type_tag(ast, key, 0)?,
2215 type_tag(ast, value, 0)?
2216 ))
2217}
2218#[must_use]
2226pub fn entry_class_of_map_class(class: &str) -> Option<String> {
2227 let rest = class.strip_prefix(MAP_PREFIX)?;
2228 let rest = rest.strip_prefix('_').map_or(rest, |tail| {
2229 let after = tail.strip_prefix('b').and_then(|bound| {
2231 let end = bound.find('_')?;
2232 let (digits, remainder) = bound.split_at(end);
2233 let is_bound =
2234 digits == "x" || (!digits.is_empty() && digits.bytes().all(|b| b.is_ascii_digit()));
2235 (is_bound && remainder[1..].contains('_')).then(|| &remainder[1..])
2236 });
2237 after.unwrap_or(tail)
2238 });
2239 rest.contains('_').then(|| format!("{ENTRY_PREFIX}{rest}"))
2241}
2242
2243pub fn map_class_name(ast: &Ast, id: Id<TypeNode>) -> Result<String, MaterialiseError> {
2248 let Ok(node) = ast.type_(id) else {
2249 return Err(MaterialiseError::Unevaluated {
2250 construct: "map".to_string(),
2251 at: OUTER.to_string(),
2252 });
2253 };
2254 let TypeKind::Map { key, value, bound } = &node.kind else {
2255 return Err(MaterialiseError::Unevaluated {
2256 construct: "map".to_string(),
2257 at: named(&node.name, node.node_id),
2258 });
2259 };
2260 let (Some(key), Some(value)) = (key.resolved(), value.resolved()) else {
2261 return Err(MaterialiseError::Unevaluated {
2262 construct: "map key or value".to_string(),
2263 at: named(&node.name, node.node_id),
2264 });
2265 };
2266 Ok(format!(
2267 "{MAP_PREFIX}{}_{}_{}",
2268 bound_tag(ast, bound),
2269 type_tag(ast, key, 0)?,
2270 type_tag(ast, value, 0)?
2271 ))
2272}
2273
2274#[must_use]
2280pub fn map_key_supported(ast: &Ast, key: Id<TypeNode>) -> bool {
2281 let mut current = key;
2282 for _ in 0..MAX_TYPE_DEPTH {
2283 let Ok(node) = ast.type_(current) else {
2284 return false;
2285 };
2286 match &node.kind {
2287 TypeKind::Primitive(PrimitiveKind::Float128) => return false,
2288 TypeKind::Primitive(_)
2289 | TypeKind::String { .. }
2290 | TypeKind::Enumeration { .. }
2291 | TypeKind::Bitmask { .. }
2292 | TypeKind::Structure { .. }
2293 | TypeKind::Union { .. }
2294 | TypeKind::Bitset { .. } => return true,
2295 TypeKind::Alias { aliased } => match aliased.resolved() {
2296 Some(next) => current = next,
2297 None => return false,
2298 },
2299 _ => return false,
2300 }
2301 }
2302 false
2303}
2304
2305fn map_class(
2322 ast: &Ast,
2323 id: Id<TypeNode>,
2324 key: Id<TypeNode>,
2325 value: Id<TypeNode>,
2326 class: &str,
2327 entry: &str,
2328 at: &str,
2329) -> Result<java::Member, MaterialiseError> {
2330 let bound = match ast.type_(id).map(|node| &node.kind) {
2331 Ok(TypeKind::Map { bound, .. }) => match bound.expr() {
2332 None => 0,
2333 Some(expr) => ast
2334 .eval_integral(expr)
2335 .ok()
2336 .and_then(|limit| u32::try_from(limit).ok())
2337 .ok_or_else(|| MaterialiseError::Unevaluated {
2338 construct: "map bound".to_string(),
2339 at: at.to_string(),
2340 })?,
2341 },
2342 _ => 0,
2343 };
2344 let key_type = zerodds_lang_render::java::type_(&boxed(java_member_type(ast, key, at)?));
2345 let value_type = zerodds_lang_render::java::type_(&boxed(java_member_type(ast, value, at)?));
2346 let entry = format!("{OUTER}.{entry}");
2347 let text = format!(
2348 " private static final long serialVersionUID = 1L;
2349 /** `map<K, V, N>`: N, or 0 for a map with no bound. */
2350 private static final int ZD_BOUND = {bound};
2351 /** DDS 1.4 2.2.2.1.1, ReturnCode_t. */
2352 public static final int RETCODE_OK = 0;
2353 /** DDS 1.4 2.2.2.1.1, ReturnCode_t. */
2354 public static final int RETCODE_ERROR = 1;
2355 /** DDS 1.4 2.2.2.1.1, ReturnCode_t. */
2356 public static final int RETCODE_BAD_PARAMETER = 3;
2357 /** DDS 1.4 2.2.2.1.1, ReturnCode_t. */
2358 public static final int RETCODE_PRECONDITION_NOT_MET = 4;
2359 private int zdMaximum;
2360
2361 /** Table 47 initialize: the size and the capacity are 0. */
2362 public {class}() {{
2363 super();
2364 }}
2365
2366 /**
2367 * Table 47 get_max_size: how many entries the map may contain. A map
2368 * the codec filled holds the entries it read, so it is never below
2369 * the size.
2370 */
2371 public int getMaxSize() {{
2372 return Math.max(zdMaximum, size());
2373 }}
2374
2375 /**
2376 * Table 47 set_max_size: BAD_PARAMETER if the new maximum is below
2377 * the size or above the bound.
2378 */
2379 public int setMaxSize(int maxSize) {{
2380 if (maxSize < size() || (ZD_BOUND != 0 && maxSize > ZD_BOUND)) {{
2381 return RETCODE_BAD_PARAMETER;
2382 }}
2383 zdMaximum = maxSize;
2384 return RETCODE_OK;
2385 }}
2386
2387 /**
2388 * Table 47 insert (for {{@code replace}} false) and insert_or_assign (for
2389 * true): an existing key is BAD_PARAMETER or has its element replaced, a
2390 * new one past the capacity is PRECONDITION_NOT_MET. A null key or
2391 * element is BAD_PARAMETER.
2392 */
2393 public int insert({key_type} key, {value_type} element, boolean replace) {{
2394 if (key == null || element == null) {{
2395 return RETCODE_BAD_PARAMETER;
2396 }}
2397 if (containsKey(key)) {{
2398 if (!replace) {{
2399 return RETCODE_BAD_PARAMETER;
2400 }}
2401 put(key, element);
2402 return RETCODE_OK;
2403 }}
2404 if (size() >= getMaxSize()) {{
2405 return RETCODE_PRECONDITION_NOT_MET;
2406 }}
2407 put(key, element);
2408 return RETCODE_OK;
2409 }}
2410
2411 /**
2412 * Table 47 copy: overwrite this map with {{@code other}}. Too large for
2413 * the capacity, {{@code autogrow}} decides: the capacity grows, or the
2414 * answer is PRECONDITION_NOT_MET and this map stays as it was.
2415 */
2416 public int copyFrom({class} other, boolean autogrow) {{
2417 if (other == null) {{
2418 return RETCODE_BAD_PARAMETER;
2419 }}
2420 if (other == this) {{
2421 return RETCODE_OK;
2422 }}
2423 if (other.size() > getMaxSize()) {{
2424 if (!autogrow) {{
2425 return RETCODE_PRECONDITION_NOT_MET;
2426 }}
2427 int grown = setMaxSize(other.size());
2428 if (grown != RETCODE_OK) {{
2429 return grown;
2430 }}
2431 }}
2432 clear();
2433 putAll(other);
2434 return RETCODE_OK;
2435 }}
2436
2437 /** The entries, in the order of the map: what the codec writes. */
2438 public {entry}[] entries() {{
2439 {entry}[] out = new {entry}[size()];
2440 int i = 0;
2441 for (java.util.Map.Entry<{key_type}, {value_type}> e : entrySet()) {{
2442 out[i] = new {entry}();
2443 out[i].key = e.getKey();
2444 out[i].value = e.getValue();
2445 i = i + 1;
2446 }}
2447 return out;
2448 }}
2449
2450 /**
2451 * The map of entries, in their order: what the codec reads. A key
2452 * twice is no map and is refused.
2453 */
2454 public static {class} of({entry}[] entries) {{
2455 {class} map = new {class}();
2456 for ({entry} e : entries) {{
2457 if (map.containsKey(e.key)) {{
2458 throw new java.lang.IllegalArgumentException(\"the map has the key \" + e.key + \" twice\");
2459 }}
2460 map.put(e.key, e.value);
2461 }}
2462 map.zdMaximum = map.size();
2463 return map;
2464 }}
2465
2466 /** No entries: where a reader starts. */
2467 public static {entry}[] noEntries() {{
2468 return new {entry}[0];
2469 }}
2470"
2471 );
2472 Ok(java::Member::Nested(Box::new(java::TypeDecl::Class(
2473 java::Class {
2474 doc: Doc::lines(&[
2475 "A map (XTypes 1.3 7.5.1.3.5): a java.util.Map in the order the entries",
2476 "were put, with the operations of Table 47 that Java does not have.",
2477 ]),
2478 annotations: Vec::new(),
2479 modifiers: vec![
2480 java::Modifier::Public,
2481 java::Modifier::Static,
2482 java::Modifier::Final,
2483 ],
2484 name: Ident::new(class),
2485 generics: Vec::new(),
2486 extends: Some(java::Type::generic(
2487 Path::new(&["java", "util", "LinkedHashMap"]),
2488 vec![
2489 boxed(java_member_type(ast, key, at)?),
2490 boxed(java_member_type(ast, value, at)?),
2491 ],
2492 )),
2493 implements: Vec::new(),
2494 members: vec![java::Member::Verbatim(text)],
2495 },
2496 ))))
2497}
2498
2499pub fn declared_path(ast: &Ast, id: Id<TypeNode>) -> Result<String, MaterialiseError> {
2510 let node = ast.type_(id).map_err(|_| MaterialiseError::Unevaluated {
2511 construct: "type".to_string(),
2512 at: OUTER.to_string(),
2513 })?;
2514 let mut segments = vec!["ZdTypes".to_string()];
2515 for (_, name) in module_chain(ast, node.module)? {
2516 segments.push(name);
2517 }
2518 segments.push(java::escape_keyword(&node.name));
2519 Ok(segments.join("."))
2520}
2521
2522fn bound_tag(ast: &Ast, bound: &zerodds_ast::Bound) -> String {
2542 match bound.expr() {
2543 None => String::new(),
2544 Some(expr) => match ast.eval_integral(expr) {
2548 Ok(limit) => format!("_b{limit}"),
2549 Err(_) => "_bx".to_string(),
2553 },
2554 }
2555}
2556
2557pub fn type_tag(ast: &Ast, id: Id<TypeNode>, depth: usize) -> Result<String, MaterialiseError> {
2574 if depth > MAX_TYPE_DEPTH {
2575 return Err(MaterialiseError::Unevaluated {
2576 construct: format!("type, whose name nests past {MAX_TYPE_DEPTH}"),
2577 at: OUTER.to_string(),
2578 });
2579 }
2580 let Ok(node) = ast.type_(id) else {
2581 return Ok("unknown".to_string());
2582 };
2583 Ok(match &node.kind {
2584 TypeKind::Primitive(kind) => kind.as_str().replace(' ', "_"),
2585 TypeKind::String {
2586 width: CharWidth::Char,
2587 bound,
2588 } => format!("string{}", bound_tag(ast, bound)),
2589 TypeKind::String {
2590 width: CharWidth::WChar,
2591 bound,
2592 } => format!("wstring{}", bound_tag(ast, bound)),
2593 TypeKind::Fixed { digits, scale } => format!("fixed_{digits}_{scale}"),
2594 TypeKind::Sequence { element, bound } => match element.resolved() {
2595 Some(element) => format!(
2596 "seq{}_{}",
2597 bound_tag(ast, bound),
2598 type_tag(ast, element, depth + 1)?
2599 ),
2600 None => format!("seq_unknown{}", bound_tag(ast, bound)),
2601 },
2602 TypeKind::Array { element, dims } => {
2603 let mut name = match element.resolved() {
2604 Some(element) => format!("arr_{}", type_tag(ast, element, depth + 1)?),
2605 None => "arr_unknown".to_string(),
2606 };
2607 for dim in dims {
2608 match ast.eval_integral(*dim) {
2609 Ok(length) => name.push_str(&format!("_{length}")),
2610 Err(_) => name.push_str("_x"),
2615 }
2616 }
2617 name
2618 }
2619 TypeKind::Map { key, value, bound } => {
2623 let key = match key.resolved() {
2624 Some(key) => type_tag(ast, key, depth + 1)?,
2625 None => "unknown".to_string(),
2626 };
2627 let value = match value.resolved() {
2628 Some(value) => type_tag(ast, value, depth + 1)?,
2629 None => "unknown".to_string(),
2630 };
2631 format!("map{}_{key}_{value}", bound_tag(ast, bound))
2632 }
2633 TypeKind::Alias { aliased } => match aliased.resolved() {
2634 Some(target) => type_tag(ast, target, depth + 1)?,
2635 None => "unknown".to_string(),
2636 },
2637 TypeKind::ValueBox { boxed: inner } => match inner.resolved() {
2638 Some(target) => format!("box_{}", type_tag(ast, target, depth + 1)?),
2639 None => "box_unknown".to_string(),
2640 },
2641 TypeKind::Structure { .. }
2642 | TypeKind::Union { .. }
2643 | TypeKind::Enumeration { .. }
2644 | TypeKind::Bitmask { .. }
2645 | TypeKind::Bitset { .. }
2646 | TypeKind::Valuetype { .. }
2647 | TypeKind::Native => {
2648 let mut segments = Vec::new();
2649 for (_, name) in module_chain(ast, node.module)? {
2650 segments.push(name);
2651 }
2652 segments.push(java::escape_keyword(&node.name));
2653 segments.join("_")
2654 }
2655 TypeKind::Any => "any".to_string(),
2656 TypeKind::ValueBase => "valuebase".to_string(),
2657 TypeKind::ObjectRef { .. } => "objectref".to_string(),
2658 TypeKind::AnnotationType { .. } => "annotation".to_string(),
2659 })
2660}
2661
2662fn nested_class(name: &str, members: Vec<java::Member>, doc: Doc) -> java::Member {
2664 nested_class_extending(name, members, doc, None, true)
2665}
2666
2667fn nested_class_extending(
2670 name: &str,
2671 members: Vec<java::Member>,
2672 doc: Doc,
2673 extends: Option<java::Type>,
2674 final_: bool,
2675) -> java::Member {
2676 let mut modifiers = vec![java::Modifier::Public, java::Modifier::Static];
2677 if final_ {
2678 modifiers.push(java::Modifier::Final);
2679 }
2680 java::Member::Nested(Box::new(java::TypeDecl::Class(java::Class {
2681 doc,
2682 annotations: Vec::new(),
2683 modifiers,
2684 name: Ident::new(name),
2685 generics: Vec::new(),
2686 extends,
2687 implements: Vec::new(),
2688 members,
2689 })))
2690}
2691
2692pub(crate) fn has_derived(ast: &Ast, id: Id<TypeNode>) -> bool {
2694 ast.types().any(|(_, node)| {
2695 matches!(
2696 &node.kind,
2697 TypeKind::Structure { base: Some(base), .. } if base.resolved() == Some(id)
2698 )
2699 })
2700}
2701
2702fn data_class(
2707 name: &str,
2708 members: Vec<java::Member>,
2709 doc: Doc,
2710 extends: Option<java::Type>,
2711 final_: bool,
2712) -> Result<java::Member, MaterialiseError> {
2713 let value_methods = equals_and_hash_code(name, &members, extends.is_some());
2714 let mut members = if mapping() == JavaMapping::Psm {
2715 encapsulate(name, members)?
2716 } else {
2717 members
2718 };
2719 members.push(java::Member::Verbatim(value_methods));
2720 Ok(nested_class_extending(name, members, doc, extends, final_))
2721}
2722
2723fn equals_and_hash_code(name: &str, members: &[java::Member], derived: bool) -> String {
2729 let fields: Vec<&str> = members
2730 .iter()
2731 .filter_map(|member| match member {
2732 java::Member::Field(data) if !data.modifiers.contains(&java::Modifier::Static) => {
2733 Some(data.name.as_str())
2734 }
2735 _ => None,
2736 })
2737 .collect();
2738 let mut equal = String::new();
2739 if derived {
2740 equal.push_str("super.equals(other)");
2741 }
2742 for field in &fields {
2743 if !equal.is_empty() {
2744 equal.push_str("\n && ");
2745 }
2746 equal.push_str(&format!(
2747 "java.util.Objects.deepEquals({field}, that.{field})"
2748 ));
2749 }
2750 if equal.is_empty() {
2751 equal.push_str("true");
2752 }
2753 let mut hashed: Vec<String> = Vec::new();
2754 if derived {
2755 hashed.push("super.hashCode()".to_string());
2756 }
2757 hashed.extend(fields.iter().map(|field| (*field).to_string()));
2758 format!(
2759 " /** Equal when every member is equal; an array by its elements. */
2760 @Override
2761 public boolean equals(Object other) {{
2762 if (this == other) {{
2763 return true;
2764 }}
2765 if (other == null || getClass() != other.getClass()) {{
2766 return false;
2767 }}
2768 {name} that = ({name}) other;
2769 return {equal};
2770 }}
2771
2772 /** Consistent with {{@link #equals(Object)}}. */
2773 @Override
2774 public int hashCode() {{
2775 return java.util.Arrays.deepHashCode(new Object[] {{ {} }});
2776 }}
2777",
2778 hashed.join(", ")
2779 )
2780}
2781
2782fn encapsulate(
2798 class_name: &str,
2799 members: Vec<java::Member>,
2800) -> Result<Vec<java::Member>, MaterialiseError> {
2801 let mut fields = Vec::new();
2802 let mut accessors = Vec::new();
2803 let mut others = Vec::new();
2804 let mut names: Vec<(String, String)> = Vec::new();
2805 for member in members {
2806 let java::Member::Field(mut data) = member else {
2807 others.push(member);
2808 continue;
2809 };
2810 if data.modifiers.contains(&java::Modifier::Static) {
2811 fields.push(java::Member::Field(data));
2812 continue;
2813 }
2814 let field_name = data.name.as_str().to_string();
2815 let type_ = data.type_.clone();
2816 data.modifiers = vec![java::Modifier::Private];
2817 fields.push(java::Member::Field(data));
2818
2819 let this = |name: &str| java::Expr::Field {
2820 value: Box::new(java::Expr::Path(Path::new(&["this"]))),
2821 name: Ident::new(name),
2822 };
2823 let getter = |name: String| {
2824 java::Member::Method(java::Method {
2825 doc: Doc::none(),
2826 annotations: Vec::new(),
2827 modifiers: vec![java::Modifier::Public],
2828 generics: Vec::new(),
2829 returns: type_.clone(),
2830 name: Ident::new(&name),
2831 params: Vec::new(),
2832 throws: Vec::new(),
2833 body: Some(java::Block {
2834 statements: vec![java::Statement::Return(Some(this(&field_name)))],
2835 }),
2836 })
2837 };
2838 names.push((getter_name(&field_name), field_name.clone()));
2839 accessors.push(getter(getter_name(&field_name)));
2840 if type_ == java::Type::Primitive(java::Primitive::Boolean) {
2841 names.push((boolean_getter_name(&field_name), field_name.clone()));
2842 accessors.push(getter(boolean_getter_name(&field_name)));
2843 }
2844 names.push((setter_name(&field_name), field_name.clone()));
2845 accessors.push(java::Member::Method(java::Method {
2846 doc: Doc::none(),
2847 annotations: Vec::new(),
2848 modifiers: vec![java::Modifier::Public],
2849 generics: Vec::new(),
2850 returns: java::Type::Void,
2851 name: Ident::new(&setter_name(&field_name)),
2852 params: vec![java::Param {
2853 annotations: Vec::new(),
2854 final_: false,
2855 type_: type_.clone(),
2856 name: Ident::new("v"),
2857 varargs: false,
2858 }],
2859 throws: Vec::new(),
2860 body: Some(java::Block {
2861 statements: vec![java::Statement::Expr(java::Expr::Binary {
2862 op: "=",
2863 lhs: Box::new(this(&field_name)),
2864 rhs: Box::new(java::Expr::Path(Path::new(&["v"]))),
2865 })],
2866 }),
2867 }));
2868 }
2869 no_spelling_collision(
2870 names
2871 .iter()
2872 .map(|(spelled, declared)| (spelled.as_str(), declared.as_str())),
2873 "Java accessor",
2874 class_name,
2875 )?;
2876 let mut out = others;
2877 out.extend(fields);
2878 out.push(java::Member::Constructor(java::Constructor {
2879 doc: Doc::none(),
2880 annotations: Vec::new(),
2881 modifiers: vec![java::Modifier::Public],
2882 name: Ident::new(class_name),
2883 params: Vec::new(),
2884 throws: Vec::new(),
2885 body: java::Block {
2886 statements: Vec::new(),
2887 },
2888 }));
2889 out.extend(accessors);
2890 Ok(out)
2891}
2892
2893fn field(type_: java::Type, name: &str, doc: Doc) -> java::Member {
2900 java::Member::Field(java::Field {
2901 doc,
2902 annotations: Vec::new(),
2903 modifiers: vec![java::Modifier::Public],
2904 type_,
2905 name: Ident::new(&java::escape_keyword(name)),
2906 value: None,
2907 })
2908}
2909
2910fn type_safe_enumeration(
2922 class: &str,
2923 value_type: java::Type,
2924 literals: Vec<(String, java::Expr)>,
2925 doc: Doc,
2926) -> java::Member {
2927 let own = || java::Type::named(Path::single(class));
2928 let path = |name: &str| java::Expr::Path(Path::single(name));
2929 let mut members = Vec::new();
2930 for (label, value) in &literals {
2931 members.push(constant(
2932 value_type.clone(),
2933 &format!("_{label}"),
2934 value.clone(),
2935 ));
2936 }
2937 for (label, _) in &literals {
2938 members.push(constant(
2939 own(),
2940 label,
2941 java::Expr::New {
2942 type_: Box::new(own()),
2943 args: vec![path(&format!("_{label}"))],
2944 },
2945 ));
2946 }
2947 members.push(java::Member::Field(java::Field {
2948 doc: Doc::none(),
2949 annotations: Vec::new(),
2950 modifiers: vec![java::Modifier::Private, java::Modifier::Final],
2951 type_: value_type.clone(),
2952 name: Ident::new("__value"),
2953 value: None,
2954 }));
2955 members.push(java::Member::Constructor(java::Constructor {
2956 doc: Doc::none(),
2957 annotations: Vec::new(),
2958 modifiers: vec![java::Modifier::Private],
2959 name: Ident::new(class),
2960 params: vec![java::Param {
2961 annotations: Vec::new(),
2962 final_: false,
2963 type_: value_type.clone(),
2964 name: Ident::new("v"),
2965 varargs: false,
2966 }],
2967 throws: Vec::new(),
2968 body: java::Block {
2969 statements: vec![java::Statement::Expr(java::Expr::Binary {
2970 op: "=",
2971 lhs: Box::new(java::Expr::Field {
2972 value: Box::new(java::Expr::This),
2973 name: Ident::new("__value"),
2974 }),
2975 rhs: Box::new(path("v")),
2976 })],
2977 },
2978 }));
2979 members.push(java::Member::Method(java::Method {
2980 doc: Doc::line("The value the IDL gives the literal."),
2981 annotations: Vec::new(),
2982 modifiers: vec![java::Modifier::Public],
2983 generics: Vec::new(),
2984 returns: value_type.clone(),
2985 name: Ident::new("value"),
2986 params: Vec::new(),
2987 throws: Vec::new(),
2988 body: Some(java::Block {
2989 statements: vec![java::Statement::Return(Some(path("__value")))],
2990 }),
2991 }));
2992 let mut lookup: Vec<java::Statement> = Vec::new();
2993 for (label, _) in &literals {
2994 lookup.push(java::Statement::If {
2995 condition: java::Expr::Binary {
2996 op: "==",
2997 lhs: Box::new(path("v")),
2998 rhs: Box::new(path(&format!("_{label}"))),
2999 },
3000 then: java::Block {
3001 statements: vec![java::Statement::Return(Some(path(&java::escape_keyword(
3002 label,
3003 ))))],
3004 },
3005 otherwise: None,
3006 });
3007 }
3008 lookup.push(java::Statement::Throw(java::Expr::New {
3009 type_: Box::new(java::Type::named(Path::new(&[
3010 "java",
3011 "lang",
3012 "IllegalArgumentException",
3013 ]))),
3014 args: vec![java::Expr::Binary {
3015 op: "+",
3016 lhs: Box::new(java::Expr::Literal(Literal::Str(format!(
3017 "not a value of {class}: "
3018 )))),
3019 rhs: Box::new(path("v")),
3020 }],
3021 }));
3022 members.push(java::Member::Method(java::Method {
3023 doc: Doc::line("The literal that has the value `v`."),
3024 annotations: Vec::new(),
3025 modifiers: vec![java::Modifier::Public, java::Modifier::Static],
3026 generics: Vec::new(),
3027 returns: own(),
3028 name: Ident::new("from_int"),
3029 params: vec![java::Param {
3030 annotations: Vec::new(),
3031 final_: false,
3032 type_: value_type,
3033 name: Ident::new("v"),
3034 varargs: false,
3035 }],
3036 throws: Vec::new(),
3037 body: Some(java::Block { statements: lookup }),
3038 }));
3039 nested_class(class, members, doc)
3040}
3041
3042fn constant(type_: java::Type, name: &str, value: java::Expr) -> java::Member {
3044 java::Member::Field(java::Field {
3045 doc: Doc::none(),
3046 annotations: Vec::new(),
3047 modifiers: vec![
3048 java::Modifier::Public,
3049 java::Modifier::Static,
3050 java::Modifier::Final,
3051 ],
3052 type_,
3053 name: Ident::new(&java::escape_keyword(name)),
3054 value: Some(value),
3055 })
3056}
3057
3058fn enumeration_class_body(class: &str, enumerators: &[(String, i128)]) -> String {
3064 let mut text = format!(
3065 " private final int zdValue;\n\n private {class}(int value) {{\n zdValue = value;\n }}\n\n"
3066 );
3067 for (name, value) in enumerators {
3068 text.push_str(&format!(
3069 " /** The value of `{name}`. */\n public static final int _{name} = {value};\n /** The instance of `{name}`. */\n public static final {class} {name} = new {class}(_{name});\n\n"
3070 ));
3071 }
3072 text.push_str(" /** The value of this enumerator. */\n public int value() {\n return zdValue;\n }\n\n");
3073 text.push_str(&format!(
3074 " /** The enumerator with this value (the first one, where two share it). */\n public static {class} from_int(int value) {{\n"
3075 ));
3076 for (name, _) in enumerators {
3077 text.push_str(&format!(
3078 " if (value == _{name}) {{\n return {name};\n }}\n"
3079 ));
3080 }
3081 text.push_str(&format!(
3082 " throw new IllegalArgumentException(\"no enumerator of {class} has the value \" + value);\n }}\n"
3083 ));
3084 text
3085}
3086
3087struct UnionAccess {
3089 name: String,
3090 type_: String,
3091 labels: Vec<String>,
3093}
3094
3095fn java_label_text(ast: &Ast, discriminator: Id<TypeNode>, value: i128) -> String {
3097 let mut current = discriminator;
3098 for _ in 0..MAX_TYPE_DEPTH {
3099 match ast.type_(current).map(|node| &node.kind) {
3100 Ok(TypeKind::Alias { aliased }) => match aliased.resolved() {
3101 Some(next) => current = next,
3102 None => break,
3103 },
3104 Ok(TypeKind::Enumeration { .. }) => {
3105 let owner = declared_path(ast, current)
3106 .unwrap_or_else(|_| format!("UnresolvedEnum{}", current.slot()));
3107 let narrowed = i32::try_from(value).unwrap_or_else(|_| {
3109 u32::try_from(value & 0xFFFF_FFFF)
3110 .map_or(0, |bits| i32::from_ne_bytes(bits.to_ne_bytes()))
3111 });
3112 return format!("{owner}.from_int({narrowed})");
3113 }
3114 Ok(TypeKind::Primitive(PrimitiveKind::Boolean)) => {
3115 return if value == 0 { "false" } else { "true" }.to_string();
3116 }
3117 Ok(TypeKind::Primitive(kind)) => {
3118 return match java_primitive(*kind) {
3119 java::Primitive::Byte => format!("(byte) {value}"),
3120 java::Primitive::Short => format!("(short) {value}"),
3121 java::Primitive::Char => format!("(char) {value}"),
3122 java::Primitive::Long => {
3125 #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
3126 let pattern = if value > i128::from(i64::MAX) {
3127 value as u64 as i64
3128 } else {
3129 value as i64
3130 };
3131 format!("{pattern}L")
3132 }
3133 _ => format!("{value}"),
3134 };
3135 }
3136 _ => break,
3137 }
3138 }
3139 format!("{value}")
3140}
3141
3142fn union_accessors(discriminator: &str, cases: &[UnionAccess]) -> String {
3148 let all: Vec<&str> = cases
3149 .iter()
3150 .flat_map(|case| case.labels.iter().map(String::as_str))
3151 .collect();
3152 let any_label = |labels: &[&str]| -> String {
3153 if labels.is_empty() {
3154 "false".to_string()
3155 } else {
3156 labels
3157 .iter()
3158 .map(|label| format!("_d == {label}"))
3159 .collect::<Vec<_>>()
3160 .join(" || ")
3161 }
3162 };
3163 let mut text = format!(
3164 " /** The discriminator. */\n public {discriminator} discriminator() {{\n return _d;\n }}\n\n"
3165 );
3166 for case in cases {
3167 let own: Vec<&str> = case.labels.iter().map(String::as_str).collect();
3168 let selected = if own.is_empty() {
3169 format!("!({})", any_label(&all))
3170 } else {
3171 any_label(&own)
3172 };
3173 let (name, ty) = (&case.name, &case.type_);
3174 text.push_str(&format!(
3175 " /** The value of `{name}`; refused when the discriminator selects another case. */\n public {ty} {name}() {{\n if (!({selected})) {{\n throw new IllegalStateException(\"the discriminator does not select `{name}`\");\n }}\n return {name};\n }}\n\n"
3176 ));
3177 if let [only] = own.as_slice() {
3178 text.push_str(&format!(
3179 " /** Sets `{name}` and selects it. */\n public void {name}({ty} value) {{\n this.{name} = value;\n this._d = {only};\n }}\n\n"
3180 ));
3181 }
3182 text.push_str(&format!(
3183 " /** Sets `{name}` under the discriminator `disc`, which has to select it. */\n public void {name}({discriminator} disc, {ty} value) {{\n {discriminator} previous = _d;\n _d = disc;\n if (!({selected})) {{\n _d = previous;\n throw new IllegalArgumentException(\"the discriminator does not select `{name}`\");\n }}\n this.{name} = value;\n }}\n\n"
3184 ));
3185 }
3186 text
3187}
3188
3189fn constant_expr(
3217 type_: &java::Type,
3218 value: &zerodds_ast::ConstValue,
3219 at: &str,
3220) -> Result<java::Expr, MaterialiseError> {
3221 use zerodds_ast::ConstValue;
3222 let refuse = |construct: String| MaterialiseError::NoLanguageForm {
3223 construct,
3224 language: "Java",
3225 at: at.to_string(),
3226 };
3227 match value {
3228 ConstValue::Integer(number) => literal_of(type_, *number, at),
3229 ConstValue::Boolean(flag) => Ok(java::Expr::Literal(Literal::Bool(*flag))),
3230 ConstValue::Char(character) => Ok(java::Expr::Literal(Literal::Char(*character))),
3231 ConstValue::WChar(point) => match constant::wchar_char(*point)
3235 .filter(|character| u32::from(*character) <= u32::from(u16::MAX))
3236 {
3237 Some(character) => Ok(java::Expr::Literal(Literal::Char(character))),
3238 None => Err(refuse(format!(
3239 "the code point U+{point:04X}, which a Java `char` does not hold"
3240 ))),
3241 },
3242 ConstValue::Float { mantissa, exponent } => {
3243 if !matches!(
3244 type_,
3245 java::Type::Primitive(java::Primitive::Float | java::Primitive::Double)
3246 ) {
3247 return Err(refuse(String::from(
3248 "a `long double` constant, which has no Java literal",
3249 )));
3250 }
3251 let mut text = constant::float_text(mantissa, exponent.as_deref());
3252 if matches!(type_, java::Type::Primitive(java::Primitive::Float)) {
3256 text.push('f');
3257 }
3258 Ok(java::Expr::Literal(Literal::Float(text)))
3259 }
3260 ConstValue::Fixed { digits, scale } => Ok(java::Expr::New {
3261 type_: Box::new(java::Type::named(Path::new(BIG_DECIMAL))),
3262 args: vec![java::Expr::Literal(Literal::Str(constant::fixed_decimal(
3263 digits, *scale,
3264 )))],
3265 }),
3266 ConstValue::String(text) => Ok(java::Expr::Literal(Literal::Str(text.clone()))),
3267 ConstValue::WString(points) => match constant::wstring_utf8(points) {
3268 Some(text) => Ok(java::Expr::Literal(Literal::Str(text))),
3269 None => Err(refuse(String::from(
3270 "a `wstring` constant holding a lone surrogate",
3271 ))),
3272 },
3273 _ => Err(refuse(String::from(
3278 "a constant form this binding has no literal for",
3279 ))),
3280 }
3281}
3282
3283fn literal_of(type_: &java::Type, value: i128, at: &str) -> Result<java::Expr, MaterialiseError> {
3285 let java::Type::Primitive(primitive) = type_ else {
3286 return Err(MaterialiseError::Unevaluated {
3287 construct: "constant's value, which is not an integer".to_string(),
3288 at: at.to_string(),
3289 });
3290 };
3291 Ok(match primitive {
3292 java::Primitive::Boolean => java::Expr::Literal(Literal::Bool(value != 0)),
3293 java::Primitive::Char => cast(
3297 *primitive,
3298 in_range(value, 0, i128::from(u16::MAX), "char", at)?,
3299 ),
3300 java::Primitive::Byte => cast(*primitive, in_range(value, -128, 127, "byte", at)?),
3301 java::Primitive::Short => cast(
3302 *primitive,
3303 in_range(
3304 value,
3305 i128::from(i16::MIN),
3306 i128::from(i16::MAX),
3307 "short",
3308 at,
3309 )?,
3310 ),
3311 java::Primitive::Int => java::Expr::Literal(Literal::int(in_range(
3312 value,
3313 i128::from(i32::MIN),
3314 i128::from(i32::MAX),
3315 "int",
3316 at,
3317 )?)),
3318 java::Primitive::Long => {
3319 let value = in_range(
3320 value,
3321 i128::from(i64::MIN),
3322 i128::from(i64::MAX),
3323 "long",
3324 at,
3325 )?;
3326 java::Expr::Literal(Literal::Int(format!("{value}L")))
3330 }
3331 java::Primitive::Float | java::Primitive::Double => {
3332 java::Expr::Literal(Literal::int(value))
3333 }
3334 })
3335}
3336
3337fn cast(primitive: java::Primitive, value: i128) -> java::Expr {
3339 java::Expr::Cast {
3340 type_: Box::new(java::Type::Primitive(primitive)),
3341 value: Box::new(java::Expr::Literal(Literal::int(value))),
3342 }
3343}
3344
3345fn in_range(
3352 value: i128,
3353 low: i128,
3354 high: i128,
3355 type_: &str,
3356 at: &str,
3357) -> Result<i128, MaterialiseError> {
3358 if value < low || value > high {
3359 return Err(MaterialiseError::NoLanguageForm {
3360 construct: format!("the value {value}, which a Java `{type_}` does not hold"),
3361 language: "Java",
3362 at: at.to_string(),
3363 });
3364 }
3365 Ok(value)
3366}
3367
3368fn named(name: &str, node: NodeId) -> String {
3374 if name.is_empty() {
3375 format!("{node:?}")
3376 } else {
3377 name.to_string()
3378 }
3379}
3380
3381fn ghost_created_for(applied: &Applied, node: NodeId, type_name: &str) -> Option<GhostId> {
3387 let anchor = applied
3388 .origin
3389 .iter()
3390 .find(|(_, model)| **model == node)
3391 .map(|(ghost, _)| *ghost)?;
3392 let typ = applied.graph.types.lookup(type_name)?;
3393 applied
3394 .graph
3395 .nodes_of_type(typ)
3396 .map(|created| created.id)
3397 .find(|created| {
3398 applied.graph.connected(&anchor, created)
3399 || applied
3400 .graph
3401 .parts(&anchor)
3402 .any(|part| applied.graph.connected(&part.other, created))
3403 })
3404}
3405
3406#[cfg(test)]
3407mod tests {
3408 #![allow(clippy::expect_used, clippy::panic, clippy::unwrap_used)]
3409 use super::*;
3410 use crate::rule;
3411 use crate::{ModelGraph, apply};
3412 use std::path::{Path as FsPath, PathBuf};
3413
3414 #[test]
3415 fn the_entry_class_is_found_from_the_map_class() {
3416 assert_eq!(
3417 entry_class_of_map_class("ZdMap_long_string").as_deref(),
3418 Some("ZdEntry_long_string")
3419 );
3420 assert_eq!(
3421 entry_class_of_map_class("ZdMap_b8_long_string").as_deref(),
3422 Some("ZdEntry_long_string")
3423 );
3424 assert_eq!(
3425 entry_class_of_map_class("ZdMap_bx_unsigned_short_string").as_deref(),
3426 Some("ZdEntry_unsigned_short_string")
3427 );
3428 assert_eq!(entry_class_of_map_class("Other_long_string"), None);
3429 for idl in [
3430 "@final struct P { map<long, string> m; };",
3431 "@final struct P { map<long, string, 8> m; };",
3432 ] {
3433 let ast = {
3434 let parsed = zerodds_idl_parse::parse(idl);
3435 let lowered = zerodds_idl_lower::lower(&parsed.syntax(), "t.idl");
3436 let mut ast = lowered.ast;
3437 ast.resolve_all_by_name().expect("resolve");
3438 ast
3439 };
3440 let map = ast
3441 .types()
3442 .find(|(_, node)| matches!(node.kind, TypeKind::Map { .. }))
3443 .map(|(id, _)| id)
3444 .expect("a map type");
3445 assert_eq!(
3446 entry_class_of_map_class(&map_class_name(&ast, map).expect("class")),
3447 Some(map_entry_name(&ast, map).expect("entry")),
3448 "{idl}"
3449 );
3450 }
3451 }
3452
3453 fn render(idl: &str, path: &str) -> Result<String, String> {
3457 with_mapping(JavaMapping::Omg, || render_in_force(idl, path))
3458 }
3459
3460 fn render_in_force(idl: &str, path: &str) -> Result<String, String> {
3461 let parsed = zerodds_idl_parse::parse(idl);
3462 if parsed.has_errors() {
3463 return Err(format!("parse: {:?}", parsed.diagnostics()));
3464 }
3465 let lowered = zerodds_idl_lower::lower(&parsed.syntax(), path);
3466 if !lowered.diagnostics.is_empty() {
3467 return Err(format!("lower: {:?}", lowered.diagnostics));
3468 }
3469 let mut ast = lowered.ast;
3470 ast.resolve_all_by_name()
3471 .map_err(|error| format!("resolve: {error}"))?;
3472 let applied = apply(&ModelGraph::of(&ast), &rule::java::catalogue())
3473 .map_err(|error| format!("apply: {error}"))?;
3474 let types = java_items(&ast, &applied).map_err(|error| format!("materialise: {error}"))?;
3475 Ok(zerodds_lang_render::java::file(&java::File {
3476 doc: Doc::lines(&[
3477 "Generated by zerodds-lang-rules. Do not edit.",
3478 "",
3479 "The Java shape of one IDL file. The wire rules are not here — they",
3480 "are in runtime/java, which implements them from DDS-XTypes 1.3.",
3481 ]),
3482 package: None,
3483 imports: Vec::new(),
3484 types,
3485 }))
3486 }
3487
3488 fn declarations(idl: &str) -> String {
3490 match render(idl, "test.idl") {
3491 Ok(text) => text,
3492 Err(why) => panic!("{why}"),
3493 }
3494 }
3495
3496 fn corpus() -> Vec<(String, PathBuf)> {
3498 let root = FsPath::new(env!("CARGO_MANIFEST_DIR")).join("../../tests/interop-corpus");
3499 let mut out = Vec::new();
3500 for entry in std::fs::read_dir(&root).expect("the corpus directory") {
3501 let entry = entry.expect("a corpus entry");
3502 let idl = entry.path().join("type.idl");
3503 if idl.is_file() {
3504 out.push((entry.file_name().to_string_lossy().into_owned(), idl));
3505 }
3506 }
3507 out.sort();
3508 out
3509 }
3510
3511 #[test]
3518 fn every_corpus_case_renders_or_names_its_failure() {
3519 let cases = corpus();
3520 assert!(
3521 cases.len() > 300,
3522 "the corpus is not where it was: {} cases",
3523 cases.len()
3524 );
3525 let out = std::env::var("ZD_JAVA_OUT").ok();
3526 let mut failures = Vec::new();
3527 for (name, path) in cases {
3528 let source = std::fs::read_to_string(&path).expect("a corpus source");
3529 match render(&source, &path.display().to_string()) {
3530 Ok(text) => {
3531 if let Some(dir) = &out {
3532 let case = FsPath::new(dir).join(&name);
3533 std::fs::create_dir_all(&case).expect("the output directory");
3534 std::fs::write(case.join(format!("{OUTER}.java")), text)
3535 .expect("the output file");
3536 }
3537 }
3538 Err(why) => failures.push(format!("{name}: {why}")),
3539 }
3540 }
3541 let failures = crate::adapter::without_the_front_end_refusals(failures);
3549 assert!(failures.is_empty(), "{failures:#?}");
3550 }
3551
3552 #[test]
3553 fn every_idl_primitive_reaches_the_java_type_that_holds_it() {
3554 let generated = declarations(
3555 "@final struct P {
3556 boolean a; octet b; char c; wchar d;
3557 int8 e; uint8 f; short g; unsigned short h;
3558 long i; unsigned long j; long long k; unsigned long long l;
3559 float m; double n; long double o;
3560 };",
3561 );
3562 for expected in [
3563 "public boolean a;",
3564 "public byte b;",
3565 "public char c;",
3566 "public char d;",
3567 "public byte e;",
3568 "public int f;",
3569 "public short g;",
3570 "public int h;",
3571 "public int i;",
3572 "public long j;",
3573 "public long k;",
3574 "public long l;",
3575 "public float m;",
3576 "public double n;",
3577 "public org.zerodds.wire.Float128 o;",
3578 ] {
3579 assert!(generated.contains(expected), "{expected}\n{generated}");
3580 }
3581 }
3582
3583 #[test]
3584 fn a_sequence_and_an_array_are_both_java_arrays() {
3585 let generated = declarations(
3586 "@final struct P { sequence<long> s; long a[3]; long g[2][3]; sequence<string> t; };",
3587 );
3588 assert!(generated.contains("public int[] s;"), "{generated}");
3589 assert!(generated.contains("public int[] a;"), "{generated}");
3590 assert!(generated.contains("public int[][] g;"), "{generated}");
3591 assert!(
3592 generated.contains("public java.lang.String[] t;"),
3593 "{generated}"
3594 );
3595 }
3596
3597 #[test]
3598 fn a_map_is_a_java_util_map_over_a_generated_entry_class() {
3599 let generated = declarations("@final struct P { map<long, string> m; };");
3600 assert!(
3603 generated.contains("class ZdEntry_long_string {"),
3604 "{generated}"
3605 );
3606 assert!(generated.contains("public int key;"), "{generated}");
3607 assert!(
3608 generated.contains("public java.lang.String value;"),
3609 "{generated}"
3610 );
3611 assert!(
3614 generated.contains(
3615 "class ZdMap_long_string extends java.util.LinkedHashMap<java.lang.Integer, java.lang.String> {"
3616 ),
3617 "{generated}"
3618 );
3619 assert!(
3620 generated.contains("public ZdTypes.ZdMap_long_string m;"),
3621 "{generated}"
3622 );
3623 for operation in [
3625 "public int getMaxSize()",
3626 "public int setMaxSize(int maxSize)",
3627 "public int insert(java.lang.Integer key, java.lang.String element, boolean replace)",
3628 "public int copyFrom(ZdMap_long_string other, boolean autogrow)",
3629 ] {
3630 assert!(generated.contains(operation), "{operation}:\n{generated}");
3631 }
3632 }
3633
3634 #[test]
3635 fn a_map_keyed_by_a_structure_is_a_java_util_map_over_a_class_with_equals() {
3636 let generated = declarations("struct K { long a; }; @final struct P { map<K, long> m; };");
3637 assert!(
3638 generated.contains("class ZdMap_K_long extends"),
3639 "{generated}"
3640 );
3641 assert!(
3642 generated.contains("public boolean equals(Object other)")
3643 && generated.contains("public int hashCode()"),
3644 "{generated}"
3645 );
3646 assert!(!generated.contains("ZdEntry_K_long[] m;"), "{generated}");
3647 }
3648
3649 #[test]
3650 fn a_map_keyed_by_a_sequence_keeps_its_entries() {
3651 let generated = declarations("@final struct P { map<sequence<long>, long> m; };");
3652 assert!(generated.contains("[] m;"), "{generated}");
3653 assert!(!generated.contains("ZdMap_"), "{generated}");
3654 }
3655
3656 #[test]
3657 fn a_bounded_map_has_a_class_of_its_own_with_the_bound() {
3658 let generated =
3659 declarations("@final struct P { map<long, long, 3> a; map<long, long> b; };");
3660 assert!(
3661 generated.contains("class ZdMap_b3_long_long extends"),
3662 "{generated}"
3663 );
3664 assert!(
3665 generated.contains("class ZdMap_long_long extends"),
3666 "{generated}"
3667 );
3668 assert!(
3669 generated.contains("private static final int ZD_BOUND = 3;"),
3670 "{generated}"
3671 );
3672 assert_eq!(generated.matches("class ZdEntry_long_long {").count(), 1);
3674 }
3675
3676 #[test]
3677 fn two_maps_of_one_shape_are_one_entry_class() {
3678 let generated = declarations("@final struct P { map<long, long> a; map<long, long> b; };");
3679 assert_eq!(generated.matches("class ZdEntry_long_long {").count(), 1);
3680 }
3681
3682 #[test]
3683 fn two_maps_of_one_java_shape_and_two_idl_shapes_are_two_entry_classes() {
3684 let generated =
3688 declarations("@final struct P { map<uint8, long> a; map<unsigned short, long> b; };");
3689 assert!(
3690 generated.contains("class ZdEntry_uint8_long {"),
3691 "{generated}"
3692 );
3693 assert!(
3694 generated.contains("class ZdEntry_unsigned_short_long {"),
3695 "{generated}"
3696 );
3697 }
3698
3699 #[test]
3700 fn an_optional_member_is_boxed_and_a_primitive_one_too() {
3701 let generated = declarations(
3702 "@mutable struct P {
3703 @optional long a; @optional string b; @optional long long c;
3704 };",
3705 );
3706 assert!(
3708 generated.contains("public java.lang.Integer a;"),
3709 "{generated}"
3710 );
3711 assert!(
3712 generated.contains("public java.lang.String b;"),
3713 "{generated}"
3714 );
3715 assert!(
3716 generated.contains("public java.lang.Long c;"),
3717 "{generated}"
3718 );
3719 }
3720
3721 #[test]
3722 fn a_user_annotation_is_an_interface_with_its_defaults_and_a_group() {
3723 let generated = declarations(
3727 "@annotation Note { string text default \"none\"; long level; };\n\
3728 @final struct P { long a; };",
3729 );
3730 assert!(
3731 generated.contains("public @interface Note {"),
3732 "{generated}"
3733 );
3734 assert!(
3735 generated.contains("java.lang.String text() default \"none\";"),
3736 "{generated}"
3737 );
3738 assert!(
3739 generated.contains("int level();"),
3740 "no default: {generated}"
3741 );
3742 assert!(
3743 generated.contains("public @interface NoteGroup {"),
3744 "{generated}"
3745 );
3746 assert!(generated.contains("Note[] value();"), "{generated}");
3747 }
3748
3749 #[test]
3750 fn an_applied_annotation_sits_on_the_java_element_and_several_go_through_the_group() {
3751 let generated = declarations(
3752 "@annotation Note { string text default \"none\"; long level; };\n\
3753 @Note(level = 3) @final struct P { @Note(text = \"a\", level = 1) \
3754 @Note(level = 2) long a; long b; };",
3755 );
3756 assert!(
3757 generated.contains("@ZdTypes.Note(level = 3)"),
3758 "{generated}"
3759 );
3760 assert!(
3761 generated.contains(
3762 "@ZdTypes.NoteGroup({@ZdTypes.Note(text = \"a\", level = 1), \
3763 @ZdTypes.Note(level = 2)})"
3764 ),
3765 "{generated}"
3766 );
3767 }
3768
3769 #[test]
3770 fn an_annotation_on_a_module_and_on_a_constant_reaches_the_java_element() {
3771 let generated = declarations(
3772 "@annotation Note { long level; };\n\
3773 @Note(level = 5) module M { @Note(level = 6) const long LIMIT = 3; \
3774 @final struct P { long a; }; };",
3775 );
3776 assert!(
3777 generated.contains("@ZdTypes.Note(level = 5)"),
3778 "{generated}"
3779 );
3780 assert!(
3781 generated.contains("@ZdTypes.Note(level = 6)"),
3782 "{generated}"
3783 );
3784 }
3785
3786 #[test]
3787 fn an_external_member_is_an_object_reference_and_a_primitive_one_is_boxed() {
3788 let generated = declarations(
3789 "@final struct P { @external short a; @external long b; @external string c; \
3790 @external sequence<long> d; @external boolean e; };",
3791 );
3792 assert!(
3793 generated.contains("public java.lang.Short a;"),
3794 "{generated}"
3795 );
3796 assert!(
3797 generated.contains("public java.lang.Integer b;"),
3798 "{generated}"
3799 );
3800 assert!(
3801 generated.contains("public java.lang.String c;"),
3802 "{generated}"
3803 );
3804 assert!(generated.contains("public int[] d;"), "{generated}");
3805 assert!(
3806 generated.contains("public java.lang.Boolean e;"),
3807 "{generated}"
3808 );
3809 }
3810
3811 #[test]
3812 fn a_union_is_a_class_with_a_discriminator_and_one_field_per_case() {
3813 let generated = declarations(
3816 "union Choice switch (long) {
3817 case 1: long whole;
3818 case 2: long other;
3819 default: double fraction;
3820 };
3821 @final struct P { Choice c; };",
3822 );
3823 assert!(generated.contains("public int _d;"), "{generated}");
3824 assert!(generated.contains("public int whole;"), "{generated}");
3825 assert!(generated.contains("public int other;"), "{generated}");
3826 assert!(generated.contains("public double fraction;"), "{generated}");
3827 assert!(
3828 generated.contains("public ZdTypes.Choice c;"),
3829 "{generated}"
3830 );
3831 }
3832
3833 #[test]
3834 fn an_enumeration_is_a_class_of_instances_and_a_member_of_it_is_one() {
3835 let generated = declarations(
3836 "enum Colour { RED, GREEN, BLUE };
3837 @final struct P { Colour c; };",
3838 );
3839 assert!(
3840 generated.contains("public static final int _RED = 0;"),
3841 "{generated}"
3842 );
3843 assert!(
3844 generated.contains("public static final Colour RED = new Colour(_RED);"),
3845 "{generated}"
3846 );
3847 assert!(
3848 generated.contains("public static final int _BLUE = 2;"),
3849 "{generated}"
3850 );
3851 assert!(generated.contains("public int value()"), "{generated}");
3852 assert!(
3853 generated.contains("public static Colour from_int(int value)"),
3854 "{generated}"
3855 );
3856 assert!(
3857 generated.contains("public ZdTypes.Colour c;"),
3858 "{generated}"
3859 );
3860 }
3861
3862 #[test]
3863 fn a_construct_with_no_java_data_form_is_named_and_not_left_out() {
3864 for (idl, construct) in [
3868 ("@final struct P { any a; long tail; };", "any"),
3869 ("@final struct P { Object o; long tail; };", "Object"),
3870 ] {
3871 let failure = render(idl, "test.idl").expect_err(construct);
3872 assert!(failure.contains(construct), "{failure}");
3873 assert!(failure.contains("Java"), "{failure}");
3874 }
3875 }
3876
3877 #[test]
3878 fn an_explicit_enumerator_value_is_written_out() {
3879 let generated = declarations("enum E { @value(-1) LOW, @value(70000) HIGH };");
3880 assert!(
3881 generated.contains("public static final int _LOW = -1;"),
3882 "{generated}"
3883 );
3884 assert!(
3885 generated.contains("public static final int _HIGH = 70000;"),
3886 "{generated}"
3887 );
3888 }
3889
3890 #[test]
3891 fn a_bitmask_takes_the_holder_its_bound_calls_for() {
3892 let narrow =
3893 declarations("@bit_bound(8) bitmask Small { @position(0) A, @position(7) B };");
3894 assert!(
3895 narrow.contains("public static final int B = 1 << 7;"),
3896 "{narrow}"
3897 );
3898 let wide = declarations("@bit_bound(64) bitmask Big { @position(0) A, @position(40) B };");
3899 assert!(
3900 wide.contains("public static final long B = 1L << 40;"),
3901 "{wide}"
3902 );
3903 }
3904
3905 #[test]
3906 fn a_bitset_is_a_class_of_one_field_per_bitfield() {
3907 let generated = declarations(
3908 "bitset Packed { bitfield<3> low; bitfield<5> high; };
3909 @final struct P { Packed value; };",
3910 );
3911 assert!(generated.contains("public int low;"), "{generated}");
3912 assert!(generated.contains("public int high;"), "{generated}");
3913 assert!(
3914 generated.contains("public ZdTypes.Packed value;"),
3915 "{generated}"
3916 );
3917 }
3918
3919 #[test]
3920 fn a_fixed_is_a_bigdecimal() {
3921 let generated = declarations(
3922 "typedef fixed<9,3> Money;
3923 @final struct P { Money amount; };",
3924 );
3925 assert!(
3926 generated.contains("public java.math.BigDecimal amount;"),
3927 "{generated}"
3928 );
3929 }
3930
3931 #[test]
3932 fn a_typedef_declares_nothing_and_says_so() {
3933 let generated = declarations(
3934 "typedef sequence<long> Row;
3935 @final struct P { Row r; };",
3936 );
3937 assert!(generated.contains("public int[] r;"), "{generated}");
3938 assert!(generated.contains("typedef int[] Row;"), "{generated}");
3939 }
3940
3941 #[test]
3942 fn a_module_is_a_nested_class_and_two_modules_do_not_collide() {
3943 let generated = declarations(
3944 "module a { @final struct P { long x; }; };
3945 module b { @final struct P { double y; }; };
3946 @final struct Q { a::P one; b::P two; };",
3947 );
3948 assert!(generated.contains("class a {"), "{generated}");
3949 assert!(generated.contains("class b {"), "{generated}");
3950 assert!(generated.contains("public ZdTypes.a.P one;"), "{generated}");
3951 assert!(generated.contains("public ZdTypes.b.P two;"), "{generated}");
3952 }
3953
3954 #[test]
3955 fn a_module_reopened_beside_a_nested_one_is_one_class() {
3956 let generated = declarations(
3960 "module common {
3961 module util { @final struct Location { double x; }; };
3962 @final struct Robot { ::common::util::Location where_; };
3963 };",
3964 );
3965 assert_eq!(
3966 generated.matches("class common {").count(),
3967 1,
3968 "{generated}"
3969 );
3970 assert!(
3971 generated.contains("public ZdTypes.common.util.Location where_;"),
3972 "{generated}"
3973 );
3974 }
3975
3976 #[test]
3977 fn an_identifier_that_is_a_java_keyword_is_escaped() {
3978 let generated = declarations(
3979 "module package {
3980 enum class { template, virtual, friend };
3981 @final struct type { long new; };
3982 };",
3983 );
3984 assert!(generated.contains("class package_ {"), "{generated}");
3985 assert!(generated.contains("class class_ {"), "{generated}");
3986 assert!(generated.contains("class type {"), "{generated}");
3987 assert!(generated.contains("public int new_;"), "{generated}");
3988 assert!(
3991 generated.contains("public static final int _template = 0;"),
3992 "{generated}"
3993 );
3994 }
3995
3996 #[test]
3997 fn a_name_that_would_capture_the_qualified_references_is_refused() {
3998 let failure = render("module java { @final struct P { long x; }; };", "test.idl")
3999 .expect_err("a module named java is refused");
4000 assert!(failure.contains("java"), "{failure}");
4001 }
4002
4003 #[test]
4004 fn a_declaration_named_after_the_outer_class_is_refused() {
4005 let failure = render("@final struct ZdTypes { long x; };", "test.idl")
4006 .expect_err("a type named after the outer class is refused");
4007 assert!(failure.contains("ZdTypes"), "{failure}");
4008 }
4009
4010 #[test]
4011 fn an_unevaluated_array_length_is_named_and_not_substituted() {
4012 let failure = render("@final struct P { long a[UNKNOWN]; };", "test.idl")
4015 .expect_err("an array whose length does not evaluate is refused");
4016 assert!(!failure.is_empty(), "{failure}");
4017 }
4018}