1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
//! V108 (15w32c + 4) — schematic-relevant subset of `V108.java`.
//!
//! VERSION = MCVersions.V15W32C (104) + 4 = 108.
//!
//! Ported: the ENTITY structure converter (V108.java:19-42) that turns a legacy
//! String `UUID` field into the `UUIDMost`/`UUIDLeast` long pair. It parses the
//! string, removes `UUID`, and sets `UUIDMost = mostSignificantBits` /
//! `UUIDLeast = leastSignificantBits`. If `UUID` is absent, or parsing fails,
//! the converter is a no-op (Java returns null without further mutation, though
//! note Java still removes `UUID` before attempting the parse — replicated here).
use super::super::registry::RegistryBuilder;
use super::super::types::MapExt;
const VERSION: i32 = 108;
/// Port of `java.util.UUID.fromString` -> (mostSignificantBits, leastSignificantBits).
///
/// Mirrors the JDK behaviour: split on '-' into exactly 5 components, parse each
/// as signed hex (`Long.parseLong(part, 16)`), then recombine. Returns `None`
/// when the shape is wrong or any component fails to parse, which corresponds to
/// Java's `UUID.fromString` throwing (caught -> return null in the converter).
fn parse_uuid(s: &str) -> Option<(i64, i64)> {
let parts: Vec<&str> = s.split('-').collect();
if parts.len() != 5 {
return None;
}
// java.util.UUID.fromString: group widths are 32/16/16 (most) and 16/48
// (least); each group is masked to its field width then shifted into place.
let p = |hex: &str| u64::from_str_radix(hex, 16).ok();
let mut most = p(parts[0])? & 0xffff_ffff;
most <<= 16;
most |= p(parts[1])? & 0xffff;
most <<= 16;
most |= p(parts[2])? & 0xffff;
let mut least = p(parts[3])? & 0xffff;
least <<= 48;
least |= p(parts[4])? & 0xffff_ffff_ffff;
Some((most as i64, least as i64))
}
/// Inverse of `parse_uuid`: port of `java.util.UUID.toString()`.
///
/// JDK formats each field as lowercase hex zero-padded to its width and joins
/// with '-': 8-4-4-4-12 (most: bits 63..32 / 31..16 / 15..0; least: 63..48 /
/// 47..0). This is the exact preimage of the forward split, so the round trip is
/// lossless for any pair that the forward could have produced.
fn format_uuid(most: i64, least: i64) -> String {
let most = most as u64;
let least = least as u64;
format!(
"{:08x}-{:04x}-{:04x}-{:04x}-{:012x}",
(most >> 32) & 0xffff_ffff,
(most >> 16) & 0xffff,
most & 0xffff,
(least >> 48) & 0xffff,
least & 0xffff_ffff_ffff,
)
}
pub fn register(reg: &mut RegistryBuilder) {
reg.entity.add_structure_converter(
VERSION,
0,
Box::new(|data, _from, _to| {
let uuid_string = match data.get_string("UUID") {
Some(s) => s.to_string(),
None => return,
};
data.take("UUID");
if let Some((most, least)) = parse_uuid(&uuid_string) {
data.set_i64("UUIDMost", most);
data.set_i64("UUIDLeast", least);
}
}),
);
// Reverse of V108.java:19-42 — recombine the UUIDMost/UUIDLeast long pair
// back into the canonical String `UUID` (JDK UUID.toString layout). Exact
// inverse of the forward split, so this is lossless (bucket B). Only acts
// when both halves are present (mirrors the forward only emitting both).
reg.entity.add_reverse_converter(
VERSION,
0,
Box::new(|data, _from, _to| {
let most = match data.get_i64("UUIDMost") {
Some(v) => v,
None => return,
};
let least = match data.get_i64("UUIDLeast") {
Some(v) => v,
None => return,
};
data.take("UUIDMost");
data.take("UUIDLeast");
data.set_string("UUID", format_uuid(most, least));
}),
);
}