From 8d48679333906251de9eb17f87d52a8e92bdfe86 Mon Sep 17 00:00:00 2001 From: Ethan Castonguay Date: Fri, 16 Oct 2026 17:34:23 -0300 Subject: [PATCH 1/3] Fix LinkedHashMap access-order serialization --- .../serializer/collection/MapSerializers.java | 55 +++++++++++++------ .../collection/MapSerializersTest.java | 27 +++++++++ 2 files changed, 65 insertions(+), 17 deletions(-) diff --git a/java/fory-core/src/main/java/org/apache/fory/serializer/collection/MapSerializers.java b/java/fory-core/src/main/java/org/apache/fory/serializer/collection/MapSerializers.java index 86bc1cf758..d114c58322 100644 --- a/java/fory-core/src/main/java/org/apache/fory/serializer/collection/MapSerializers.java +++ b/java/fory-core/src/main/java/org/apache/fory/serializer/collection/MapSerializers.java @@ -51,6 +51,7 @@ import org.apache.fory.memory.MemoryBuffer; import org.apache.fory.meta.TypeDef; import org.apache.fory.reflect.ReflectionUtils; +import org.apache.fory.reflect.FieldAccessor; import org.apache.fory.resolver.ClassResolver; import org.apache.fory.resolver.TypeInfo; import org.apache.fory.resolver.TypeResolver; @@ -63,6 +64,7 @@ import org.apache.fory.util.ExceptionUtils; import org.apache.fory.util.Preconditions; + /** * Serializers for classes implements {@link Collection}. All map serializers must extends {@link * MapSerializer}. @@ -106,26 +108,45 @@ public Map newMap(Map map) { } } - public static final class LinkedHashMapSerializer extends MapSerializer { - public LinkedHashMapSerializer(TypeResolver typeResolver) { - super(typeResolver, LinkedHashMap.class, true); - } + public static final class LinkedHashMapSerializer extends MapSerializer { + private static final class AccessOrderAccessor { + private static final FieldAccessor INSTANCE = + FieldAccessor.createAccessor( + ReflectionUtils.getDeclaredField(LinkedHashMap.class, "accessOrder")); + } - @Override - public LinkedHashMap newMap(ReadContext readContext, boolean entryReadAlwaysAdvances) { - MemoryBuffer buffer = readContext.getBuffer(); - int numElements = readMapSize(readContext, buffer, entryReadAlwaysAdvances); - setNumElements(numElements); - LinkedHashMap hashMap = new LinkedHashMap(numElements); - readContext.reference(hashMap); - return hashMap; - } + public LinkedHashMapSerializer(TypeResolver typeResolver) { + super(typeResolver, LinkedHashMap.class, true); + } - @Override - public Map newMap(Map map) { - return new LinkedHashMap(map.size()); + @Override + public Map onMapWrite(WriteContext writeContext, LinkedHashMap value) { + Map map = super.onMapWrite(writeContext, value); + if (!config.isXlang()) { + writeContext.getBuffer().writeBoolean(AccessOrderAccessor.INSTANCE.getBoolean(value)); + } + return map; + } + + @Override + public LinkedHashMap newMap(ReadContext readContext, boolean entryReadAlwaysAdvances) { + MemoryBuffer buffer = readContext.getBuffer(); + int numElements = readMapSize(readContext, buffer, entryReadAlwaysAdvances); + setNumElements(numElements); + + boolean accessOrder = !config.isXlang() && buffer.readBoolean(); + LinkedHashMap hashMap = new LinkedHashMap(numElements, 0.75f, accessOrder); + readContext.reference(hashMap); + return hashMap; + } + + @Override + public Map newMap(Map map) { + boolean accessOrder = + !config.isXlang() && AccessOrderAccessor.INSTANCE.getBoolean(map); + return new LinkedHashMap(map.size(), 0.75f, accessOrder); + } } - } public static final class IdentityHashMapSerializer extends JDKCompatibleMapSerializer { diff --git a/java/fory-core/src/test/java/org/apache/fory/serializer/collection/MapSerializersTest.java b/java/fory-core/src/test/java/org/apache/fory/serializer/collection/MapSerializersTest.java index 0fab5bd62e..69640e59aa 100644 --- a/java/fory-core/src/test/java/org/apache/fory/serializer/collection/MapSerializersTest.java +++ b/java/fory-core/src/test/java/org/apache/fory/serializer/collection/MapSerializersTest.java @@ -264,6 +264,33 @@ public void basicTestCaseWithMultiConfig( serDeCheck(fory, obj1); } + @Test + public void testLinkedHashMapAccessOrder() { + Fory fory = getJavaFory(); + + LinkedHashMap original = new LinkedHashMap<>(16, 0.75f, true); + original.put(1, 1); + original.put(2, 2); + original.put(3, 3); + original.put(4, 4); + + byte[] bytes = fory.serialize(original); + LinkedHashMap deserialized = + (LinkedHashMap) fory.deserialize(bytes); + + original.get(2); + original.get(4); + original.get(1); + original.get(3); + + deserialized.get(2); + deserialized.get(4); + deserialized.get(1); + deserialized.get(3); + + assertEquals(new ArrayList<>(deserialized.keySet()), new ArrayList<>(original.keySet())); + } + @Test(dataProvider = "referenceTrackingConfig") public void testBasicMap(boolean referenceTrackingConfig) { Fory fory = From 27ae67b623d5bf7fb12a0a0d81c5fc11a8845d7d Mon Sep 17 00:00:00 2001 From: Ethan Castonguay Date: Fri, 16 Oct 2026 18:46:50 -0300 Subject: [PATCH 2/3] style(java): format LinkedHashMap access-order changes --- .../serializer/collection/MapSerializers.java | 68 +++++++++---------- .../collection/MapSerializersTest.java | 52 +++++++------- 2 files changed, 59 insertions(+), 61 deletions(-) diff --git a/java/fory-core/src/main/java/org/apache/fory/serializer/collection/MapSerializers.java b/java/fory-core/src/main/java/org/apache/fory/serializer/collection/MapSerializers.java index d114c58322..6b19c54f33 100644 --- a/java/fory-core/src/main/java/org/apache/fory/serializer/collection/MapSerializers.java +++ b/java/fory-core/src/main/java/org/apache/fory/serializer/collection/MapSerializers.java @@ -50,8 +50,8 @@ import org.apache.fory.context.WriteContext; import org.apache.fory.memory.MemoryBuffer; import org.apache.fory.meta.TypeDef; -import org.apache.fory.reflect.ReflectionUtils; import org.apache.fory.reflect.FieldAccessor; +import org.apache.fory.reflect.ReflectionUtils; import org.apache.fory.resolver.ClassResolver; import org.apache.fory.resolver.TypeInfo; import org.apache.fory.resolver.TypeResolver; @@ -64,7 +64,6 @@ import org.apache.fory.util.ExceptionUtils; import org.apache.fory.util.Preconditions; - /** * Serializers for classes implements {@link Collection}. All map serializers must extends {@link * MapSerializer}. @@ -108,45 +107,44 @@ public Map newMap(Map map) { } } - public static final class LinkedHashMapSerializer extends MapSerializer { - private static final class AccessOrderAccessor { - private static final FieldAccessor INSTANCE = - FieldAccessor.createAccessor( - ReflectionUtils.getDeclaredField(LinkedHashMap.class, "accessOrder")); - } + public static final class LinkedHashMapSerializer extends MapSerializer { + private static final class AccessOrderAccessor { + private static final FieldAccessor INSTANCE = + FieldAccessor.createAccessor( + ReflectionUtils.getDeclaredField(LinkedHashMap.class, "accessOrder")); + } - public LinkedHashMapSerializer(TypeResolver typeResolver) { - super(typeResolver, LinkedHashMap.class, true); - } + public LinkedHashMapSerializer(TypeResolver typeResolver) { + super(typeResolver, LinkedHashMap.class, true); + } - @Override - public Map onMapWrite(WriteContext writeContext, LinkedHashMap value) { - Map map = super.onMapWrite(writeContext, value); - if (!config.isXlang()) { - writeContext.getBuffer().writeBoolean(AccessOrderAccessor.INSTANCE.getBoolean(value)); - } - return map; - } + @Override + public Map onMapWrite(WriteContext writeContext, LinkedHashMap value) { + Map map = super.onMapWrite(writeContext, value); + if (!config.isXlang()) { + writeContext.getBuffer().writeBoolean(AccessOrderAccessor.INSTANCE.getBoolean(value)); + } + return map; + } - @Override - public LinkedHashMap newMap(ReadContext readContext, boolean entryReadAlwaysAdvances) { - MemoryBuffer buffer = readContext.getBuffer(); - int numElements = readMapSize(readContext, buffer, entryReadAlwaysAdvances); - setNumElements(numElements); + @Override + public LinkedHashMap newMap(ReadContext readContext, boolean entryReadAlwaysAdvances) { + MemoryBuffer buffer = readContext.getBuffer(); + int numElements = readMapSize(readContext, buffer, entryReadAlwaysAdvances); + setNumElements(numElements); - boolean accessOrder = !config.isXlang() && buffer.readBoolean(); - LinkedHashMap hashMap = new LinkedHashMap(numElements, 0.75f, accessOrder); - readContext.reference(hashMap); - return hashMap; - } + boolean accessOrder = !config.isXlang() && buffer.readBoolean(); + LinkedHashMap hashMap = new LinkedHashMap(numElements, 0.75f, accessOrder); + readContext.reference(hashMap); + return hashMap; + } - @Override - public Map newMap(Map map) { - boolean accessOrder = - !config.isXlang() && AccessOrderAccessor.INSTANCE.getBoolean(map); - return new LinkedHashMap(map.size(), 0.75f, accessOrder); - } + @Override + public Map newMap(Map map) { + boolean accessOrder = !config.isXlang() && AccessOrderAccessor.INSTANCE.getBoolean(map); + return new LinkedHashMap(map.size(), 0.75f, accessOrder); } + } public static final class IdentityHashMapSerializer extends JDKCompatibleMapSerializer { diff --git a/java/fory-core/src/test/java/org/apache/fory/serializer/collection/MapSerializersTest.java b/java/fory-core/src/test/java/org/apache/fory/serializer/collection/MapSerializersTest.java index 69640e59aa..acafa8eced 100644 --- a/java/fory-core/src/test/java/org/apache/fory/serializer/collection/MapSerializersTest.java +++ b/java/fory-core/src/test/java/org/apache/fory/serializer/collection/MapSerializersTest.java @@ -264,32 +264,32 @@ public void basicTestCaseWithMultiConfig( serDeCheck(fory, obj1); } - @Test - public void testLinkedHashMapAccessOrder() { - Fory fory = getJavaFory(); - - LinkedHashMap original = new LinkedHashMap<>(16, 0.75f, true); - original.put(1, 1); - original.put(2, 2); - original.put(3, 3); - original.put(4, 4); - - byte[] bytes = fory.serialize(original); - LinkedHashMap deserialized = - (LinkedHashMap) fory.deserialize(bytes); - - original.get(2); - original.get(4); - original.get(1); - original.get(3); - - deserialized.get(2); - deserialized.get(4); - deserialized.get(1); - deserialized.get(3); - - assertEquals(new ArrayList<>(deserialized.keySet()), new ArrayList<>(original.keySet())); - } + @Test + public void testLinkedHashMapAccessOrder() { + Fory fory = getJavaFory(); + + LinkedHashMap original = new LinkedHashMap<>(16, 0.75f, true); + original.put(1, 1); + original.put(2, 2); + original.put(3, 3); + original.put(4, 4); + + byte[] bytes = fory.serialize(original); + LinkedHashMap deserialized = + (LinkedHashMap) fory.deserialize(bytes); + + original.get(2); + original.get(4); + original.get(1); + original.get(3); + + deserialized.get(2); + deserialized.get(4); + deserialized.get(1); + deserialized.get(3); + + assertEquals(new ArrayList<>(deserialized.keySet()), new ArrayList<>(original.keySet())); + } @Test(dataProvider = "referenceTrackingConfig") public void testBasicMap(boolean referenceTrackingConfig) { From bc94e23696810b129ea99546a5e27eafd4d223f3 Mon Sep 17 00:00:00 2001 From: Ethan Castonguay Date: Fri, 16 Oct 2026 19:08:07 -0300 Subject: [PATCH 3/3] test(java): strengthen LinkedHashMap access-order regression --- .../fory/serializer/collection/MapSerializersTest.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/java/fory-core/src/test/java/org/apache/fory/serializer/collection/MapSerializersTest.java b/java/fory-core/src/test/java/org/apache/fory/serializer/collection/MapSerializersTest.java index acafa8eced..333220ccb3 100644 --- a/java/fory-core/src/test/java/org/apache/fory/serializer/collection/MapSerializersTest.java +++ b/java/fory-core/src/test/java/org/apache/fory/serializer/collection/MapSerializersTest.java @@ -279,16 +279,22 @@ public void testLinkedHashMapAccessOrder() { (LinkedHashMap) fory.deserialize(bytes); original.get(2); + deserialized.get(2); + + assertEquals(new ArrayList<>(original.keySet()), new ArrayList<>(deserialized.keySet())); + assertEquals( + new ArrayList<>(java.util.Arrays.asList(1, 3, 4, 2)), + new ArrayList<>(deserialized.keySet())); + original.get(4); original.get(1); original.get(3); - deserialized.get(2); deserialized.get(4); deserialized.get(1); deserialized.get(3); - assertEquals(new ArrayList<>(deserialized.keySet()), new ArrayList<>(original.keySet())); + assertEquals(new ArrayList<>(original.keySet()), new ArrayList<>(deserialized.keySet())); } @Test(dataProvider = "referenceTrackingConfig")