This is an automated email from the ASF dual-hosted git repository. chaokunyang pushed a commit to tag v0.14.1-rc1 in repository https://gitbox.apache.org/repos/asf/fory.git
commit 491519a7c559af0c3cf1234e8c0c1822ab62ef95 Author: Vybhav Jayasankar <[email protected]> AuthorDate: Tue Dec 23 21:49:09 2025 +0530 fix(java): Fix CopyOnWriteArrayList field serialization (#3079) ## Why? Fory doesn't currently support serialization of any object that has a `CopyOnWriteArrayList` field with codegen, because the generated code attempts to cast `CollectionSnapshot` to `List`, throwing the following exception : ``` org.apache.fory.exception.SerializationException: java.lang.ClassCastException: class org.apache.fory.collection.CollectionSnapshot cannot be cast to class java.util.List (org.apache.fory.collection.CollectionSnapshot is in unnamed module of loader 'app'; java.util.List is in module java.base of loader 'bootstrap') ( . . . ) org.apache.fory.serializer.collection.CollectionSerializersTest.testCopyOnWriteArrayListNested(CollectionSerializersTest.java:407) Caused by: java.lang.ClassCastException: class org.apache.fory.collection.CollectionSnapshot cannot be cast to class java.util.List (org.apache.fory.collection.CollectionSnapshot is in unnamed module of loader 'app'; java.util.List is in module java.base of loader 'bootstrap') at org.apache.fory.serializer.collection.CollectionSerializersTest_NestedCopyOnWriteArrayListForyCodec_0.writeFields$(CollectionSerializersTest_NestedCopyOnWriteArrayListForyCodec_0.java:63) at org.apache.fory.serializer.collection.CollectionSerializersTest_NestedCopyOnWriteArrayListForyCodec_0.write(CollectionSerializersTest_NestedCopyOnWriteArrayListForyCodec_0.java:130) ``` This is the relevant code from the generated codec that attempts the invalid class cast. ``` CollectionLikeSerializer collectionLikeSerializer = this.writeCollectionClassInfo(list0, memoryBuffer1); if (collectionLikeSerializer.supportCodegenHook()) { java.util.List list1 = (java.util.List)collectionLikeSerializer.onCollectionWrite(memoryBuffer1, list0); ``` ## What does this PR do? * feat: Update `CollectionSnapshot` to extend `AbstractList` instead of `AbstractCollection`, and implement `get(index)` method * test: Add test in `CollectionSerializersTest` for serializing objects with CopyOnWriteArrayList fields * test: Add test in `CollectionSnapshotTest` for `CollectionSnapshot.get(index)` ## Related issues Fixes pending issues from #2918. ## Does this PR introduce any user-facing change? - [ ] Does this PR introduce any public API change? - [ ] Does this PR introduce any binary protocol compatibility change? ## Benchmark --- .../apache/fory/collection/CollectionSnapshot.java | 9 +++++-- .../fory/collection/CollectionSnapshotTest.java | 30 ++++++++++++++++++++++ .../collection/CollectionSerializersTest.java | 18 +++++++++++++ 3 files changed, 55 insertions(+), 2 deletions(-) diff --git a/java/fory-core/src/main/java/org/apache/fory/collection/CollectionSnapshot.java b/java/fory-core/src/main/java/org/apache/fory/collection/CollectionSnapshot.java index 4f15a909c..de8aae70c 100644 --- a/java/fory-core/src/main/java/org/apache/fory/collection/CollectionSnapshot.java +++ b/java/fory-core/src/main/java/org/apache/fory/collection/CollectionSnapshot.java @@ -19,7 +19,7 @@ package org.apache.fory.collection; -import java.util.AbstractCollection; +import java.util.AbstractList; import java.util.Collection; import java.util.Iterator; import org.apache.fory.annotation.Internal; @@ -46,7 +46,7 @@ import org.apache.fory.annotation.Internal; * @since 1.0 */ @Internal -public class CollectionSnapshot<E> extends AbstractCollection<E> { +public class CollectionSnapshot<E> extends AbstractList<E> { /** Threshold for array reallocation during clear operation to prevent memory leaks. */ private static final int CLEAR_ARRAY_SIZE_THRESHOLD = 2048; @@ -79,6 +79,11 @@ public class CollectionSnapshot<E> extends AbstractCollection<E> { return size; } + @Override + public E get(int index) { + return array.get(index); + } + /** * Returns an iterator over the elements in this collection. The iterator is designed for * single-pass iteration and maintains its position through a shared index. diff --git a/java/fory-core/src/test/java/org/apache/fory/collection/CollectionSnapshotTest.java b/java/fory-core/src/test/java/org/apache/fory/collection/CollectionSnapshotTest.java index e0e05bd6b..de1c536de 100644 --- a/java/fory-core/src/test/java/org/apache/fory/collection/CollectionSnapshotTest.java +++ b/java/fory-core/src/test/java/org/apache/fory/collection/CollectionSnapshotTest.java @@ -107,4 +107,34 @@ public class CollectionSnapshotTest { assertEquals(result, newData); } } + + @Test + public void testGet() { + CollectionSnapshot<String> snapshot = new CollectionSnapshot<>(); + List<String> source = Arrays.asList("a", "b", "c"); + snapshot.setCollection(source); + + assertEquals(snapshot.get(0), "a"); + assertEquals(snapshot.get(1), "b"); + assertEquals(snapshot.get(2), "c"); + } + + @Test + public void testIndexedAccessAfterReuse() { + CollectionSnapshot<String> snapshot = new CollectionSnapshot<>(); + + // First use + snapshot.setCollection(Arrays.asList("a", "b", "c")); + assertEquals(snapshot.get(0), "a"); + assertEquals(snapshot.get(1), "b"); + assertEquals(snapshot.get(2), "c"); + + snapshot.clear(); + + // Second use + snapshot.setCollection(Arrays.asList("x", "y")); + assertEquals(snapshot.get(0), "x"); + assertEquals(snapshot.get(1), "y"); + assertEquals(snapshot.size(), 2); + } } diff --git a/java/fory-core/src/test/java/org/apache/fory/serializer/collection/CollectionSerializersTest.java b/java/fory-core/src/test/java/org/apache/fory/serializer/collection/CollectionSerializersTest.java index 59f12386f..607f1087a 100644 --- a/java/fory-core/src/test/java/org/apache/fory/serializer/collection/CollectionSerializersTest.java +++ b/java/fory-core/src/test/java/org/apache/fory/serializer/collection/CollectionSerializersTest.java @@ -62,6 +62,7 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.LongStream; import lombok.AllArgsConstructor; import lombok.Data; +import lombok.EqualsAndHashCode; import org.apache.fory.Fory; import org.apache.fory.ForyTestBase; import org.apache.fory.config.Language; @@ -389,6 +390,23 @@ public class CollectionSerializersTest extends ForyTestBase { CollectionSerializers.CopyOnWriteArrayListSerializer.class); } + @EqualsAndHashCode + public static class NestedCopyOnWriteArrayList { + private final CopyOnWriteArrayList<String> list; + + public NestedCopyOnWriteArrayList(CopyOnWriteArrayList<String> list) { + this.list = list; + } + } + + @Test + public void testCopyOnWriteArrayListNested() { + final CopyOnWriteArrayList<String> list = + new CopyOnWriteArrayList<>(Arrays.asList("a", "b", "c")); + NestedCopyOnWriteArrayList nestedObject = new NestedCopyOnWriteArrayList(list); + Assert.assertEquals(nestedObject, serDe(getJavaFory(), nestedObject)); + } + @Test(dataProvider = "foryCopyConfig") public void testCopyOnWriteArrayList(Fory fory) { final CopyOnWriteArrayList<Object> list = --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
