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]


Reply via email to