zerodds_lang_rules/emit/java.rs
1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3//! The Java backend of [`CodecTarget`].
4//!
5//! The same walk as the C column, spelled in Java. What that spelling
6//! settles, and why each of these is the backend's answer rather than
7//! the walk's:
8//!
9//! - **No pointers.** A codec takes the sample as a reference, so
10//! [`CodecTarget::member`] is `v.x` and [`CodecTarget::whole`] is `v`.
11//! [`CodecTarget::handoff`] hands the value either way, because there
12//! is no address to take and no array to decay.
13//! - **No free functions.** `runtime/c` exports `zdw_write_u32(w, x)`
14//! and `runtime/java` has `w.writeU32(x)`; the walk asks for
15//! `write_u32` and each backend spells it. A codec for a type is a
16//! `static` method rather than a function, for the same reason.
17//! - **No forward declarations.** A method may call one declared below
18//! it, so [`CodecTarget::routine`] answers `None` to a prototype and
19//! the walk emits only bodies. That is the whole of what C's
20//! declaration order costs, expressed once.
21//! - **No unsigned types.** A counter is `int`; the runtime's
22//! `writeU32` takes what fits and the widening is its business, not
23//! the generated code's.
24//!
25//! What is *not* different is the wire. `runtime/java` implements
26//! DDS-XTypes 1.3 §7.4 from the clause, the same as `runtime/c` does,
27//! and neither calls the other. Where the two columns disagree on an
28//! octet, one of them has misread the specification — which is the only
29//! reason to have a second column at all.
30
31use super::{BitPatternSource, CodecTarget, Constant, Handle, Handoff, Routine, Scalar};
32use crate::adapter::java::{JavaMapping, assign, assignment, mapping, type_tag};
33use crate::encode::EncodeError;
34use zerodds_ast::{Ast, MemberNode, TypeNode};
35use zerodds_ast_core::Id;
36use zerodds_lang_cst::common::{Doc, Ident, Literal, Path};
37use zerodds_lang_cst::java;
38
39/// The Java backend.
40#[derive(Debug, Clone, Copy, PartialEq, Eq)]
41pub struct Java;
42
43impl Java {
44 /// `write_u32` as `writeU32`.
45 ///
46 /// The runtime's methods are named by Java's convention and the walk
47 /// asks in the wire's, so one of the two has to convert. Doing it
48 /// here means a new operation is added to the walk under one name.
49 fn camel(method: &str) -> String {
50 let mut out = String::new();
51 let mut rising = false;
52 for ch in method.chars() {
53 if ch == '_' {
54 rising = true;
55 continue;
56 }
57 if rising {
58 out.extend(ch.to_uppercase());
59 rising = false;
60 } else {
61 out.push(ch);
62 }
63 }
64 out
65 }
66
67 fn path(name: &str) -> Path {
68 Path::new(&name.split('.').collect::<Vec<_>>())
69 }
70
71 /// The reader of a map: `var v = Map.noEntries();`, the entries read
72 /// into it, and `return Map.of(v);`.
73 fn map_reading_body(class: &Path, body: java::Block) -> java::Block {
74 let entries = entries_type(class);
75 let on_class = |name: &str, args: Vec<java::Expr>| java::Expr::MethodCall {
76 receiver: Some(Box::new(java::Expr::Path(class.clone()))),
77 name: Ident::new(name),
78 args,
79 };
80 let mut statements = vec![java::Statement::Declare {
81 final_: false,
82 type_: entries,
83 name: Ident::new("v"),
84 value: Some(on_class("noEntries", Vec::new())),
85 }];
86 statements.extend(body.statements);
87 statements.push(java::Statement::Return(Some(on_class(
88 "of",
89 vec![Self::whole("v")],
90 ))));
91 java::Block { statements }
92 }
93
94 /// A reader's body, with the sample it answers with around it.
95 ///
96 /// `<T> v = zd_def_<T>(); … return v;`. Two things are settled
97 /// here and neither is a spelling:
98 ///
99 /// - **Where the sample comes from.** Table 9's value, through the
100 /// generated defaulter, and not `new T()` or `null`. Every
101 /// position the stream does not carry is then already what
102 /// DDS-XTypes 1.3 Table 9 says it is, without the walk having to
103 /// reach each one; a `null` there would be a `NullPointerException`
104 /// at the dump instead of a member with a defined value, and
105 /// `new T()` would be a second answer to Table 9 beside
106 /// `default_java.rs`.
107 /// - **What the name is.** The defaulter and the decoder are keyed
108 /// the same way — [`crate::emit::decode::decoder_name`] and
109 /// [`crate::default_java::defaulter_name`] differ only in the
110 /// four letters after `zd_` — so the one is the other with the
111 /// verb changed. A name that does not resolve is a `javac` error
112 /// naming the method, which is where a mismatch shows.
113 fn reading_body(name: &str, sample: java::Type, body: java::Block) -> java::Block {
114 let defaulter = crate::default_java::defaulter_from_decoder(name);
115 let mut statements = vec![java::Statement::Declare {
116 final_: false,
117 type_: sample,
118 name: Ident::new("v"),
119 value: Some(Self::call(&defaulter, Vec::new())),
120 }];
121 statements.extend(body.statements);
122 statements.push(java::Statement::Return(Some(Self::whole("v"))));
123 java::Block { statements }
124 }
125}
126
127/// The reader of a map works on its entries, so what the walk returns and what it
128/// falls back to are entries: `return v;` becomes `return Map.of(v);` and the default
129/// `v = zd_def_T();` (a map, as everywhere else) becomes `v = zd_def_T().entries();`.
130///
131/// zerodds-lint: recursion-depth 8 (the nesting of the statements of one codec routine; each
132/// call descends into one block of the statement it is given)
133fn map_returns(block: &mut java::Block, class: &Path) {
134 let is_v = |expr: &java::Expr| {
135 matches!(expr, java::Expr::Path(path)
136 if path.segments().len() == 1 && path.segments()[0].as_str() == "v")
137 };
138 for statement in &mut block.statements {
139 match statement {
140 java::Statement::Return(Some(value)) if is_v(value) => {
141 let entries = value.clone();
142 *value = java::Expr::MethodCall {
143 receiver: Some(Box::new(java::Expr::Path(class.clone()))),
144 name: Ident::new("of"),
145 args: vec![entries],
146 };
147 }
148 java::Statement::Expr(java::Expr::Binary { op: "=", lhs, rhs })
149 if is_v(lhs)
150 && matches!(rhs.as_ref(), java::Expr::MethodCall { receiver: None, name, .. }
151 if name.as_str().starts_with("zd_def_")) =>
152 {
153 let call = (**rhs).clone();
154 **rhs = java::Expr::MethodCall {
155 receiver: Some(Box::new(call)),
156 name: Ident::new("entries"),
157 args: Vec::new(),
158 };
159 }
160 java::Statement::If {
161 then, otherwise, ..
162 } => {
163 map_returns(then, class);
164 if let Some(otherwise) = otherwise {
165 map_returns(otherwise, class);
166 }
167 }
168 java::Statement::ForEach { body, .. }
169 | java::Statement::For { body, .. }
170 | java::Statement::While { body, .. } => map_returns(body, class),
171 java::Statement::Switch { cases, .. } => {
172 for case in cases {
173 map_returns(&mut case.body, class);
174 }
175 }
176 _ => {}
177 }
178 }
179}
180
181/// The class path of a sample that is a generated map class
182/// (`ZdTypes.ZdMap…`).
183/// `ZdEntry_<key>_<value>[]`, the type of `Map.entries()` and `Map.noEntries()`, next
184/// to the map class `class`. Spelled out: the generated code compiles for Java 8,
185/// where `var` is a restricted type name that javac warns about.
186fn entries_type(class: &Path) -> java::Type {
187 let segments: Vec<&str> = class.segments().iter().map(Ident::as_str).collect();
188 let (last, owner) = segments.split_last().unwrap_or((&"", &[]));
189 let entry =
190 crate::adapter::java::entry_class_of_map_class(last).unwrap_or_else(|| (*last).to_string());
191 let mut path: Vec<&str> = owner.to_vec();
192 path.push(&entry);
193 java::Type::Array(Box::new(java::Type::named(Path::new(&path))))
194}
195
196fn map_class_of(sample: &java::Type) -> Option<Path> {
197 let java::Type::Named { path, args } = sample else {
198 return None;
199 };
200 let last = path.segments().last()?;
201 (args.is_empty() && last.as_str().starts_with(crate::adapter::java::MAP_PREFIX))
202 .then(|| path.clone())
203}
204
205impl CodecTarget for Java {
206 type Expr = java::Expr;
207 type Stmt = java::Statement;
208 type Block = java::Block;
209 type Type = java::Type;
210 type Decl = java::Member;
211
212 // -- types --------------------------------------------------------
213
214 /// An unsigned local goes in the next signed type up, and that is
215 /// not a stylistic choice.
216 ///
217 /// Java has no unsigned types. A `uint8` local declared `byte` holds
218 /// 200 as −56, and `Writer.writeU8` takes an `int` because it means
219 /// *a number in [0, 255]* — a caller handing it −56 has already lost
220 /// the value, before any octet is written. So `uint8` and `uint16`
221 /// are `int` and `uint32` is `long`.
222 ///
223 /// `uint64` is the exception: a `long` holding a **pattern** rather
224 /// than a number, because there is no wider type.
225 /// `writeU64(-1L)` writes `ffffffffffffffff`, which is
226 /// 18446744073709551615.
227 ///
228 /// The same decision `adapter/java.rs` makes for a member's type and
229 /// `runtime/java/Writer.java` for its parameters. Three places have
230 /// to agree, and they are named here because a fourth reading is the
231 /// one that would quietly disagree.
232 fn scalar(kind: Scalar) -> Self::Type {
233 java::Type::Primitive(match kind {
234 Scalar::Bool => java::Primitive::Boolean,
235 Scalar::Char => java::Primitive::Char,
236 Scalar::Int8 => java::Primitive::Byte,
237 Scalar::Int16 => java::Primitive::Short,
238 Scalar::Index | Scalar::Size | Scalar::Int32 | Scalar::UInt8 | Scalar::UInt16 => {
239 java::Primitive::Int
240 }
241 Scalar::Int64 | Scalar::UInt32 | Scalar::UInt64 => java::Primitive::Long,
242 Scalar::Float => java::Primitive::Float,
243 Scalar::Double => java::Primitive::Double,
244 // binary128 has no Java primitive; `runtime/java` carries it
245 // as a pair of longs behind `Float128`.
246 Scalar::LongDouble => {
247 return java::Type::named(Path::new(&["Float128"]));
248 }
249 })
250 }
251
252 fn named(name: &str) -> Self::Type {
253 java::Type::named(Self::path(name))
254 }
255
256 fn nothing() -> Self::Type {
257 java::Type::Void
258 }
259
260 /// `runtime/java` publishes each handle as a `public static final`
261 /// class nested in `Writer`, so the name a generated file writes is
262 /// qualified. Unqualified would compile only where the file happened
263 /// to import the nested class by name.
264 fn handle(kind: Handle) -> Self::Type {
265 java::Type::named(Self::path(match kind {
266 Handle::DHeader => "Writer.DHeader",
267 Handle::EmHeader => "Writer.EmHeader",
268 Handle::Pid => "Writer.Pid",
269 // Not nested in either: the behaviour is a policy the
270 // caller chooses, and `runtime/java` publishes it beside
271 // `WcharWidth` and the rest. The probe imports it, so the
272 // simple name is what the generated file writes.
273 Handle::TryConstruct => "TryConstruct",
274 Handle::ReaderMark => "Reader.Mark",
275 Handle::PidRead => "Reader.PidRead",
276 Handle::EmHeaderRead => "Reader.EmHeaderRead",
277 }))
278 }
279
280 /// A Java `char` is already one code unit and widens to the `int`
281 /// the runtime takes. The narrowing a `char` needs is the runtime's,
282 /// which keeps the low byte — the same octet C reaches by
283 /// truncating one step earlier.
284 /// No cast: every holder a code point lives in here — `char`,
285 /// `byte`, `short` — widens to the runtime's `int` parameter
286 /// implicitly (JLS 5.1.2), and the explicit `(int)` this used to
287 /// write is the one thing `-Xlint:cast -Werror` rejects on a javac
288 /// that flags redundant casts — 68 tao/rti cells stopped compiling
289 /// on the Linux test host the day its JDK started to.
290 fn code_point(value: Self::Expr, _narrow: bool) -> Self::Expr {
291 value
292 }
293
294 /// `Long.compareUnsigned` for the eight-octet width, whose value
295 /// lives in a `long` as its bit pattern and whose own `<` is
296 /// signed. The narrower widths live zero-extended in a wider
297 /// signed type, where the inherited `<` is already the unsigned
298 /// order.
299 fn unsigned_less(octets: u8, left: Self::Expr, right: Self::Expr) -> Self::Expr {
300 if octets == 1 {
301 // An `octet` is a `byte` and a `uint8` an `int`; the low eight bits are the number
302 // for both.
303 let low = |value: java::Expr| {
304 java::Expr::Paren(Box::new(Self::binary("&", value, Self::int(0xFF))))
305 };
306 return Self::binary("<", low(left), low(right));
307 }
308 if octets < 8 {
309 return Self::binary("<", left, right);
310 }
311 Self::binary(
312 "<",
313 java::Expr::MethodCall {
314 receiver: Some(Box::new(java::Expr::Path(Path::new(&["Long"])))),
315 name: Ident::new("compareUnsigned"),
316 args: vec![left, right],
317 },
318 Self::int(0),
319 )
320 }
321
322 /// `false` before `true`, spelled as the conjunction — Java's `<`
323 /// does not take two booleans. In the house style of `unary`'s `!`:
324 /// the left key is out as `== false`.
325 fn bool_less(left: Self::Expr, right: Self::Expr) -> Self::Expr {
326 Self::binary(
327 "&&",
328 java::Expr::Paren(Box::new(java::Expr::Binary {
329 op: "==",
330 lhs: Box::new(left),
331 rhs: Box::new(java::Expr::Literal(Literal::Bool(false))),
332 })),
333 right,
334 )
335 }
336
337 /// Four statics on the runtime's writer, because none of the forms
338 /// has a Java expression: a `long` cast of a negative `int`
339 /// sign-extends where the pattern needs a mask, a `boolean` has no
340 /// conversion to an integer at all, and a float's bits are
341 /// `Float.floatToRawIntBits`'s to read. The walk hands the integer
342 /// form a `long` already — its `Int64` cast — so `bitPatternInt`
343 /// only masks.
344 fn bit_pattern(source: BitPatternSource, value: Self::Expr) -> Self::Expr {
345 let call = |name: &str, args: Vec<java::Expr>| java::Expr::MethodCall {
346 receiver: Some(Box::new(java::Expr::Path(Path::new(&["Writer"])))),
347 name: Ident::new(name),
348 args,
349 };
350 match source {
351 BitPatternSource::Int { octets } => call(
352 "bitPatternInt",
353 vec![value, java::Expr::Literal(Literal::Int(octets.to_string()))],
354 ),
355 BitPatternSource::Bool => call("bitPatternBool", vec![value]),
356 BitPatternSource::Float32 => call("bitPatternF32", vec![value]),
357 BitPatternSource::Float64 => call("bitPatternF64", vec![value]),
358 }
359 }
360
361 /// `runtime/java` publishes these as enum constants, one type per
362 /// policy, so each name carries its own.
363 fn constant(which: Constant) -> Self::Expr {
364 java::Expr::Path(Self::path(match which {
365 Constant::WcharWidth2 => "WcharWidth.OCTETS_2",
366 Constant::WcharWidth4 => "WcharWidth.OCTETS_4",
367 Constant::WcharFixed => "WcharFraming.FIXED",
368 Constant::WcharGiopLengthPrefixed => "WcharFraming.GIOP_LENGTH_PREFIXED",
369 Constant::WstringUnitsStream => "WstringUnitOrder.STREAM",
370 Constant::WstringUnitsBigEndianAlways => "WstringUnitOrder.BIG_ENDIAN_ALWAYS",
371 Constant::WstringUnitsByteOrderMark => "WstringUnitOrder.BYTE_ORDER_MARK",
372 Constant::WstringUnitsHostUnswapped => "WstringUnitOrder.HOST_UNSWAPPED",
373 Constant::WstringSpecification => "WstringLengthPolicy.SPECIFICATION",
374 Constant::WstringCharacterCount => "WstringLengthPolicy.CHARACTER_COUNT",
375 Constant::WstringCharacterCountTerminated => {
376 "WstringLengthPolicy.CHARACTER_COUNT_TERMINATED"
377 }
378 Constant::WstringSpecificationWideUnits => {
379 "WstringLengthPolicy.SPECIFICATION_WIDE_UNITS"
380 }
381 Constant::WstringRosTypeSupport => "WstringLengthPolicy.ROS_TYPE_SUPPORT",
382 Constant::WstringOctetsTerminated2 => "WstringLengthPolicy.OCTETS_TERMINATED_2",
383 Constant::WstringOctetsTerminated4 => "WstringLengthPolicy.OCTETS_TERMINATED_4",
384 Constant::WstringCharactersTerminated2 => "WstringLengthPolicy.CHARACTERS_TERMINATED_2",
385 Constant::FixedDeclared => "FixedDigitCount.DECLARED",
386 Constant::FixedValuePrecision => "FixedDigitCount.VALUE_PRECISION",
387 Constant::FixedValueStringDigits => "FixedDigitCount.VALUE_STRING_DIGITS",
388 Constant::ParameterMember => "ParameterRole.MEMBER",
389 Constant::ParameterOptional => "ParameterRole.OPTIONAL",
390 Constant::TryConstructDiscard => "TryConstruct.DISCARD",
391 Constant::TryConstructTrim => "TryConstruct.TRIM",
392 Constant::TryConstructUseDefault => "TryConstruct.USE_DEFAULT",
393 Constant::StatusOk => "Reader.Status.OK",
394 Constant::StatusDiscarded => "Reader.Status.DISCARDED",
395 Constant::StatusImpossibleLength => "Reader.Status.IMPOSSIBLE_LENGTH",
396 Constant::StatusOutOfRange => "Reader.Status.OUT_OF_RANGE",
397 Constant::StatusTruncated => "Reader.Status.TRUNCATED",
398 }))
399 }
400
401 /// The materialiser's answer, not a second reading of it.
402 ///
403 /// `adapter/java.rs::unsigned_holder` decides what a `@bit_bound`
404 /// set's field is declared as, and a codec that assembled the value
405 /// in a different width would either narrow it or need a cast the
406 /// generated source does not have.
407 fn unsigned_holder(bits: u16) -> Self::Type {
408 crate::adapter::java::unsigned_holder(bits)
409 }
410
411 /// Java has no free functions; the runtime class's own static
412 /// method stands in.
413 ///
414 /// Which class is not a guess: C exports one namespace and Java has
415 /// two, so the operation says which side of the wire it belongs to.
416 /// A name this table does not know is written on `Reader` and
417 /// `javac` names it — a compile error that stops the probe, rather
418 /// than a call that goes to the wrong class and cannot.
419 fn runtime_static(method: &str, args: Vec<Self::Expr>) -> Self::Expr {
420 let receiver = match method {
421 "no_dheader" => "Writer",
422 _ => "Reader",
423 };
424 java::Expr::MethodCall {
425 receiver: Some(Box::new(java::Expr::Path(Path::new(&[receiver])))),
426 name: Ident::new(&Self::camel(method)),
427 args,
428 }
429 }
430
431 // -- expressions --------------------------------------------------
432
433 fn name(text: &str) -> Self::Expr {
434 java::Expr::Path(Self::path(text))
435 }
436
437 fn int(value: i128) -> Self::Expr {
438 java::Expr::Literal(Literal::int(value))
439 }
440
441 fn literal(value: Literal) -> Self::Expr {
442 java::Expr::Literal(value)
443 }
444
445 /// A free function is a `static` method on the generated class, so
446 /// an unqualified call reaches it from another method of the same
447 /// class without naming the class.
448 /// `strcmp` is the walk's name for a three-way code-unit
449 /// comparison; here it is `a.compareTo(b)`, which compares UTF-16
450 /// code units. For the corpus's keys — all inside the BMP — that
451 /// is the same order as UTF-8 octets; a supplementary code point
452 /// mixed with U+E000..U+FFFF would part the two, and no corpus
453 /// case declares such a key.
454 fn call(callee: &str, args: Vec<Self::Expr>) -> Self::Expr {
455 if callee == "strcmp" {
456 let mut arguments = args.into_iter();
457 if let (Some(left), Some(right)) = (arguments.next(), arguments.next()) {
458 return java::Expr::MethodCall {
459 receiver: Some(Box::new(left)),
460 name: Ident::new("compareTo"),
461 args: vec![right],
462 };
463 }
464 return java::Expr::MethodCall {
465 receiver: None,
466 name: Ident::new("strcmp"),
467 args: Vec::new(),
468 };
469 }
470 java::Expr::MethodCall {
471 receiver: None,
472 name: Ident::new(callee),
473 args,
474 }
475 }
476
477 /// A `fixed<P,S>` is a `java.math.BigDecimal` (CORBA 3.3 part 3 §1.5.6) and the runtime
478 /// writes and reads its decimal text: the value goes in as `toPlainString()` and comes
479 /// back through `new BigDecimal(text)`.
480 fn runtime(receiver: &str, method: &str, mut args: Vec<Self::Expr>) -> Self::Expr {
481 if method == "write_fixed" && !args.is_empty() {
482 let value = args.remove(0);
483 args.insert(
484 0,
485 java::Expr::MethodCall {
486 receiver: Some(Box::new(value)),
487 name: Ident::new("toPlainString"),
488 args: Vec::new(),
489 },
490 );
491 }
492 let call = java::Expr::MethodCall {
493 receiver: Some(Box::new(java::Expr::Path(Self::path(receiver)))),
494 name: Ident::new(&Self::camel(method)),
495 args,
496 };
497 if method == "read_fixed" {
498 return java::Expr::New {
499 type_: Box::new(java::Type::named(Path::new(&[
500 "java",
501 "math",
502 "BigDecimal",
503 ]))),
504 args: vec![call],
505 };
506 }
507 call
508 }
509
510 /// `runtime/java` carries the three HDDS axes: `beginPackedElement`
511 /// and `finishPackedElement` on the writer and the reader, the
512 /// DHEADER pair for a sequence element's own length, and
513 /// `writeWstringUtf8Octets` / `readWstringUtf8Octets`.
514 fn carries_hdds_axes() -> bool {
515 true
516 }
517
518 /// `v.x`, or under [`JavaMapping::Psm`] `v.getX()`, which
519 /// [`crate::adapter::java::assign`] turns into `v.setX(value)` where
520 /// the walk writes the member.
521 fn member(base: &str, field: &str) -> Self::Expr {
522 if mapping() == JavaMapping::Psm {
523 return crate::adapter::java::accessor(java::Expr::Path(Self::path(base)), field);
524 }
525 java::Expr::Field {
526 value: Box::new(java::Expr::Path(Self::path(base))),
527 name: Ident::new(field),
528 }
529 }
530
531 /// The sample itself, with no dereference to write.
532 fn whole(base: &str) -> Self::Expr {
533 java::Expr::Path(Self::path(base))
534 }
535
536 /// `L` on anything that will not fit an `int`, and the pattern on
537 /// anything that will not fit a `long`.
538 ///
539 /// A `case 16045690984833335998:` over an `unsigned long long` is
540 /// past `Long.MAX_VALUE` and Java has no wider integer, so the label
541 /// is written as the signed decimal with the same sixty-four bits —
542 /// which is how `adapter/java.rs` holds the value it is compared
543 /// against and how `Writer.writeU64` takes it. Anything else does
544 /// not compile, which is the better failure but still a failure.
545 fn wide_int(value: i128) -> Self::Expr {
546 let text = if value > i128::from(i64::MAX) {
547 #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
548 let pattern = value as u64 as i64;
549 format!("{pattern}L")
550 } else if value > i128::from(i32::MAX) || value < i128::from(i32::MIN) {
551 format!("{value}L")
552 } else {
553 format!("{value}")
554 };
555 java::Expr::Literal(Literal::Int(text))
556 }
557
558 /// A count, with no suffix: Java has no unsigned literal, and `5u`
559 /// is not a token it has at all.
560 ///
561 /// Wide enough to hold itself, through [`CodecTarget::wide_int`]: a
562 /// `sequence<long, 3000000000>` bound is past `int` and the `L` is
563 /// what keeps the comparison against a `long` count honest.
564 fn unsigned_int(value: i128) -> Self::Expr {
565 Self::wide_int(value)
566 }
567
568 /// A 64-bit pattern in a `long`, which is where Java keeps one.
569 ///
570 /// `writeU64(-1L)` writes `ffffffffffffffff`; the same reading in
571 /// reverse is what a mask above `Long.MAX_VALUE` is here.
572 fn wide_unsigned(value: u64) -> Self::Expr {
573 #[allow(clippy::cast_possible_wrap)]
574 let pattern = value as i64;
575 java::Expr::Literal(Literal::Int(format!("{pattern}L")))
576 }
577
578 /// A signed 64-bit constant. Always suffixed: an enumerator's
579 /// pattern is compared against a `long` and an `int` literal there
580 /// would be widened after the comparison's type was chosen.
581 fn wide_signed(value: i128) -> Self::Expr {
582 #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
583 let pattern = if value > i128::from(i64::MAX) {
584 value as u64 as i64
585 } else {
586 value as i64
587 };
588 java::Expr::Literal(Literal::Int(format!("{pattern}L")))
589 }
590
591 fn field_of(value: Self::Expr, field: &str) -> Self::Expr {
592 java::Expr::Field {
593 value: Box::new(value),
594 name: Ident::new(field),
595 }
596 }
597
598 fn index(value: Self::Expr, at: Self::Expr) -> Self::Expr {
599 crate::adapter::java::index(value, at)
600 }
601
602 /// `.length`, or the bound itself where the materialiser fixed one.
603 ///
604 /// Both a `sequence<T>` and an IDL array are a Java `T[]` here —
605 /// `adapter/java.rs` says so, and `docs/language-limits.md` gives
606 /// the reason a `map` is an array of a generated pair rather than a
607 /// `java.util.Map`. So the length is a field and never `.size()`;
608 /// a backend that asked a list for its size would generate source
609 /// against a type the materialiser does not emit.
610 fn count_of(base: &str, fixed: Option<u64>) -> Self::Expr {
611 match fixed {
612 Some(bound) => java::Expr::Literal(Literal::int(i128::from(bound))),
613 // A sequence under `JavaMapping::Psm` may be a `Collection`
614 // and has no `.length`; `Sequences.count` is the length of
615 // either, chosen by the type of its argument.
616 None if mapping() == JavaMapping::Psm => {
617 crate::adapter::java::sequences_count(Self::whole(base))
618 }
619 None => java::Expr::Field {
620 value: Box::new(Self::whole(base)),
621 name: Ident::new("length"),
622 },
623 }
624 }
625
626 fn binary(op: &'static str, lhs: Self::Expr, rhs: Self::Expr) -> Self::Expr {
627 java::Expr::Binary {
628 op,
629 lhs: Box::new(lhs),
630 rhs: Box::new(rhs),
631 }
632 }
633
634 /// Java has no `Unary` node, and the three operators the walk uses
635 /// — `!`, `~` and `-` — are written as a binary against the
636 /// identity that leaves them alone.
637 ///
638 /// `~x` is `-1L ^ x` and not `0 - x`: the complement is what a
639 /// bitmask's undeclared-bit mask is built with, and it is taken in
640 /// sixty-four bits because that is the width the mask has.
641 fn unary(op: &'static str, value: Self::Expr) -> Self::Expr {
642 match op {
643 "!" => java::Expr::Binary {
644 op: "==",
645 lhs: Box::new(value),
646 rhs: Box::new(java::Expr::Literal(Literal::Bool(false))),
647 },
648 "~" => java::Expr::Paren(Box::new(java::Expr::Binary {
649 op: "^",
650 lhs: Box::new(java::Expr::Literal(Literal::Int("-1L".to_string()))),
651 rhs: Box::new(value),
652 })),
653 _ => java::Expr::Binary {
654 op,
655 lhs: Box::new(java::Expr::Literal(Literal::int(0))),
656 rhs: Box::new(value),
657 },
658 }
659 }
660
661 /// A bracket, written because the walk asked for one.
662 ///
663 /// The renderer adds none of its own, and Java's precedence is not
664 /// C's where it matters most here: `&` binds *looser* than `!=`, so
665 /// `bits & mask != 0` is a type error rather than the comparison
666 /// the walk built, and looser than `^`, where it is a different
667 /// number with nothing to say so.
668 fn paren(value: Self::Expr) -> Self::Expr {
669 java::Expr::Paren(Box::new(value))
670 }
671
672 /// A widening to `long` is written by the walk and not needed here.
673 ///
674 /// C casts a bit set's accumulator to `uint64_t` because its holder
675 /// may be narrower; Java widens an integer to `long` on its own, and
676 /// the cast is a `[cast]` warning — which `-Werror` turns into a
677 /// probe that does not compile. Every cast the walk asks for with
678 /// `long` as its target has an integer source, so dropping it is
679 /// safe; a narrowing to `int` is not, and is kept.
680 fn cast(type_: Self::Type, value: Self::Expr) -> Self::Expr {
681 if type_ == java::Type::Primitive(java::Primitive::Long) {
682 return value;
683 }
684 // The walk's `signed char` — the C spelling of a map key's
685 // signed order — is `byte` here: the cast keeps the low octet
686 // and reads it in two's complement, which is the order the
687 // corpus was recorded under.
688 if type_ == java::Type::named(Self::path("signed char")) {
689 return java::Expr::Cast {
690 type_: Box::new(java::Type::Primitive(java::Primitive::Byte)),
691 value: Box::new(value),
692 };
693 }
694 // A `boolean` discriminator's labels arrive as 0 and 1 — the
695 // integers the IDL evaluator gives — and Java has no conversion
696 // from either. The label is the value it stands for.
697 if type_ == java::Type::Primitive(java::Primitive::Boolean) {
698 if let java::Expr::Literal(Literal::Int(text)) = &value {
699 return java::Expr::Literal(Literal::Bool(text.trim_end_matches('L') != "0"));
700 }
701 }
702 // `(int) 1` on a union label is redundant and `[cast]` says so.
703 // An `int` literal is already an `int`; one carrying `L` is not
704 // and keeps its cast.
705 if type_ == java::Type::Primitive(java::Primitive::Int)
706 && matches!(&value, java::Expr::Literal(Literal::Int(text)) if !text.ends_with('L'))
707 {
708 return value;
709 }
710 java::Expr::Cast {
711 type_: Box::new(type_),
712 value: Box::new(value),
713 }
714 }
715
716 /// Nothing to take an address of and no array to decay: both forms
717 /// hand the value.
718 fn handoff(value: Self::Expr, _form: Handoff) -> Self::Expr {
719 value
720 }
721
722 // -- statements ---------------------------------------------------
723
724 fn declare(type_: Self::Type, name: &str, value: Option<Self::Expr>) -> Self::Stmt {
725 java::Statement::Declare {
726 final_: false,
727 type_,
728 name: Ident::new(name),
729 value,
730 }
731 }
732
733 /// An expression statement; an assignment to a member or to an
734 /// element of a sequence goes through [`assign`].
735 fn perform(value: Self::Expr) -> Self::Stmt {
736 assignment(java::Statement::Expr(value))
737 }
738
739 /// `return v;` where the walk asked for a bare `return`.
740 ///
741 /// Only the reader leaves a routine early — the encoder emits no
742 /// `return` at all — and a Java decoder answers with the sample it
743 /// has built rather than writing through a parameter it cannot
744 /// have. See [`CodecTarget::read_call`] for why. A bare `return;`
745 /// here would leave the caller with whatever it had before, which
746 /// is the silent-wrong-value shape.
747 ///
748 /// If the encoder ever does emit one, `javac` refuses `return v;`
749 /// in a `void` method and names the file — loud, and at build time.
750 fn give_back(value: Option<Self::Expr>) -> Self::Stmt {
751 java::Statement::Return(Some(value.unwrap_or_else(|| Self::whole("v"))))
752 }
753
754 /// Nothing. `javac` has no unread-parameter warning, and `zd_tc;`
755 /// is not a Java statement.
756 fn mark_used(_name: &str) -> Vec<Self::Stmt> {
757 Vec::new()
758 }
759
760 /// The call on its own. A method call is already a Java statement,
761 /// and `(void) expr;` is not one at all.
762 fn drop_value(value: Self::Expr) -> Self::Stmt {
763 java::Statement::Expr(value)
764 }
765
766 /// `place = zd_dec_T(r, behaviour);`
767 ///
768 /// Java has no address to pass and no out-parameter to pass one in,
769 /// so a decoder answers with its value. [`CodecTarget::routine`]
770 /// and [`CodecTarget::give_back`] are the other two halves of that
771 /// decision.
772 fn read_call(
773 callee: &str,
774 place: Self::Expr,
775 _form: Handoff,
776 behaviour: Self::Expr,
777 ) -> Self::Stmt {
778 assign(place, Self::call(callee, vec![Self::name("r"), behaviour]))
779 }
780
781 /// `v = zd_dec_T(r, zd_tc);` — a `typedef`, handed straight on.
782 fn read_forward(callee: &str) -> Self::Stmt {
783 Self::read_call(
784 callee,
785 Self::whole("v"),
786 Handoff::Value,
787 Self::name("zd_tc"),
788 )
789 }
790
791 /// A shorter array, because a Java array is its own length.
792 ///
793 /// C leaves the elements past the bound in the buffer and moves the
794 /// count; there is no count here to move.
795 fn keep_first(base: &str, count: Self::Expr) -> Vec<Self::Stmt> {
796 if mapping() == JavaMapping::Psm {
797 // Arrays.copyOf for an array, the first elements of a list for
798 // a Collection: `Sequences.first` takes either.
799 return vec![assign(
800 Self::whole(base),
801 crate::adapter::java::sequences_first(
802 Self::whole(base),
803 Self::cast(java::Type::Primitive(java::Primitive::Int), count),
804 ),
805 )];
806 }
807 vec![java::Statement::Expr(java::Expr::Binary {
808 op: "=",
809 lhs: Box::new(Self::whole(base)),
810 rhs: Box::new(java::Expr::MethodCall {
811 receiver: Some(Box::new(java::Expr::Path(Path::new(&[
812 "java", "util", "Arrays",
813 ])))),
814 name: Ident::new("copyOf"),
815 // Through `cast`, which drops the `(int)` on a literal
816 // that is already one: a bound is written out as a
817 // number and `-Xlint:cast -Werror` names a redundant
818 // cast on it.
819 args: vec![
820 Self::whole(base),
821 Self::cast(java::Type::Primitive(java::Primitive::Int), count),
822 ],
823 }),
824 })]
825 }
826
827 fn when(
828 condition: Self::Expr,
829 then: Self::Block,
830 otherwise: Option<Self::Block>,
831 ) -> Self::Stmt {
832 java::Statement::If {
833 condition,
834 then,
835 otherwise,
836 }
837 }
838
839 /// `for (int i = 0; i < bound; i = i + 1)`.
840 ///
841 /// The step is written out rather than `i++` for the same reason C's
842 /// is: the tree has no increment node, and one spelling in both
843 /// backends keeps the two columns' generated source comparable by
844 /// eye when their octets disagree.
845 fn count_up(counter: &str, bound: Self::Expr, body: Self::Block) -> Self::Stmt {
846 java::Statement::For {
847 init: Box::new(java::Statement::Declare {
848 final_: false,
849 type_: java::Type::Primitive(java::Primitive::Int),
850 name: Ident::new(counter),
851 value: Some(java::Expr::Literal(Literal::int(0))),
852 }),
853 condition: java::Expr::Binary {
854 op: "<",
855 lhs: Box::new(java::Expr::Path(Path::new(&[counter]))),
856 rhs: Box::new(bound),
857 },
858 step: java::Expr::Binary {
859 op: "=",
860 lhs: Box::new(java::Expr::Path(Path::new(&[counter]))),
861 rhs: Box::new(java::Expr::Binary {
862 op: "+",
863 lhs: Box::new(java::Expr::Path(Path::new(&[counter]))),
864 rhs: Box::new(java::Expr::Literal(Literal::int(1))),
865 }),
866 },
867 body,
868 }
869 }
870
871 fn until(condition: Self::Expr, body: Self::Block) -> Self::Stmt {
872 java::Statement::While { condition, body }
873 }
874
875 fn leave() -> Self::Stmt {
876 java::Statement::Break
877 }
878
879 fn note(doc: Doc) -> Self::Stmt {
880 java::Statement::Comment(doc)
881 }
882
883 // -- assembly -----------------------------------------------------
884
885 /// The array, sized — and its slots given something to be where the
886 /// fill writes into them rather than assigning them.
887 ///
888 /// `new T[n]` throws rather than returning null when it cannot be
889 /// served, so there is no guard and the fill follows
890 /// unconditionally. An array and not a list, because that is what
891 /// the materialiser declares the member as; a `new ArrayList<>(n)`
892 /// here would compile against nothing.
893 ///
894 /// `new T[n]` of a class type is `n` nulls. A sequence's element
895 /// decoder answers with a value and the slot is assigned outright,
896 /// so nothing is needed; a `map`'s two halves are written into the
897 /// entry at `v[i]`, and an entry that was never made is a null
898 /// dereference at the first key. That is what `assigned_whole`
899 /// separates — not a guess from the element's type, which cannot
900 /// tell the two apart.
901 fn make_room(
902 base: &str,
903 element: Self::Type,
904 count: Self::Expr,
905 assigned_whole: bool,
906 fill: Vec<Self::Stmt>,
907 ) -> Vec<Self::Stmt> {
908 if mapping() == JavaMapping::Psm && assigned_whole {
909 // A new array or a new list of the kind the sample already is:
910 // `Sequences.alloc` answers with the type of its first
911 // argument, so the one routine serves an array and a
912 // `Collection<E>`.
913 let mut out = vec![assign(
914 Self::whole(base),
915 crate::adapter::java::sequences_alloc(
916 Self::whole(base),
917 Self::cast(java::Type::Primitive(java::Primitive::Int), count),
918 ),
919 )];
920 out.extend(fill);
921 return out;
922 }
923 let mut out = vec![java::Statement::Expr(java::Expr::Binary {
924 op: "=",
925 lhs: Box::new(Self::whole(base)),
926 rhs: Box::new(java::Expr::NewArray {
927 element: Box::new(element.clone()),
928 // `(int)` and not the count as it stands: a sequence
929 // length is a `uint32` on the wire and this column
930 // holds one in a `long`, which `new T[..]` does not
931 // take. The narrowing is safe because the count was
932 // already checked against what is left in the stream —
933 // `sequence_body` refuses `IMPOSSIBLE_LENGTH` first.
934 // Through `cast`, which keeps an array bound that is
935 // already an `int` literal bare — `new long[(int) 4]`
936 // is the redundant cast `-Xlint:cast -Werror` names.
937 lengths: vec![Self::cast(
938 java::Type::Primitive(java::Primitive::Int),
939 count,
940 )],
941 elements: Vec::new(),
942 }),
943 })];
944 if !assigned_whole {
945 out.push(Self::count_up(
946 "zd_e",
947 Self::count_of(base, None),
948 java::Block {
949 statements: vec![java::Statement::Expr(java::Expr::Binary {
950 op: "=",
951 lhs: Box::new(java::Expr::Index {
952 value: Box::new(Self::whole(base)),
953 index: Box::new(Self::name("zd_e")),
954 }),
955 rhs: Box::new(java::Expr::New {
956 type_: Box::new(element),
957 args: Vec::new(),
958 }),
959 })],
960 },
961 ));
962 }
963 out.extend(fill);
964 out
965 }
966
967 /// The cast the three narrow holders need, and no other.
968 ///
969 /// `readI8` answers an `int`, because a number in [-128, 127] is an
970 /// `int` once Java has widened it, and a `byte` field does not take
971 /// one without being told to. The same for `short` and for the
972 /// `char` an IDL `char` and `wchar` both map to. Everything else is
973 /// already the field's type, and a cast there is what
974 /// `-Xlint:cast -Werror` fails the build over.
975 ///
976 /// One narrowing and not two: where the walk has already asked for
977 /// a width — the element form of a `wchar` under TAO's framing —
978 /// that cast is folded into this one rather than written inside it.
979 fn store(place: Self::Expr, type_: Self::Type, value: Self::Expr) -> Self::Stmt {
980 let narrow = matches!(
981 type_,
982 java::Type::Primitive(java::Primitive::Byte | java::Primitive::Short)
983 ) || type_ == java::Type::Primitive(java::Primitive::Char);
984 if !narrow {
985 return assign(place, value);
986 }
987 let inner = match value {
988 java::Expr::Cast { value, .. } => *value,
989 other => other,
990 };
991 assign(
992 place,
993 java::Expr::Cast {
994 type_: Box::new(type_),
995 value: Box::new(inner),
996 },
997 )
998 }
999
1000 /// `<T> name = new <T>();` — a Java local of a class type is a
1001 /// reference to nothing until one is made.
1002 fn storage(type_: Self::Type, name: &str) -> Self::Stmt {
1003 java::Statement::Declare {
1004 final_: false,
1005 type_: type_.clone(),
1006 name: Ident::new(name),
1007 value: Some(java::Expr::New {
1008 type_: Box::new(type_),
1009 args: Vec::new(),
1010 }),
1011 }
1012 }
1013
1014 /// `runtime/java` publishes a header's parts behind accessors, so
1015 /// the field the walk names is a call here.
1016 ///
1017 /// A `match` and not a re-casing of the name: `end` is `isEnd()`
1018 /// and `extent` is computed from the length code, so a rule that
1019 /// only camel-cased would name two methods that are not there.
1020 fn header_field(value: Self::Expr, field: &str) -> Self::Expr {
1021 java::Expr::MethodCall {
1022 receiver: Some(Box::new(value)),
1023 name: Ident::new(match field {
1024 "pid" => "pid",
1025 "end" => "isEnd",
1026 "ignore" => "ignore",
1027 "length" => "length",
1028 "member_id" => "memberId",
1029 "must_understand" => "mustUnderstand",
1030 "extent" => "extent",
1031 // A field the runtime does not publish is a `javac`
1032 // error naming the method, which is where a header that
1033 // grew a part shows up.
1034 other => other,
1035 }),
1036 args: Vec::new(),
1037 }
1038 }
1039
1040 /// Absence is the value, so presence is a comparison and not a
1041 /// second field.
1042 fn present(base: &str, field: &str) -> Self::Expr {
1043 java::Expr::Binary {
1044 op: "!=",
1045 lhs: Box::new(Self::member(base, field)),
1046 rhs: Box::new(Self::absent()),
1047 }
1048 }
1049
1050 /// Nothing on the way in — assigning the member is what makes it
1051 /// present — and `null` on the way out.
1052 fn set_present(base: &str, field: &str, present: bool) -> Vec<Self::Stmt> {
1053 if present {
1054 return Vec::new();
1055 }
1056 vec![assign(Self::member(base, field), Self::absent())]
1057 }
1058
1059 /// One field per case, directly on the class.
1060 fn case_member(base: &str, field: &str) -> Self::Expr {
1061 Self::member(base, field)
1062 }
1063
1064 /// The sequence is the array.
1065 fn entry_half(entry: Self::Expr, key: bool) -> Self::Expr {
1066 java::Expr::Field {
1067 value: Box::new(entry),
1068 name: Ident::new(if key { "key" } else { "value" }),
1069 }
1070 }
1071
1072 fn optional_value(_ast: &Ast, _id: Id<TypeNode>, base: &str, field: &str) -> Self::Expr {
1073 Self::member(base, field)
1074 }
1075
1076 /// `v[i]`, and nothing around it: `adapter/java.rs` gives a sequence
1077 /// an array, whose elements are real slots at every element type.
1078 fn sequence_element(
1079 base: &str,
1080 index: Self::Expr,
1081 _element: Self::Type,
1082 ) -> (Vec<Self::Stmt>, Self::Expr, Vec<Self::Stmt>) {
1083 if mapping() == JavaMapping::Psm {
1084 // The slot may be an array's or a `Collection`'s; `assign`
1085 // writes it as `Sequences.put(v, i, value)`.
1086 return (
1087 Vec::new(),
1088 crate::adapter::java::sequence_slot(Self::whole(base), index),
1089 Vec::new(),
1090 );
1091 }
1092 (
1093 Vec::new(),
1094 Self::index(Self::elements(base), index),
1095 Vec::new(),
1096 )
1097 }
1098
1099 /// The boxed field itself. Assigning to it is what makes the member
1100 /// present, so there is nothing to do first.
1101 fn optional_destination(base: &str, field: &str) -> (Vec<Self::Stmt>, Self::Expr) {
1102 (Vec::new(), Self::member(base, field))
1103 }
1104
1105 fn elements(base: &str) -> Self::Expr {
1106 if mapping() == JavaMapping::Psm {
1107 return crate::adapter::java::sequence_view(Self::whole(base));
1108 }
1109 Self::whole(base)
1110 }
1111
1112 fn absent() -> Self::Expr {
1113 java::Expr::Literal(Literal::Null)
1114 }
1115
1116 /// `null`, because a `String` reference can be one.
1117 fn materialised(value: Self::Expr) -> Option<Self::Expr> {
1118 Some(java::Expr::Binary {
1119 op: "!=",
1120 lhs: Box::new(value),
1121 rhs: Box::new(Self::absent()),
1122 })
1123 }
1124
1125 fn block(statements: Vec<Self::Stmt>) -> Self::Block {
1126 java::Block { statements }
1127 }
1128
1129 fn into_statements(block: Self::Block) -> Vec<Self::Stmt> {
1130 block.statements
1131 }
1132
1133 /// `private static void encodeT(Writer w, T v)`.
1134 ///
1135 /// A prototype gets `None`: a Java method may call one declared
1136 /// below it, so there is nothing to declare ahead. `tolerate_unused`
1137 /// is dropped for the same kind of reason — `javac` does not warn on
1138 /// an uncalled private method, so there is no warning to suppress.
1139 fn routine(spec: Routine<Self>) -> Option<Self::Decl> {
1140 let mut body = spec.body?;
1141 let mut params = vec![java::Param {
1142 annotations: Vec::new(),
1143 final_: false,
1144 type_: java::Type::named(Path::new(&[if spec.writing { "Writer" } else { "Reader" }])),
1145 name: Ident::new(if spec.writing { "w" } else { "r" }),
1146 varargs: false,
1147 }];
1148 // A map is a `java.util.Map` to the application (XTypes 1.3
1149 // §7.5.1.3.5) and its entries, in order, to the walk: the writer
1150 // takes the map and works on `map.entries()`, the reader works on an
1151 // array of entries and answers `Map.of(entries)`.
1152 let map_class = map_class_of(&spec.sample);
1153 // The writer is handed the sample; the reader answers with one.
1154 // See `CodecTarget::read_call`: Java has no out-parameter, so
1155 // the destination is the caller's assignment target and the
1156 // routine's own `v` is a local.
1157 if spec.writing {
1158 let name = if map_class.is_some() { "zd_map" } else { "v" };
1159 params.push(java::Param {
1160 annotations: Vec::new(),
1161 final_: false,
1162 type_: spec.sample.clone(),
1163 name: Ident::new(name),
1164 varargs: false,
1165 });
1166 if let Some(class_path) = &map_class {
1167 body.statements.insert(
1168 0,
1169 java::Statement::Declare {
1170 final_: true,
1171 type_: entries_type(class_path),
1172 name: Ident::new("v"),
1173 value: Some(java::Expr::MethodCall {
1174 receiver: Some(Box::new(java::Expr::Path(Path::new(&["zd_map"])))),
1175 name: Ident::new("entries"),
1176 args: Vec::new(),
1177 }),
1178 },
1179 );
1180 }
1181 }
1182 params.extend(spec.extra.into_iter().map(|(type_, name)| java::Param {
1183 annotations: Vec::new(),
1184 final_: false,
1185 type_,
1186 name: Ident::new(&name),
1187 varargs: false,
1188 }));
1189 let (returns, body) = if spec.writing {
1190 (java::Type::Void, body)
1191 } else if let Some(class) = map_class {
1192 map_returns(&mut body, &class);
1193 (spec.sample.clone(), Self::map_reading_body(&class, body))
1194 } else {
1195 (
1196 spec.sample.clone(),
1197 Self::reading_body(&spec.name, spec.sample, body),
1198 )
1199 };
1200 Some(java::Member::Method(java::Method {
1201 doc: spec.doc,
1202 annotations: Vec::new(),
1203 // `public` and not `private`. A probe drops these members
1204 // into its own class and either reaches them; a generated
1205 // application does not — `ZdCodec` is the class it calls,
1206 // and a `private static` member of it is a codec nothing
1207 // outside the file can encode a sample with. Neither
1208 // `wireprobe-zerodds-java-gen` nor
1209 // `crates/endpoint-e2e/tests/java.rs` is affected: both
1210 // inline the members, where either modifier compiles, and
1211 // both cut the text at `public class ZdCodec {`, which is
1212 // the class's own line and not a member's.
1213 modifiers: vec![java::Modifier::Public, java::Modifier::Static],
1214 generics: Vec::new(),
1215 returns,
1216 name: Ident::new(&spec.name),
1217 params,
1218 throws: Vec::new(),
1219 body: Some(body),
1220 }))
1221 }
1222
1223 // -- the materialiser's boundary ----------------------------------
1224
1225 /// The materialiser's spelling, which for an aggregate is the
1226 /// nested path: `ZdTypes.common.First`, not `common_First`.
1227 ///
1228 /// `type_name` is the other one and is a *name* — what the codec
1229 /// method is called after. A codec that took `common_First` would
1230 /// name a class the materialiser never declares; every declaration
1231 /// nests inside `ZdTypes`, because Java allows one public top-level
1232 /// type per file and one file is what a probe compiles.
1233 fn value_type(ast: &Ast, id: Id<TypeNode>) -> Self::Type {
1234 crate::adapter::java::java_member_type(ast, id, "codec").unwrap_or_else(|_| {
1235 java::Type::named(Path::new(
1236 &[format!("UnresolvedType{}", id.slot()).as_str()],
1237 ))
1238 })
1239 }
1240
1241 /// The Java materialiser's name for a type.
1242 ///
1243 /// `type_tag` refuses a type whose name nests past its depth cap and
1244 /// this signature cannot; the walk asking for a name has already
1245 /// planned the type's layout, so a refusal here would be the second
1246 /// opinion of two.
1247 ///
1248 /// The fallback carries the arena slot, and that is the whole point
1249 /// of it. `type_tag`'s own answer for an unresolvable type is the
1250 /// bare word `unknown`, and two of those in one model would be two
1251 /// types sharing one codec — a member written by the wrong encoder,
1252 /// with nothing red anywhere. Slotted, two failures cannot collide,
1253 /// and `zd_enc_unresolved_type_41` is a name a reader can trace back
1254 /// to the node that produced it.
1255 fn type_name(ast: &Ast, id: Id<TypeNode>) -> String {
1256 type_tag(ast, id, 0).unwrap_or_else(|_| format!("unresolved_type_{}", id.slot()))
1257 }
1258
1259 fn escape(name: &str) -> String {
1260 java::escape_keyword(name)
1261 }
1262
1263 /// `ZdTypes.ZdEntry_<key>_<value>`, qualified.
1264 ///
1265 /// The entry class is declared at the outer level and a codec that
1266 /// wrote the bare name would reach a class only a file that
1267 /// imported it by name would find. `adapter/java.rs` writes the
1268 /// same path for a `map` member's element type.
1269 fn map_entry_name(ast: &Ast, id: Id<TypeNode>) -> String {
1270 crate::adapter::java::map_entry_name(ast, id).map_or_else(
1271 |_| format!("UnresolvedEntry{}", id.slot()),
1272 |name| format!("{}.{name}", crate::adapter::java::OUTER),
1273 )
1274 }
1275
1276 /// Java has packages, so a module needs no prefix in a name.
1277 fn module_prefix(_ast: &Ast, _id: Id<TypeNode>) -> String {
1278 String::new()
1279 }
1280
1281 /// The member's own `@default`, or Table 9's value for its type.
1282 ///
1283 /// # Errors
1284 /// As [`crate::default_java::member_default`]: a `@default` this
1285 /// column cannot spell is reported rather than quietly replaced by
1286 /// Table 9's zero.
1287 fn member_default(
1288 ast: &Ast,
1289 node: &MemberNode,
1290 type_: Id<TypeNode>,
1291 place: Self::Expr,
1292 ) -> Result<Vec<Self::Stmt>, EncodeError> {
1293 crate::default_java::member_default(ast, node, type_, place)
1294 }
1295
1296 /// `place = zd_def_<T>();` — Table 9, through the generated
1297 /// defaulter.
1298 ///
1299 /// A method and not a literal, for the reason `default_java.rs`
1300 /// gives: a literal cannot carry an aggregate, and two columns with
1301 /// two answers to Table 9 is a divergence no test turns red.
1302 ///
1303 /// # Errors
1304 /// Never here. Whether the type *has* a Java default is decided
1305 /// where the defaulter is built, and refused there by name.
1306 fn default_into(
1307 ast: &Ast,
1308 id: Id<TypeNode>,
1309 place: Self::Expr,
1310 ) -> Result<Self::Stmt, EncodeError> {
1311 Ok(crate::default_java::default_into(ast, id, place))
1312 }
1313
1314 /// `ZdTypes.<modules>.<Enum>.<LITERAL>`.
1315 ///
1316 /// The materialiser declares a class of `public static final int`
1317 /// per enumeration and every reference is written from the outer
1318 /// class down; an unqualified `BLUE` names nothing.
1319 fn enumerator_constant(ast: &Ast, id: Id<TypeNode>, enumerator: &str) -> Self::Expr {
1320 crate::default_java::enumerator_constant(ast, id, enumerator)
1321 }
1322
1323 /// An enumeration is a class (CORBA 3.3 part 3 §1.5.9) and its integer is `value()`.
1324 fn enumerator_ordinal(_ast: &Ast, _id: Id<TypeNode>, value: Self::Expr) -> Self::Expr {
1325 java::Expr::MethodCall {
1326 receiver: Some(Box::new(value)),
1327 name: Ident::new("value"),
1328 args: Vec::new(),
1329 }
1330 }
1331
1332 /// The label of a union whose discriminator is an enumeration is the instance of that
1333 /// value (`Color.from_int(1)`); any other discriminator takes the cast integer.
1334 fn case_label(ast: &Ast, discriminator: Id<TypeNode>, value: i128) -> Self::Expr {
1335 let mut current = discriminator;
1336 for _ in 0..32 {
1337 match ast.type_(current).map(|node| &node.kind) {
1338 Ok(zerodds_ast::TypeKind::Alias { aliased }) => match aliased.resolved() {
1339 Some(next) => current = next,
1340 None => break,
1341 },
1342 Ok(zerodds_ast::TypeKind::Enumeration { .. }) => {
1343 let owner = crate::adapter::java::declared_path(ast, current)
1344 .unwrap_or_else(|_| format!("UnresolvedEnum{}", current.slot()));
1345 return java::Expr::MethodCall {
1346 receiver: Some(Box::new(java::Expr::Path(Path::new(
1347 &owner.split('.').collect::<Vec<_>>(),
1348 )))),
1349 name: Ident::new("from_int"),
1350 args: vec![Self::int(i128::from(i32::try_from(value).unwrap_or_else(
1351 // A value above `int` is the bit pattern it occupies.
1352 |_| {
1353 u32::try_from(value & 0xFFFF_FFFF)
1354 .map_or(0, |bits| i32::from_ne_bytes(bits.to_ne_bytes()))
1355 },
1356 )))],
1357 };
1358 }
1359 _ => break,
1360 }
1361 }
1362 Self::cast(Self::value_type(ast, discriminator), Self::wide_int(value))
1363 }
1364}