Skip to content

Commit 02175a1

Browse files
author
kaori-seasons
committed
refactor: support xlang ref
1 parent 83097b2 commit 02175a1

File tree

7 files changed

+289
-2
lines changed

7 files changed

+289
-2
lines changed

java/fory-core/src/test/java/org/apache/fory/RustXlangTest.java

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,8 @@ public void testRust() throws Exception {
127127
testStructVersionCheck(Language.RUST, command);
128128
command.set(RUST_TESTCASE_INDEX, "test_consistent_named");
129129
testConsistentNamed(Language.RUST, command);
130+
command.set(RUST_TESTCASE_INDEX, "test_reference_alignment");
131+
testReferenceAlignment(Language.RUST, command);
130132
}
131133

132134
private void testBuffer(Language language, List<String> command) throws IOException {
@@ -941,4 +943,79 @@ private void assertStringEquals(Object actual, Object expected, boolean useToStr
941943
Assert.assertEquals(actual, expected);
942944
}
943945
}
946+
947+
/**
948+
* Test reference type alignment between Java and Rust in xlang mode.
949+
*
950+
* <p>This test verifies that:
951+
* <ul>
952+
* <li>Primitive types (int, double, boolean) don't write RefFlag
953+
* <li>Reference types (String, List, Map) write RefFlag in xlang mode
954+
* <li>Rust-serialized data can be correctly deserialized in Java
955+
* <li>Java-serialized data can be correctly deserialized in Rust
956+
* </ul>
957+
*/
958+
private void testReferenceAlignment(Language language, List<String> command)
959+
throws IOException {
960+
Fory fory = Fory.builder()
961+
.withLanguage(Language.XLANG)
962+
.withCompatibleMode(CompatibleMode.COMPATIBLE)
963+
.build();
964+
965+
// Serialize test data from Java
966+
MemoryBuffer buffer = MemoryBuffer.newHeapBuffer(256);
967+
968+
// 1. Primitive int (no RefFlag)
969+
fory.serialize(buffer, 42);
970+
971+
// 2. Boxed Integer (with RefFlag for null handling)
972+
fory.serialize(buffer, Integer.valueOf(100));
973+
974+
// 3. String (reference type, with RefFlag)
975+
fory.serialize(buffer, "hello");
976+
977+
// 4. List<String> (reference type, with RefFlag)
978+
fory.serialize(buffer, Arrays.asList("a", "b"));
979+
980+
// 5. Map<String, Integer> (reference type, with RefFlag)
981+
Map<String, Integer> map = new HashMap<>();
982+
map.put("key1", 10);
983+
map.put("key2", 20);
984+
fory.serialize(buffer, map);
985+
986+
// 6. Double (primitive, no RefFlag)
987+
fory.serialize(buffer, 3.14);
988+
989+
// 7. Boolean (primitive, no RefFlag)
990+
fory.serialize(buffer, true);
991+
992+
byte[] bytes = buffer.getBytes(0, buffer.writerIndex());
993+
LOG.info("Java serialized {} bytes for reference alignment test", bytes.length);
994+
995+
// Send to Rust for verification
996+
Path dataFile = Files.createTempFile("test_reference_alignment", "data");
997+
Pair<Map<String, String>, File> env_workdir = setFilePath(language, command, dataFile, bytes);
998+
Assert.assertTrue(
999+
executeCommand(command, 30, env_workdir.getLeft(), env_workdir.getRight()),
1000+
"Rust test failed");
1001+
1002+
// Read back Rust's serialization and verify
1003+
MemoryBuffer buffer2 = MemoryUtils.wrap(Files.readAllBytes(dataFile));
1004+
1005+
Assert.assertEquals(fory.deserialize(buffer2), 42, "i32 value mismatch");
1006+
Assert.assertEquals(fory.deserialize(buffer2), Integer.valueOf(100), "Option<i32> value mismatch");
1007+
Assert.assertEquals(fory.deserialize(buffer2), "hello", "String value mismatch");
1008+
Assert.assertEquals(
1009+
fory.deserialize(buffer2), Arrays.asList("a", "b"), "Vec<String> value mismatch");
1010+
1011+
@SuppressWarnings("unchecked")
1012+
Map<String, Integer> deserializedMap = (Map<String, Integer>) fory.deserialize(buffer2);
1013+
Assert.assertEquals(deserializedMap.get("key1"), Integer.valueOf(10), "HashMap key1 mismatch");
1014+
Assert.assertEquals(deserializedMap.get("key2"), Integer.valueOf(20), "HashMap key2 mismatch");
1015+
1016+
Assert.assertEquals(fory.deserialize(buffer2), 3.14, "f64 value mismatch");
1017+
Assert.assertEquals(fory.deserialize(buffer2), true, "bool value mismatch");
1018+
1019+
LOG.info("Reference alignment test passed!");
1020+
}
9441021
}

