Skip to main content

zerodds_idl_java/
type_map.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3//! Mapping of IDL primitives to Java type strings.
4//!
5//! Follows OMG IDL4-Java-Mapping v1.0 §6 (Type Mapping). Java has no
6//! native unsigned integer types — the spec-conformant workaround:
7//!
8//! - `unsigned short` → Java `int` (not `short`, to cover the full
9//!   value range).
10//! - `unsigned long` → Java `long` (not `int`).
11//! - `unsigned long long` → Java `long` (with doc note; the default is
12//!   the `long` variant; `BigInteger` would be the full solution,
13//!   but is outside the current scope).
14//!
15//! `boolean` → `boolean`, `octet` → `byte`, `char`/`wchar` → `char`,
16//! `string`/`wstring` → `String`, `float`/`double` → `float`/`double`.
17
18use zerodds_idl::ast::{FloatingType, IntegerType, PrimitiveType};
19
20/// Mapping from a [`PrimitiveType`] to its Java type.
21#[must_use]
22pub fn primitive_to_java(p: PrimitiveType) -> &'static str {
23    match p {
24        PrimitiveType::Boolean => "boolean",
25        PrimitiveType::Octet => "byte",
26        PrimitiveType::Char => "char",
27        PrimitiveType::WideChar => "char",
28        PrimitiveType::Integer(i) => integer_to_java(i),
29        PrimitiveType::Floating(f) => floating_to_java(f),
30    }
31}
32
33/// Mapping from integer subtypes.
34///
35/// Spec-conformant unsigned workaround:
36/// - `unsigned short` → `int` (16-bit range does not fit in `short`).
37/// - `unsigned long` → `long` (32-bit range does not fit in `int`).
38/// - `unsigned long long` → `long` (with sign wrap; see doc).
39#[must_use]
40pub fn integer_to_java(i: IntegerType) -> &'static str {
41    match i {
42        IntegerType::Short | IntegerType::Int16 => "short",
43        IntegerType::Long | IntegerType::Int32 => "int",
44        IntegerType::LongLong | IntegerType::Int64 => "long",
45        // Unsigned workaround: use the next wider signed type.
46        IntegerType::UShort | IntegerType::UInt16 => "int",
47        IntegerType::ULong | IntegerType::UInt32 => "long",
48        // unsigned long long: Java has no 64-bit unsigned integer.
49        // Default: `long` (signed, with doc comment in the emitter).
50        IntegerType::ULongLong | IntegerType::UInt64 => "long",
51        IntegerType::Int8 => "byte",
52        IntegerType::UInt8 => "short",
53    }
54}
55
56/// Mapping from floating-point subtypes. `long double` is outside the current scope and is
57/// handled in `typespec_to_java` as [`crate::error::JavaGenError::UnsupportedConstruct`].
58#[must_use]
59pub fn floating_to_java(f: FloatingType) -> &'static str {
60    match f {
61        FloatingType::Float => "float",
62        FloatingType::Double => "double",
63        FloatingType::LongDouble => "double",
64    }
65}
66
67/// Returns `true` if an IDL integer type requires the Java unsigned workaround
68/// (doc note in the generator).
69#[must_use]
70pub fn is_unsigned(i: IntegerType) -> bool {
71    matches!(
72        i,
73        IntegerType::UShort
74            | IntegerType::UInt16
75            | IntegerType::ULong
76            | IntegerType::UInt32
77            | IntegerType::ULongLong
78            | IntegerType::UInt64
79            | IntegerType::UInt8
80    )
81}
82
83/// Boxed wrapper class for a primitive Java type, used for generics
84/// (e.g. `List<Integer>` instead of `List<int>`).
85#[must_use]
86pub fn primitive_to_java_boxed(p: PrimitiveType) -> &'static str {
87    match p {
88        PrimitiveType::Boolean => "Boolean",
89        PrimitiveType::Octet => "Byte",
90        PrimitiveType::Char => "Character",
91        PrimitiveType::WideChar => "Character",
92        PrimitiveType::Integer(i) => integer_to_java_boxed(i),
93        PrimitiveType::Floating(f) => floating_to_java_boxed(f),
94    }
95}
96
97/// Boxed variant for integer subtypes.
98#[must_use]
99pub fn integer_to_java_boxed(i: IntegerType) -> &'static str {
100    match i {
101        IntegerType::Short | IntegerType::Int16 => "Short",
102        IntegerType::Long | IntegerType::Int32 => "Integer",
103        IntegerType::LongLong | IntegerType::Int64 => "Long",
104        IntegerType::UShort | IntegerType::UInt16 => "Integer",
105        IntegerType::ULong | IntegerType::UInt32 => "Long",
106        IntegerType::ULongLong | IntegerType::UInt64 => "Long",
107        IntegerType::Int8 => "Byte",
108        IntegerType::UInt8 => "Short",
109    }
110}
111
112/// Boxed variant for floating-point subtypes.
113#[must_use]
114pub fn floating_to_java_boxed(f: FloatingType) -> &'static str {
115    match f {
116        FloatingType::Float => "Float",
117        FloatingType::Double => "Double",
118        FloatingType::LongDouble => "Double",
119    }
120}
121
122#[cfg(test)]
123mod tests {
124    #![allow(clippy::expect_used, clippy::panic)]
125    use super::*;
126
127    #[test]
128    fn primitive_boolean() {
129        assert_eq!(primitive_to_java(PrimitiveType::Boolean), "boolean");
130    }
131
132    #[test]
133    fn primitive_octet() {
134        assert_eq!(primitive_to_java(PrimitiveType::Octet), "byte");
135    }
136
137    #[test]
138    fn primitive_char() {
139        assert_eq!(primitive_to_java(PrimitiveType::Char), "char");
140    }
141
142    #[test]
143    fn primitive_wchar() {
144        assert_eq!(primitive_to_java(PrimitiveType::WideChar), "char");
145    }
146
147    #[test]
148    fn integer_short_signed() {
149        assert_eq!(integer_to_java(IntegerType::Short), "short");
150    }
151
152    #[test]
153    fn integer_long_signed() {
154        assert_eq!(integer_to_java(IntegerType::Long), "int");
155    }
156
157    #[test]
158    fn integer_long_long_signed() {
159        assert_eq!(integer_to_java(IntegerType::LongLong), "long");
160    }
161
162    #[test]
163    fn integer_unsigned_short_widens_to_int() {
164        // Spec §6.2: unsigned short → int (16-bit value range does not
165        // fit in Java `short`, since it is signed).
166        assert_eq!(integer_to_java(IntegerType::UShort), "int");
167    }
168
169    #[test]
170    fn integer_unsigned_long_widens_to_long() {
171        assert_eq!(integer_to_java(IntegerType::ULong), "long");
172    }
173
174    #[test]
175    fn integer_unsigned_long_long_keeps_long() {
176        // Default workaround: `long` (sign wrap accepted,
177        // stretch goal: BigInteger).
178        assert_eq!(integer_to_java(IntegerType::ULongLong), "long");
179    }
180
181    #[test]
182    fn integer_explicit_widths() {
183        assert_eq!(integer_to_java(IntegerType::Int8), "byte");
184        assert_eq!(integer_to_java(IntegerType::UInt8), "short");
185        assert_eq!(integer_to_java(IntegerType::Int16), "short");
186        assert_eq!(integer_to_java(IntegerType::UInt16), "int");
187        assert_eq!(integer_to_java(IntegerType::Int32), "int");
188        assert_eq!(integer_to_java(IntegerType::UInt32), "long");
189        assert_eq!(integer_to_java(IntegerType::Int64), "long");
190        assert_eq!(integer_to_java(IntegerType::UInt64), "long");
191    }
192
193    #[test]
194    fn floating_float_double() {
195        assert_eq!(floating_to_java(FloatingType::Float), "float");
196        assert_eq!(floating_to_java(FloatingType::Double), "double");
197    }
198
199    #[test]
200    fn unsigned_marker_is_correct() {
201        assert!(!is_unsigned(IntegerType::Short));
202        assert!(is_unsigned(IntegerType::UShort));
203        assert!(!is_unsigned(IntegerType::Long));
204        assert!(is_unsigned(IntegerType::ULong));
205        assert!(is_unsigned(IntegerType::ULongLong));
206    }
207
208    #[test]
209    fn boxed_long_is_long() {
210        assert_eq!(integer_to_java_boxed(IntegerType::LongLong), "Long");
211    }
212
213    #[test]
214    fn boxed_int_is_integer() {
215        assert_eq!(integer_to_java_boxed(IntegerType::Long), "Integer");
216    }
217
218    #[test]
219    fn boxed_unsigned_long_is_long() {
220        assert_eq!(integer_to_java_boxed(IntegerType::ULong), "Long");
221    }
222
223    #[test]
224    fn boxed_primitive_boolean_is_capital_boolean() {
225        assert_eq!(primitive_to_java_boxed(PrimitiveType::Boolean), "Boolean");
226    }
227
228    #[test]
229    fn boxed_octet_is_byte_capital() {
230        assert_eq!(primitive_to_java_boxed(PrimitiveType::Octet), "Byte");
231    }
232
233    #[test]
234    fn boxed_floats() {
235        assert_eq!(floating_to_java_boxed(FloatingType::Float), "Float");
236        assert_eq!(floating_to_java_boxed(FloatingType::Double), "Double");
237    }
238}