This is an automated email from the ASF dual-hosted git repository.
chaokunyang pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/fory.git
The following commit(s) were added to refs/heads/main by this push:
new 4777250f0 fix(java): auto-select child serializers for sorted
containers (#3552)
4777250f0 is described below
commit 4777250f0c8ffe02eafb5812dbf674c93486ecba
Author: Shawn Yang <[email protected]>
AuthorDate: Fri Apr 10 22:58:13 2026 +0800
fix(java): auto-select child serializers for sorted containers (#3552)
## Why?
Auto serializer selection was falling back to JDK-compatible serializers
for eligible sorted/container subclasses such as `TreeSet`, `TreeMap`,
`ConcurrentSkipListSet`, `ConcurrentSkipListMap`, and `PriorityQueue`.
That routed child containers through the inefficient
`ObjectStreamSerializer` path even when the optimized child-container
strategy could preserve both container state and subclass fields.
## What does this PR do?
- extend `ChildContainerSerializers` with constructor-aware child
serializers for sorted sets, sorted maps, and priority queues
- teach auto-selection to use those serializers for eligible subclasses
instead of the JDK-compatible fallback
- register the new serializers in the builtin serializer factory and
GraalVM defaults
- add regression coverage for auto-selected sorted/container subclasses
with preserved child state and comparator behavior
## Related issues
Closes #3521.
## AI Contribution Checklist
- [ ] Substantial AI assistance was used in this PR: `yes` / `no`
- [ ] If `yes`, I included a completed [AI Contribution
Checklist](https://github.com/apache/fory/blob/main/AI_POLICY.md#9-contributor-checklist-for-ai-assisted-prs)
in this PR description and the required `AI Usage Disclosure`.
- [ ] If `yes`, my PR description includes the required `ai_review`
summary and screenshot evidence of the final clean AI review results
from both fresh reviewers on the current PR diff or current HEAD after
the latest code changes.
## 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?
---
.../org/apache/fory/serializer/Serializers.java | 9 +
.../collection/ChildContainerSerializers.java | 426 ++++++++++++++++++++-
.../java/org/apache/fory/util/GraalvmSupport.java | 3 +
.../collection/ChildContainerSerializersTest.java | 147 +++++++
4 files changed, 565 insertions(+), 20 deletions(-)
diff --git
a/java/fory-core/src/main/java/org/apache/fory/serializer/Serializers.java
b/java/fory-core/src/main/java/org/apache/fory/serializer/Serializers.java
index 601523962..627075bc2 100644
--- a/java/fory-core/src/main/java/org/apache/fory/serializer/Serializers.java
+++ b/java/fory-core/src/main/java/org/apache/fory/serializer/Serializers.java
@@ -246,6 +246,15 @@ public class Serializers {
if (serializerClass == ChildContainerSerializers.ChildMapSerializer.class)
{
return new ChildContainerSerializers.ChildMapSerializer(typeResolver,
type);
}
+ if (serializerClass ==
ChildContainerSerializers.ChildSortedSetSerializer.class) {
+ return new
ChildContainerSerializers.ChildSortedSetSerializer(typeResolver, type);
+ }
+ if (serializerClass ==
ChildContainerSerializers.ChildPriorityQueueSerializer.class) {
+ return new
ChildContainerSerializers.ChildPriorityQueueSerializer(typeResolver, type);
+ }
+ if (serializerClass ==
ChildContainerSerializers.ChildSortedMapSerializer.class) {
+ return new
ChildContainerSerializers.ChildSortedMapSerializer(typeResolver, type);
+ }
if (serializerClass == SingletonCollectionSerializer.class) {
return new SingletonCollectionSerializer(typeResolver, type);
}
diff --git
a/java/fory-core/src/main/java/org/apache/fory/serializer/collection/ChildContainerSerializers.java
b/java/fory-core/src/main/java/org/apache/fory/serializer/collection/ChildContainerSerializers.java
index 68a3ef673..76b07cbb7 100644
---
a/java/fory-core/src/main/java/org/apache/fory/serializer/collection/ChildContainerSerializers.java
+++
b/java/fory-core/src/main/java/org/apache/fory/serializer/collection/ChildContainerSerializers.java
@@ -21,20 +21,29 @@ package org.apache.fory.serializer.collection;
import static org.apache.fory.collection.Collections.ofHashSet;
+import java.lang.invoke.MethodHandle;
import java.lang.reflect.Field;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
+import java.util.PriorityQueue;
import java.util.Set;
+import java.util.SortedMap;
+import java.util.SortedSet;
+import java.util.TreeMap;
+import java.util.TreeSet;
import java.util.Vector;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentSkipListMap;
+import java.util.concurrent.ConcurrentSkipListSet;
import org.apache.fory.builder.LayerMarkerClassGenerator;
import org.apache.fory.context.CopyContext;
import org.apache.fory.context.MetaReadContext;
@@ -65,26 +74,35 @@ import org.apache.fory.util.Preconditions;
public class ChildContainerSerializers {
public static Class<? extends Serializer>
getCollectionSerializerClass(Class<?> cls) {
- if (ChildCollectionSerializer.superClasses.contains(cls)) {
+ if (ChildCollectionSerializer.superClasses.contains(cls)
+ || ChildSortedSetSerializer.superClasses.contains(cls)
+ || ChildPriorityQueueSerializer.superClasses.contains(cls)) {
return null;
}
if (ClassResolver.useReplaceResolveSerializer(cls)) {
return null;
}
- // Collection/Map must have default constructor to be invoked by fory,
otherwise created object
- // can't be used to adding elements.
- // For example: `new ArrayList<Integer> { add(1);}`, without default
constructor, created
- // list will have elementData as null, adding elements will raise NPE.
- if (!ReflectionUtils.hasNoArgConstructor(cls)) {
- return null;
- }
+ Class<?> childClass = cls;
while (cls != Object.class) {
if (ChildCollectionSerializer.superClasses.contains(cls)) {
+ if (!ReflectionUtils.hasNoArgConstructor(childClass)) {
+ return null;
+ }
if (cls == ArrayList.class) {
return ChildArrayListSerializer.class;
} else {
return ChildCollectionSerializer.class;
}
+ } else if (ChildSortedSetSerializer.superClasses.contains(cls)) {
+ if (ChildSortedSetSerializer.supports(childClass)) {
+ return ChildSortedSetSerializer.class;
+ }
+ return null;
+ } else if (ChildPriorityQueueSerializer.superClasses.contains(cls)) {
+ if (ChildPriorityQueueSerializer.supports(childClass)) {
+ return ChildPriorityQueueSerializer.class;
+ }
+ return null;
} else {
if (JavaSerializer.getReadRefMethod(cls, false) != null
|| JavaSerializer.getWriteObjectMethod(cls, false) != null) {
@@ -97,22 +115,25 @@ public class ChildContainerSerializers {
}
public static Class<? extends Serializer> getMapSerializerClass(Class<?>
cls) {
- if (ChildMapSerializer.superClasses.contains(cls)) {
+ if (ChildMapSerializer.superClasses.contains(cls)
+ || ChildSortedMapSerializer.superClasses.contains(cls)) {
return null;
}
if (ClassResolver.useReplaceResolveSerializer(cls)) {
return null;
}
- // Collection/Map must have default constructor to be invoked by fory,
otherwise created object
- // can't be used to adding elements.
- // For example: `new ArrayList<Integer> { add(1);}`, without default
constructor, created
- // list will have elementData as null, adding elements will raise NPE.
- if (!ReflectionUtils.hasNoArgConstructor(cls)) {
- return null;
- }
+ Class<?> childClass = cls;
while (cls != Object.class) {
if (ChildMapSerializer.superClasses.contains(cls)) {
+ if (!ReflectionUtils.hasNoArgConstructor(childClass)) {
+ return null;
+ }
return ChildMapSerializer.class;
+ } else if (ChildSortedMapSerializer.superClasses.contains(cls)) {
+ if (ChildSortedMapSerializer.supports(childClass)) {
+ return ChildSortedMapSerializer.class;
+ }
+ return null;
} else {
if (JavaSerializer.getReadRefMethod(cls, false) != null
|| JavaSerializer.getWriteObjectMethod(cls, false) != null) {
@@ -137,9 +158,16 @@ public class ChildContainerSerializers {
);
protected SerializationFieldInfo[] fieldInfos;
protected final Serializer[] slotsSerializers;
+ protected final Set<Class<?>> slotSuperClasses;
public ChildCollectionSerializer(TypeResolver typeResolver, Class<T> cls) {
+ this(typeResolver, cls, superClasses);
+ }
+
+ protected ChildCollectionSerializer(
+ TypeResolver typeResolver, Class<T> cls, Set<Class<?>> superClasses) {
super(typeResolver, cls);
+ this.slotSuperClasses = superClasses;
slotsSerializers = buildSlotsSerializers(typeResolver, superClasses,
cls);
}
@@ -162,12 +190,21 @@ public class ChildContainerSerializers {
@Override
public Collection newCollection(CopyContext copyContext, Collection
originCollection) {
Collection newCollection = super.newCollection(copyContext,
originCollection);
+ copyChildFields(copyContext, originCollection, newCollection);
+ return newCollection;
+ }
+
+ protected final void readChildFields(ReadContext readContext, Object
collection) {
+ readAndSetFields(readContext, typeResolver, collection,
slotsSerializers);
+ }
+
+ protected final void copyChildFields(
+ CopyContext copyContext, Object originCollection, Object
newCollection) {
if (fieldInfos == null) {
- List<Field> fields =
ReflectionUtils.getFieldsWithoutSuperClasses(type, superClasses);
+ List<Field> fields =
ReflectionUtils.getFieldsWithoutSuperClasses(type, slotSuperClasses);
fieldInfos = FieldGroups.buildFieldsInfo(typeResolver,
fields).allFields;
}
AbstractObjectSerializer.copyFields(copyContext, fieldInfos,
originCollection, newCollection);
- return newCollection;
}
}
@@ -187,6 +224,104 @@ public class ChildContainerSerializers {
}
}
+ public static final class ChildSortedSetSerializer<T extends SortedSet>
+ extends ChildCollectionSerializer<T> {
+ public static final Set<Class<?>> superClasses =
+ ofHashSet(TreeSet.class, ConcurrentSkipListSet.class);
+ private final SortedSetSubclassFactory<T> subclassFactory;
+
+ public ChildSortedSetSerializer(TypeResolver typeResolver, Class<T> cls) {
+ super(typeResolver, cls, superClasses);
+ subclassFactory = new SortedSetSubclassFactory<>(cls);
+ }
+
+ static boolean supports(Class<?> cls) {
+ return SortedSetSubclassFactory.supports(cls);
+ }
+
+ @Override
+ public Collection onCollectionWrite(WriteContext writeContext, T value) {
+ MemoryBuffer buffer = writeContext.getBuffer();
+ buffer.writeVarUint32Small7(value.size());
+ writeContext.writeRef(value.comparator());
+ for (Serializer slotsSerializer : slotsSerializers) {
+ slotsSerializer.write(writeContext, value);
+ }
+ return value;
+ }
+
+ @Override
+ public T newCollection(ReadContext readContext) {
+ MemoryBuffer buffer = readContext.getBuffer();
+ int numElements = buffer.readVarUint32Small7();
+ setNumElements(numElements);
+ int refId = readContext.lastPreservedRefId();
+ Comparator comparator = (Comparator) readContext.readRef();
+ T collection = subclassFactory.newCollection(comparator);
+ readContext.setReadRef(refId, collection);
+ readChildFields(readContext, collection);
+ return collection;
+ }
+
+ @Override
+ public Collection newCollection(CopyContext copyContext, Collection
originCollection) {
+ T newCollection =
+ subclassFactory.newCollection(
+ copyContext.copyObject(((SortedSet<?>)
originCollection).comparator()));
+ copyChildFields(copyContext, originCollection, newCollection);
+ return newCollection;
+ }
+ }
+
+ public static final class ChildPriorityQueueSerializer<T extends
PriorityQueue>
+ extends ChildCollectionSerializer<T> {
+ public static final Set<Class<?>> superClasses =
ofHashSet(PriorityQueue.class);
+ private final PriorityQueueSubclassFactory<T> subclassFactory;
+
+ public ChildPriorityQueueSerializer(TypeResolver typeResolver, Class<T>
cls) {
+ super(typeResolver, cls, superClasses);
+ subclassFactory = new PriorityQueueSubclassFactory<>(cls);
+ }
+
+ static boolean supports(Class<?> cls) {
+ return PriorityQueueSubclassFactory.supports(cls);
+ }
+
+ @Override
+ public Collection onCollectionWrite(WriteContext writeContext, T value) {
+ MemoryBuffer buffer = writeContext.getBuffer();
+ buffer.writeVarUint32Small7(value.size());
+ writeContext.writeRef(value.comparator());
+ for (Serializer slotsSerializer : slotsSerializers) {
+ slotsSerializer.write(writeContext, value);
+ }
+ return value;
+ }
+
+ @Override
+ public T newCollection(ReadContext readContext) {
+ MemoryBuffer buffer = readContext.getBuffer();
+ int numElements = buffer.readVarUint32Small7();
+ setNumElements(numElements);
+ int refId = readContext.lastPreservedRefId();
+ Comparator comparator = (Comparator) readContext.readRef();
+ T collection = subclassFactory.newCollection(comparator, numElements);
+ readContext.setReadRef(refId, collection);
+ readChildFields(readContext, collection);
+ return collection;
+ }
+
+ @Override
+ public Collection newCollection(CopyContext copyContext, Collection
originCollection) {
+ T newCollection =
+ subclassFactory.newCollection(
+ copyContext.copyObject(((PriorityQueue<?>)
originCollection).comparator()),
+ originCollection.size());
+ copyChildFields(copyContext, originCollection, newCollection);
+ return newCollection;
+ }
+ }
+
/**
* Serializer for subclasses of {@link ChildMapSerializer#superClasses} if
no jdk custom
* serialization in those classes.
@@ -197,11 +332,18 @@ public class ChildContainerSerializers {
HashMap.class, LinkedHashMap.class, ConcurrentHashMap.class
// TreeMap/ConcurrentSkipListMap need comparator as constructor
argument
);
- private final Serializer[] slotsSerializers;
+ protected final Serializer[] slotsSerializers;
private SerializationFieldInfo[] fieldInfos;
+ protected final Set<Class<?>> slotSuperClasses;
public ChildMapSerializer(TypeResolver typeResolver, Class<T> cls) {
+ this(typeResolver, cls, superClasses);
+ }
+
+ protected ChildMapSerializer(
+ TypeResolver typeResolver, Class<T> cls, Set<Class<?>> superClasses) {
super(typeResolver, cls);
+ this.slotSuperClasses = superClasses;
slotsSerializers = buildSlotsSerializers(typeResolver, superClasses,
cls);
}
@@ -225,15 +367,259 @@ public class ChildContainerSerializers {
@Override
public Map newMap(CopyContext copyContext, Map originMap) {
Map newMap = super.newMap(copyContext, originMap);
+ copyChildFields(copyContext, originMap, newMap);
+ return newMap;
+ }
+
+ protected final void readChildFields(ReadContext readContext, Object map) {
+ readAndSetFields(readContext, typeResolver, map, slotsSerializers);
+ }
+
+ protected final void copyChildFields(CopyContext copyContext, Object
originMap, Object newMap) {
if (fieldInfos == null || fieldInfos.length == 0) {
- List<Field> fields =
ReflectionUtils.getFieldsWithoutSuperClasses(type, superClasses);
+ List<Field> fields =
ReflectionUtils.getFieldsWithoutSuperClasses(type, slotSuperClasses);
fieldInfos = FieldGroups.buildFieldsInfo(typeResolver,
fields).allFields;
}
AbstractObjectSerializer.copyFields(copyContext, fieldInfos, originMap,
newMap);
+ }
+ }
+
+ public static final class ChildSortedMapSerializer<T extends SortedMap>
+ extends ChildMapSerializer<T> {
+ public static final Set<Class<?>> superClasses =
+ ofHashSet(TreeMap.class, ConcurrentSkipListMap.class);
+ private final SortedMapSubclassFactory<T> subclassFactory;
+
+ public ChildSortedMapSerializer(TypeResolver typeResolver, Class<T> cls) {
+ super(typeResolver, cls, superClasses);
+ subclassFactory = new SortedMapSubclassFactory<>(cls);
+ }
+
+ static boolean supports(Class<?> cls) {
+ return SortedMapSubclassFactory.supports(cls);
+ }
+
+ @Override
+ public Map onMapWrite(WriteContext writeContext, T value) {
+ MemoryBuffer buffer = writeContext.getBuffer();
+ buffer.writeVarUint32Small7(value.size());
+ writeContext.writeRef(value.comparator());
+ for (Serializer slotsSerializer : slotsSerializers) {
+ slotsSerializer.write(writeContext, value);
+ }
+ return value;
+ }
+
+ @Override
+ public Map newMap(ReadContext readContext) {
+ MemoryBuffer buffer = readContext.getBuffer();
+ int numElements = buffer.readVarUint32Small7();
+ setNumElements(numElements);
+ int refId = readContext.lastPreservedRefId();
+ Comparator comparator = (Comparator) readContext.readRef();
+ T map = subclassFactory.newMap(comparator);
+ readContext.setReadRef(refId, map);
+ readChildFields(readContext, map);
+ return map;
+ }
+
+ @Override
+ public Map newMap(CopyContext copyContext, Map originMap) {
+ T newMap =
+ subclassFactory.newMap(
+ copyContext.copyObject(((SortedMap<?, ?>)
originMap).comparator()));
+ copyChildFields(copyContext, originMap, newMap);
return newMap;
}
}
+ private static final class SortedSetSubclassFactory<T extends SortedSet> {
+ private final Class<T> type;
+ private final MethodHandle noArgConstructor;
+ private final MethodHandle comparatorConstructor;
+ private final MethodHandle sortedSetConstructor;
+ private final MethodHandle collectionConstructor;
+
+ private SortedSetSubclassFactory(Class<T> type) {
+ this.type = type;
+ noArgConstructor = ReflectionUtils.getCtrHandle(type, false);
+ comparatorConstructor = findConstructorHandle(type, Comparator.class);
+ sortedSetConstructor = findConstructorHandle(type, SortedSet.class);
+ collectionConstructor = findConstructorHandle(type, Collection.class);
+ }
+
+ private static boolean supports(Class<?> cls) {
+ return ReflectionUtils.getCtrHandle(cls, false) != null
+ || findConstructorHandle(cls, Comparator.class) != null
+ || findConstructorHandle(cls, SortedSet.class) != null
+ || findConstructorHandle(cls, Collection.class) != null;
+ }
+
+ private T newCollection(Comparator comparator) {
+ if (type == TreeSet.class) {
+ return (T) new TreeSet(comparator);
+ }
+ if (type == ConcurrentSkipListSet.class) {
+ return (T) new ConcurrentSkipListSet(comparator);
+ }
+ if (comparatorConstructor != null) {
+ return invokeConstructor(comparatorConstructor, comparator);
+ }
+ if (comparator != null && sortedSetConstructor != null) {
+ return invokeConstructor(sortedSetConstructor, new
TreeSet(comparator));
+ }
+ if (noArgConstructor != null) {
+ return invokeConstructor(noArgConstructor);
+ }
+ if (comparator == null && collectionConstructor != null) {
+ return invokeConstructor(collectionConstructor,
Collections.emptyList());
+ }
+ throw unsupportedConstructor(type);
+ }
+ }
+
+ private static final class SortedMapSubclassFactory<T extends SortedMap> {
+ private final Class<T> type;
+ private final MethodHandle noArgConstructor;
+ private final MethodHandle comparatorConstructor;
+ private final MethodHandle sortedMapConstructor;
+ private final MethodHandle mapConstructor;
+
+ private SortedMapSubclassFactory(Class<T> type) {
+ this.type = type;
+ noArgConstructor = ReflectionUtils.getCtrHandle(type, false);
+ comparatorConstructor = findConstructorHandle(type, Comparator.class);
+ sortedMapConstructor = findConstructorHandle(type, SortedMap.class);
+ mapConstructor = findConstructorHandle(type, Map.class);
+ }
+
+ private static boolean supports(Class<?> cls) {
+ return ReflectionUtils.getCtrHandle(cls, false) != null
+ || findConstructorHandle(cls, Comparator.class) != null
+ || findConstructorHandle(cls, SortedMap.class) != null
+ || findConstructorHandle(cls, Map.class) != null;
+ }
+
+ private T newMap(Comparator comparator) {
+ if (type == TreeMap.class) {
+ return (T) new TreeMap(comparator);
+ }
+ if (type == ConcurrentSkipListMap.class) {
+ return (T) new ConcurrentSkipListMap(comparator);
+ }
+ if (comparatorConstructor != null) {
+ return invokeConstructor(comparatorConstructor, comparator);
+ }
+ if (comparator != null && sortedMapConstructor != null) {
+ return invokeConstructor(sortedMapConstructor, new
TreeMap(comparator));
+ }
+ if (noArgConstructor != null) {
+ return invokeConstructor(noArgConstructor);
+ }
+ if (comparator == null && mapConstructor != null) {
+ return invokeConstructor(mapConstructor, Collections.emptyMap());
+ }
+ throw unsupportedConstructor(type);
+ }
+ }
+
+ private static final class PriorityQueueSubclassFactory<T extends
PriorityQueue> {
+ private final Class<T> type;
+ private final MethodHandle noArgConstructor;
+ private final MethodHandle capacityConstructor;
+ private final MethodHandle comparatorConstructor;
+ private final MethodHandle capacityComparatorConstructor;
+ private final MethodHandle priorityQueueConstructor;
+ private final MethodHandle sortedSetConstructor;
+ private final MethodHandle collectionConstructor;
+
+ private PriorityQueueSubclassFactory(Class<T> type) {
+ this.type = type;
+ noArgConstructor = ReflectionUtils.getCtrHandle(type, false);
+ capacityConstructor = findConstructorHandle(type, int.class);
+ comparatorConstructor = findConstructorHandle(type, Comparator.class);
+ capacityComparatorConstructor = findConstructorHandle(type, int.class,
Comparator.class);
+ priorityQueueConstructor = findConstructorHandle(type,
PriorityQueue.class);
+ sortedSetConstructor = findConstructorHandle(type, SortedSet.class);
+ collectionConstructor = findConstructorHandle(type, Collection.class);
+ }
+
+ private static boolean supports(Class<?> cls) {
+ return ReflectionUtils.getCtrHandle(cls, false) != null
+ || findConstructorHandle(cls, int.class) != null
+ || findConstructorHandle(cls, Comparator.class) != null
+ || findConstructorHandle(cls, int.class, Comparator.class) != null
+ || findConstructorHandle(cls, PriorityQueue.class) != null
+ || findConstructorHandle(cls, SortedSet.class) != null
+ || findConstructorHandle(cls, Collection.class) != null;
+ }
+
+ private T newCollection(Comparator comparator, int numElements) {
+ int capacity = Math.max(numElements, 1);
+ if (type == PriorityQueue.class) {
+ return (T) new PriorityQueue(capacity, comparator);
+ }
+ if (capacityComparatorConstructor != null) {
+ return invokeConstructor(capacityComparatorConstructor, capacity,
comparator);
+ }
+ if (comparator != null) {
+ if (comparatorConstructor != null) {
+ return invokeConstructor(comparatorConstructor, comparator);
+ }
+ if (priorityQueueConstructor != null) {
+ return invokeConstructor(
+ priorityQueueConstructor, new PriorityQueue(capacity,
comparator));
+ }
+ if (sortedSetConstructor != null) {
+ return invokeConstructor(sortedSetConstructor, new
TreeSet(comparator));
+ }
+ if (collectionConstructor != null) {
+ return invokeConstructor(collectionConstructor, new
TreeSet(comparator));
+ }
+ }
+ if (noArgConstructor != null) {
+ return invokeConstructor(noArgConstructor);
+ }
+ if (capacityConstructor != null) {
+ return invokeConstructor(capacityConstructor, capacity);
+ }
+ if (priorityQueueConstructor != null) {
+ return invokeConstructor(priorityQueueConstructor, new
PriorityQueue(capacity, comparator));
+ }
+ if (sortedSetConstructor != null) {
+ return invokeConstructor(sortedSetConstructor, new
TreeSet(comparator));
+ }
+ if (collectionConstructor != null) {
+ return invokeConstructor(collectionConstructor,
Collections.emptyList());
+ }
+ throw unsupportedConstructor(type);
+ }
+ }
+
+ private static MethodHandle findConstructorHandle(Class<?> cls, Class<?>...
parameterTypes) {
+ try {
+ return ReflectionUtils.getCtrHandle(cls, parameterTypes);
+ } catch (Throwable t) {
+ return null;
+ }
+ }
+
+ private static <T> T invokeConstructor(MethodHandle constructor, Object...
args) {
+ try {
+ return (T) constructor.invokeWithArguments(args);
+ } catch (Throwable t) {
+ throw new RuntimeException(t);
+ }
+ }
+
+ private static UnsupportedOperationException unsupportedConstructor(Class<?>
cls) {
+ return new UnsupportedOperationException(
+ "Class "
+ + cls.getName()
+ + " requires a supported child-container constructor for
auto-selected optimized"
+ + " serialization");
+ }
+
private static <T> Serializer[] buildSlotsSerializers(
TypeResolver typeResolver, Set<Class<?>> superClasses, Class<T> cls) {
Preconditions.checkArgument(!superClasses.contains(cls));
diff --git
a/java/fory-core/src/main/java/org/apache/fory/util/GraalvmSupport.java
b/java/fory-core/src/main/java/org/apache/fory/util/GraalvmSupport.java
index 388f06905..07b3912eb 100644
--- a/java/fory-core/src/main/java/org/apache/fory/util/GraalvmSupport.java
+++ b/java/fory-core/src/main/java/org/apache/fory/util/GraalvmSupport.java
@@ -96,6 +96,9 @@ public class GraalvmSupport {
registerDefaultSerializerClass(ChildContainerSerializers.ChildArrayListSerializer.class);
registerDefaultSerializerClass(ChildContainerSerializers.ChildCollectionSerializer.class);
registerDefaultSerializerClass(ChildContainerSerializers.ChildMapSerializer.class);
+
registerDefaultSerializerClass(ChildContainerSerializers.ChildSortedSetSerializer.class);
+
registerDefaultSerializerClass(ChildContainerSerializers.ChildPriorityQueueSerializer.class);
+
registerDefaultSerializerClass(ChildContainerSerializers.ChildSortedMapSerializer.class);
registerDefaultSerializerClass(SingletonCollectionSerializer.class);
registerDefaultSerializerClass(SingletonMapSerializer.class);
registerDefaultSerializerClass(SingletonObjectSerializer.class);
diff --git
a/java/fory-core/src/test/java/org/apache/fory/serializer/collection/ChildContainerSerializersTest.java
b/java/fory-core/src/test/java/org/apache/fory/serializer/collection/ChildContainerSerializersTest.java
index 9cd171827..5169e822f 100644
---
a/java/fory-core/src/test/java/org/apache/fory/serializer/collection/ChildContainerSerializersTest.java
+++
b/java/fory-core/src/test/java/org/apache/fory/serializer/collection/ChildContainerSerializersTest.java
@@ -24,15 +24,22 @@ import com.google.common.collect.ImmutableMap;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
+import java.util.PriorityQueue;
+import java.util.SortedMap;
+import java.util.SortedSet;
+import java.util.TreeMap;
import java.util.TreeSet;
import java.util.Vector;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentSkipListMap;
+import java.util.concurrent.ConcurrentSkipListSet;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import lombok.AllArgsConstructor;
@@ -413,6 +420,146 @@ public class ChildContainerSerializersTest extends
ForyTestBase {
Assert.assertEquals(deserialized, holder);
}
+ private static final class LengthThenNaturalComparator implements
Comparator<String> {
+ @Override
+ public int compare(String left, String right) {
+ int delta = Integer.compare(left.length(), right.length());
+ if (delta != 0) {
+ return delta;
+ }
+ return left.compareTo(right);
+ }
+ }
+
+ public static class AutoChildTreeSet extends TreeSet<String> {
+ private String state;
+
+ public AutoChildTreeSet() {}
+ }
+
+ public static class AutoChildConcurrentSkipListSet extends
ConcurrentSkipListSet<String> {
+ private String state;
+
+ public AutoChildConcurrentSkipListSet(SortedSet<String> values) {
+ super(values);
+ }
+ }
+
+ public static class AutoChildPriorityQueue extends PriorityQueue<String> {
+ private String state;
+
+ public AutoChildPriorityQueue(SortedSet<String> values) {
+ super(values);
+ }
+ }
+
+ public static class AutoChildTreeMap extends TreeMap<String, String> {
+ private String state;
+
+ public AutoChildTreeMap() {}
+ }
+
+ public static class AutoChildConcurrentSkipListMap extends
ConcurrentSkipListMap<String, String> {
+ private String state;
+
+ public AutoChildConcurrentSkipListMap(SortedMap<String, String> values) {
+ super(values);
+ }
+ }
+
+ private static SortedSet<String> newComparatorSortedSetSource() {
+ TreeSet<String> set = new TreeSet<>(new LengthThenNaturalComparator());
+ set.addAll(ImmutableList.of("bbb", "a", "cc"));
+ return set;
+ }
+
+ private static SortedMap<String, String> newComparatorSortedMapSource() {
+ TreeMap<String, String> map = new TreeMap<>(new
LengthThenNaturalComparator());
+ map.put("bbb", "B");
+ map.put("a", "A");
+ map.put("cc", "C");
+ return map;
+ }
+
+ private static List<String> drainPriorityQueue(PriorityQueue<String> queue) {
+ PriorityQueue<String> copy = new PriorityQueue<>(queue);
+ List<String> values = new ArrayList<>();
+ while (!copy.isEmpty()) {
+ values.add(copy.poll());
+ }
+ return values;
+ }
+
+ @Test(dataProvider = "foryConfig")
+ public void testAutoSelectsOptimizedSortedCollectionChildSerializers(Fory
fory) {
+ Assert.assertEquals(
+ fory.getTypeResolver().getSerializerClass(AutoChildTreeSet.class),
+ ChildContainerSerializers.ChildSortedSetSerializer.class);
+ AutoChildTreeSet treeSet = new AutoChildTreeSet();
+ treeSet.state = "tree-state";
+ treeSet.addAll(ImmutableList.of("b", "a", "c"));
+ AutoChildTreeSet treeSetCopy = serDe(fory, treeSet);
+ Assert.assertEquals(treeSetCopy, treeSet);
+ Assert.assertEquals(treeSetCopy.state, treeSet.state);
+ Assert.assertNull(treeSetCopy.comparator());
+
+ Assert.assertEquals(
+
fory.getTypeResolver().getSerializerClass(AutoChildConcurrentSkipListSet.class),
+ ChildContainerSerializers.ChildSortedSetSerializer.class);
+ AutoChildConcurrentSkipListSet skipListSet =
+ new AutoChildConcurrentSkipListSet(newComparatorSortedSetSource());
+ skipListSet.state = "skip-state";
+ AutoChildConcurrentSkipListSet skipListSetCopy = serDe(fory, skipListSet);
+ Assert.assertEquals(skipListSetCopy, skipListSet);
+ Assert.assertEquals(skipListSetCopy.state, skipListSet.state);
+ Assert.assertEquals(new ArrayList<>(skipListSetCopy), new
ArrayList<>(skipListSet));
+ Assert.assertNotNull(skipListSetCopy.comparator());
+ Assert.assertEquals(skipListSetCopy.comparator().getClass(),
LengthThenNaturalComparator.class);
+
+ Assert.assertEquals(
+
fory.getTypeResolver().getSerializerClass(AutoChildPriorityQueue.class),
+ ChildContainerSerializers.ChildPriorityQueueSerializer.class);
+ AutoChildPriorityQueue priorityQueue =
+ new AutoChildPriorityQueue(newComparatorSortedSetSource());
+ priorityQueue.state = "queue-state";
+ AutoChildPriorityQueue priorityQueueCopy = serDe(fory, priorityQueue);
+ Assert.assertEquals(priorityQueueCopy.state, priorityQueue.state);
+ Assert.assertEquals(drainPriorityQueue(priorityQueueCopy),
drainPriorityQueue(priorityQueue));
+ Assert.assertNotNull(priorityQueueCopy.comparator());
+ Assert.assertEquals(
+ priorityQueueCopy.comparator().getClass(),
LengthThenNaturalComparator.class);
+ }
+
+ @Test(dataProvider = "foryConfig")
+ public void testAutoSelectsOptimizedSortedMapChildSerializers(Fory fory) {
+ Assert.assertEquals(
+ fory.getTypeResolver().getSerializerClass(AutoChildTreeMap.class),
+ ChildContainerSerializers.ChildSortedMapSerializer.class);
+ AutoChildTreeMap treeMap = new AutoChildTreeMap();
+ treeMap.state = "tree-map-state";
+ treeMap.put("b", "B");
+ treeMap.put("a", "A");
+ treeMap.put("c", "C");
+ AutoChildTreeMap treeMapCopy = serDe(fory, treeMap);
+ Assert.assertEquals(treeMapCopy, treeMap);
+ Assert.assertEquals(treeMapCopy.state, treeMap.state);
+ Assert.assertNull(treeMapCopy.comparator());
+
+ Assert.assertEquals(
+
fory.getTypeResolver().getSerializerClass(AutoChildConcurrentSkipListMap.class),
+ ChildContainerSerializers.ChildSortedMapSerializer.class);
+ AutoChildConcurrentSkipListMap skipListMap =
+ new AutoChildConcurrentSkipListMap(newComparatorSortedMapSource());
+ skipListMap.state = "skip-map-state";
+ AutoChildConcurrentSkipListMap skipListMapCopy = serDe(fory, skipListMap);
+ Assert.assertEquals(skipListMapCopy, skipListMap);
+ Assert.assertEquals(skipListMapCopy.state, skipListMap.state);
+ Assert.assertEquals(
+ new ArrayList<>(skipListMapCopy.keySet()), new
ArrayList<>(skipListMap.keySet()));
+ Assert.assertNotNull(skipListMapCopy.comparator());
+ Assert.assertEquals(skipListMapCopy.comparator().getClass(),
LengthThenNaturalComparator.class);
+ }
+
/* Mixed collection subclass test (TreeSet + HashMap subclasses) */
public static class ChildTreeSet extends TreeSet<ChildTreeSetEntry> {
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]