rust/fory-core/src/serializer/core.rs

Lines changed: 79 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -258,8 +258,21 @@ pub trait Serializer: 'static {
258258
Self: Sized,
259259
{
260260
if write_ref_info {
261-
// skip check option/pointer, the Serializer for such types will override `fory_write`.
262-
context.writer.write_i8(RefFlag::NotNullValue as i8);
261+
// In xlang mode, determine RefFlag based on type characteristics
262+
let should_write_ref = if context.is_xlang() {
263+
Self::fory_is_xlang_ref_type()
264+
} else {
265+
// In non-xlang mode, use original logic
266+
true
267+
};
268+
269+
if should_write_ref {
270+
// This is a reference type in xlang context - write RefValue
271+
context.writer.write_i8(RefFlag::RefValue as i8);
272+
} else {
273+
// This is a value type - write NotNullValue
274+
context.writer.write_i8(RefFlag::NotNullValue as i8);
275+
}
263276
}
264277
if write_type_info {
265278
// Serializer for dynamic types should override `fory_write` to write actual typeinfo.
@@ -1217,6 +1230,70 @@ pub trait Serializer: 'static {
12171230
std::mem::size_of::<Self>()
12181231
}
12191232

1233+
/// Indicate whether this type should be treated as a reference type in cross-language (xlang) serialization.
1234+
///
1235+
/// In cross-language scenarios, type systems differ between languages. For example:
1236+
/// - In Java: `String`, `List`, `Map` are reference types (need RefFlag)
1237+
/// - In Rust: `String`, `Vec`, `HashMap` are value types (by default, no RefFlag)
1238+
///
1239+
/// This method bridges the gap by allowing Rust types to declare their cross-language reference semantics.
1240+
///
1241+
/// # Returns
1242+
///
1243+
/// - `true` if this type should be treated as a reference type in xlang mode
1244+
/// (will write RefFlag during serialization)
1245+
/// - `false` if this type should be treated as a value type in xlang mode
1246+
/// (will not write RefFlag, default)
1247+
///
1248+
/// # Type Mapping Guidelines
1249+
///
1250+
/// | Rust Type | Java Type | Should Return |
1251+
/// |-----------|-----------|---------------|
1252+
/// | `i32`, `f64`, `bool` | `int`, `double`, `boolean` | `false` |
1253+
/// | `Option<i32>` | `Integer` | `false` (Option handles null) |
1254+
/// | `String` | `String` | `true` |
1255+
/// | `Vec<T>` | `List<T>` | `true` |
1256+
/// | `HashMap<K,V>` | `Map<K,V>` | `true` |
1257+
/// | User struct | Java object | `true` |
1258+
///
1259+
/// # Default Implementation
1260+
///
1261+
/// Returns `false` for all types. Override for types that correspond to reference types in other languages.
1262+
///
1263+
/// # Examples
1264+
///
1265+
/// ```rust,ignore
1266+
/// // String should be treated as reference type in Java
1267+
/// impl Serializer for String {
1268+
/// fn fory_is_xlang_ref_type() -> bool {
1269+
/// true
1270+
/// }
1271+
/// }
1272+
///
1273+
/// // i32 is primitive in Java (int)
1274+
/// impl Serializer for i32 {
1275+
/// fn fory_is_xlang_ref_type() -> bool {
1276+
/// false // default
1277+
/// }
1278+
/// }
1279+
/// ```
1280+
///
1281+
/// # Implementation Notes
1282+
///
1283+
/// - Only affects behavior when `context.is_xlang()` is true
1284+
/// - Used in [`fory_write`] to determine RefFlag behavior
1285+
/// - Fory implements this for all built-in types
1286+
/// - User types should override this for proper xlang interop
1287+
///
1288+
/// [`fory_write`]: Serializer::fory_write
1289+
#[inline(always)]
1290+
fn fory_is_xlang_ref_type() -> bool
1291+
where
1292+
Self: Sized,
1293+
{
1294+
false
1295+
}
1296+
12201297
/// **[USER IMPLEMENTATION REQUIRED]** Downcast to `&dyn Any` for dynamic type checking.
12211298
///
12221299
/// This method enables runtime type checking and downcasting, required for

rust/fory-core/src/serializer/list.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,14 @@ impl<T: Serializer + ForyDefault> Serializer for Vec<T> {
121121
fn as_any(&self) -> &dyn std::any::Any {
122122
self
123123
}
124+
125+
fn fory_is_xlang_ref_type() -> bool
126+
where
127+
Self: Sized,
128+
{
129+
// Vec<T> corresponds to Java List<T>, which is a reference type
130+
true
131+
}
124132
}
125133

126134
impl<T> ForyDefault for Vec<T> {
@@ -173,6 +181,14 @@ impl<T: Serializer + ForyDefault> Serializer for VecDeque<T> {
173181
fn as_any(&self) -> &dyn std::any::Any {
174182
self
175183
}
184+
185+
fn fory_is_xlang_ref_type() -> bool
186+
where
187+
Self: Sized,
188+
{
189+
// VecDeque<T> corresponds to Java List<T>, which is a reference type
190+
true
191+
}
176192
}
177193

178194
impl<T> ForyDefault for VecDeque<T> {
@@ -225,6 +241,14 @@ impl<T: Serializer + ForyDefault> Serializer for LinkedList<T> {
225241
fn as_any(&self) -> &dyn std::any::Any {
226242
self
227243
}
244+
245+
fn fory_is_xlang_ref_type() -> bool
246+
where
247+
Self: Sized,
248+
{
249+
// LinkedList<T> corresponds to Java List<T>, which is a reference type
250+
true
251+
}
228252
}
229253

230254
impl<T> ForyDefault for LinkedList<T> {

rust/fory-core/src/serializer/map.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -638,6 +638,14 @@ impl<K: Serializer + ForyDefault + Eq + std::hash::Hash, V: Serializer + ForyDef
638638
fn fory_read_type_info(context: &mut ReadContext) -> Result<(), Error> {
639639
read_basic_type_info::<Self>(context)
640640
}
641+
642+
fn fory_is_xlang_ref_type() -> bool
643+
where
644+
Self: Sized,
645+
{
646+
// HashMap<K,V> corresponds to Java Map<K,V>, which is a reference type
647+
true
648+
}
641649
}
642650

643651
impl<K, V> ForyDefault for HashMap<K, V> {
@@ -766,6 +774,14 @@ impl<K: Serializer + ForyDefault + Ord + std::hash::Hash, V: Serializer + ForyDe
766774
fn fory_read_type_info(context: &mut ReadContext) -> Result<(), Error> {
767775
read_basic_type_info::<Self>(context)
768776
}
777+
778+
fn fory_is_xlang_ref_type() -> bool
779+
where
780+
Self: Sized,
781+
{
782+
// BTreeMap<K,V> corresponds to Java Map<K,V>, which is a reference type
783+
true
784+
}
769785
}
770786

771787
impl<K, V> ForyDefault for BTreeMap<K, V> {

rust/fory-core/src/serializer/set.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,14 @@ impl<T: Serializer + ForyDefault + Eq + std::hash::Hash> Serializer for HashSet<
6868
fn as_any(&self) -> &dyn std::any::Any {
6969
self
7070
}
71+
72+
fn fory_is_xlang_ref_type() -> bool
73+
where
74+
Self: Sized,
75+
{
76+
// HashSet<T> corresponds to Java Set<T>, which is a reference type
77+
true
78+
}
7179
}
7280

7381
impl<T> ForyDefault for HashSet<T> {
@@ -115,6 +123,14 @@ impl<T: Serializer + ForyDefault + Ord> Serializer for BTreeSet<T> {
115123
fn as_any(&self) -> &dyn std::any::Any {
116124
self
117125
}
126+
127+
fn fory_is_xlang_ref_type() -> bool
128+
where
129+
Self: Sized,
130+
{
131+
// BTreeSet<T> corresponds to Java Set<T>, which is a reference type
132+
true
133+
}
118134
}
119135

120136
impl<T> ForyDefault for BTreeSet<T> {

rust/fory-core/src/serializer/string.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,15 @@ impl Serializer for String {
9898
fn fory_read_type_info(context: &mut ReadContext) -> Result<(), Error> {
9999
read_basic_type_info::<Self>(context)
100100
}
101+
102+
#[inline(always)]
103+
fn fory_is_xlang_ref_type() -> bool
104+
where
105+
Self: Sized,
106+
{
107+
// String is a reference type in Java, should write RefFlag in xlang mode
108+
true
109+
}
101110
}
102111

103112
impl ForyDefault for String {

rust/tests/tests/test_cross_language.rs

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -734,3 +734,71 @@ fn test_struct_version_check() {
734734
assert_eq!(new_local_obj, local_obj);
735735
fs::write(&data_file_path, new_bytes).unwrap();
736736
}
737+
738+
/// Test reference type alignment between Java and Rust in xlang mode.
739+
///
740+
/// This test verifies that:
741+
/// 1. Primitive types (i32, f64, bool) don't write RefFlag
742+
/// 2. Reference types (String, Vec, HashMap) write RefFlag in xlang mode
743+
/// 3. Java-serialized data can be correctly deserialized in Rust
744+
/// 4. Rust-serialized data can be correctly deserialized in Java
745+
#[test]
746+
#[ignore]
747+
fn test_reference_alignment() {
748+
let data_file_path = get_data_file();
749+
750+
// Read data serialized by Java
751+
let bytes = fs::read(&data_file_path).unwrap();
752+
let mut fory = Fory::default().xlang(true).compatible(true);
753+
754+
let reader = Reader::new(bytes.as_slice());
755+
let mut context = ReadContext::new_from_fory(reader, &fory);
756+
757+
// Deserialize values in the same order as Java wrote them
758+
// 1. Primitive int (no RefFlag)
759+
let v1: i32 = fory.deserialize_with_context(&mut context).unwrap();
760+
assert_eq!(v1, 42, "i32 value mismatch");
761+
762+
// 2. Boxed Integer (with RefFlag for null handling)
763+
let v2: Option<i32> = fory.deserialize_with_context(&mut context).unwrap();
764+
assert_eq!(v2, Some(100), "Option<i32> value mismatch");
765+
766+
// 3. String (reference type, with RefFlag)
767+
let v3: String = fory.deserialize_with_context(&mut context).unwrap();
768+
assert_eq!(v3, "hello", "String value mismatch");
769+
770+
// 4. List<String> (reference type, with RefFlag)
771+
let v4: Vec<String> = fory.deserialize_with_context(&mut context).unwrap();
772+
assert_eq!(v4, vec!["a".to_string(), "b".to_string()], "Vec<String> value mismatch");
773+
774+
// 5. Map<String, Integer> (reference type, with RefFlag)
775+
let v5: HashMap<String, i32> = fory.deserialize_with_context(&mut context).unwrap();
776+
let mut expected_map = HashMap::new();
777+
expected_map.insert("key1".to_string(), 10);
778+
expected_map.insert("key2".to_string(), 20);
779+
assert_eq!(v5, expected_map, "HashMap value mismatch");
780+
781+
// 6. Double (primitive, no RefFlag)
782+
let v6: f64 = fory.deserialize_with_context(&mut context).unwrap();
783+
assert_eq!(v6, 3.14, "f64 value mismatch");
784+
785+
// 7. Boolean (primitive, no RefFlag)
786+
let v7: bool = fory.deserialize_with_context(&mut context).unwrap();
787+
assert_eq!(v7, true, "bool value mismatch");
788+
789+
// Now serialize data back to Java
790+
let writer = Writer::default();
791+
let mut context = WriteContext::new_from_fory(writer, &fory);
792+
793+
// Serialize in the same order
794+
fory.serialize_with_context(&42i32, &mut context).unwrap();
795+
fory.serialize_with_context(&Some(100i32), &mut context).unwrap();
796+
fory.serialize_with_context(&"hello".to_string(), &mut context).unwrap();
797+
fory.serialize_with_context(&vec!["a".to_string(), "b".to_string()], &mut context).unwrap();
798+
fory.serialize_with_context(&expected_map, &mut context).unwrap();
799+
fory.serialize_with_context(&3.14f64, &mut context).unwrap();
800+
fory.serialize_with_context(&true, &mut context).unwrap();
801+
802+
// Write back to file for Java to verify
803+
fs::write(&data_file_path, context.writer.dump()).unwrap();
804+
}

0 commit comments

Comments
 (0)