Skip to main content

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}