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 14f9f4d6a fix(java): preserve ConcurrentSkipListSet comparator on copy 
(#3520)
14f9f4d6a is described below

commit 14f9f4d6aef5ef75c248c19e0e9de671b95a8c0f
Author: Sebastian Mandrean <[email protected]>
AuthorDate: Fri Mar 27 13:22:40 2026 +0100

    fix(java): preserve ConcurrentSkipListSet comparator on copy (#3520)
    
    ## Why?
    
    As a follow-up to #3342 and #3344, I wanted to make sure there is test
    coverage for all the public constructors of the different Map/Set
    classes supported by the serializers.
    
    That exposed a copy-path bug in `ConcurrentSkipListSetSerializer`
    (reported as #3519) which this PR addresses.
    
    The deserialize path already preserves the comparator, but
    `fory.copy(...)` rebuilt `ConcurrentSkipListSet` with the default
    constructor path, which dropped the comparator and silently changed
    ordering semantics. This is easy to miss because `Set.equals(...)` still
    passes even when iteration order changes.
    
    ## What does this PR do?
    
    Its kind-of two part: fix the bug, and add more test coverage.
    Specifically:
    
    - Preserve the comparator when copying `ConcurrentSkipListSet` by
    overriding the collection copy construction path in
    `ConcurrentSkipListSetSerializer`.
    - Add constructor-matrix coverage for:
      - `TreeSet`
      - `TreeMap`
      - `ConcurrentSkipListSet`
      - `ConcurrentSkipListMap`
      - `PriorityQueue`
    - Exercise both round-trip deserialize and `fory.copy(...)` behavior for
    the relevant public constructors.
    - Assert concrete runtime type, comparator nullability, and observable
    ordering semantics.
    - Use queue-specific assertions for `PriorityQueue` by comparing drained
    poll order instead of relying on `equals(...)`.
    
    ## Related issues
    
    - Fixes #3519
    - Related to #3337
    - Related to #3343
    
    ## AI Contribution Checklist
    
    - [x] Substantial AI assistance was used in this PR: `yes` / `no`
    - [x] 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`.
    - [x] If `yes`, I can explain and defend all important changes without
    AI help.
    - [x] If `yes`, I reviewed AI-assisted code changes line by line before
    submission.
    - [x] If `yes`, I ran adequate human verification and recorded evidence
    (checks run locally or in CI, pass/fail summary, and confirmation I
    reviewed results).
    - [x] If `yes`, I added/updated tests and specs where required.
    - [x] If `yes`, I validated protocol/performance impacts with evidence
    when applicable.
    - [x] If `yes`, I verified licensing and provenance compliance.
    
    AI Usage Disclosure
    - substantial_ai_assistance: yes
    - scope: code drafting, test design
    - affected_files_or_subsystems: java/fory-core collection serializers
    and java/fory-core collection/map serializer tests
    - human_verification: local verification evidence available: `cd java &&
    ENABLE_FORY_DEBUG_OUTPUT=1 mvn -T16 -pl fory-core
    
-Dtest=org.apache.fory.serializer.collection.CollectionSerializersTest,org.apache.fory.serializer.collection.MapSerializersTest
    test` -> pass (237 tests, 0 failures)
    - performance_verification: no dedicated benchmark run; runtime change
    is limited to the `fory.copy(...)` path for `ConcurrentSkipListSet`,
    while serialize/deserialize hot paths are unchanged; the added
    comparator copy matches existing `PriorityQueue` and
    `ConcurrentSkipListMap` copy behavior
    - provenance_license_confirmation: repository-local
    Apache-2.0-compatible changes only; no third-party code introduced
    
    ## 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
    
    No dedicated benchmark was run.
    
    This PR is a correctness fix plus test coverage expansion, not a
    performance optimization:
    - The runtime code change only affects the `fory.copy(...)` path for
    `ConcurrentSkipListSet`.
    - Serialization and deserialization hot paths are unchanged.
    - The added work is one comparator copy per copied
    `ConcurrentSkipListSet`, which is expected to be negligible relative to
    element copy and skip-list insertion work, and is consistent with
    existing `PriorityQueue` and `ConcurrentSkipListMap` copy behavior.
    
    Co-authored-by: Shawn Yang <[email protected]>
---
 .../collection/CollectionSerializers.java          |   7 +
 .../collection/CollectionSerializersTest.java      | 322 +++++++++++++++++++++
 .../serializer/collection/MapSerializersTest.java  | 195 +++++++++++++
 3 files changed, 524 insertions(+)

diff --git 
a/java/fory-core/src/main/java/org/apache/fory/serializer/collection/CollectionSerializers.java
 
b/java/fory-core/src/main/java/org/apache/fory/serializer/collection/CollectionSerializers.java
index 92e0a8b93..53a115d0d 100644
--- 
a/java/fory-core/src/main/java/org/apache/fory/serializer/collection/CollectionSerializers.java
+++ 
b/java/fory-core/src/main/java/org/apache/fory/serializer/collection/CollectionSerializers.java
@@ -476,6 +476,13 @@ public class CollectionSerializers {
       refResolver.setReadObject(refId, skipListSet);
       return skipListSet;
     }
+
+    @Override
+    public Collection newCollection(Collection originCollection) {
+      Comparator comparator =
+          fory.copyObject(((ConcurrentSkipListSet) 
originCollection).comparator());
+      return new ConcurrentSkipListSet(comparator);
+    }
   }
 
   public static final class SetFromMapSerializer extends 
CollectionSerializer<Set<?>> {
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 fb292f33e..cd56bdd5c 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
@@ -59,6 +59,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.CopyOnWriteArraySet;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Supplier;
 import java.util.stream.LongStream;
 import lombok.AllArgsConstructor;
 import lombok.Data;
@@ -78,6 +79,57 @@ import org.testng.collections.Maps;
 
 @SuppressWarnings("rawtypes")
 public class CollectionSerializersTest extends ForyTestBase {
+  private static final List<String> SORTED_COLLECTION_INPUT = 
ImmutableList.of("bbb", "a", "cc");
+  private static final List<String> NATURAL_SORT_ORDER = ImmutableList.of("a", 
"bbb", "cc");
+  private static final List<String> LENGTH_SORT_ORDER = ImmutableList.of("a", 
"cc", "bbb");
+
+  private static final class LengthThenNaturalComparator
+      implements Comparator<String>, Serializable {
+    @Override
+    public int compare(String left, String right) {
+      int delta = left.length() - right.length();
+      return delta != 0 ? delta : left.compareTo(right);
+    }
+  }
+
+  private static final class SortedSetConstructorCase {
+    private final String name;
+    private final Class<?> expectedType;
+    private final boolean comparatorExpected;
+    private final List<String> expectedOrder;
+    private final Supplier<SortedSet<String>> factory;
+
+    private SortedSetConstructorCase(
+        String name,
+        Class<?> expectedType,
+        boolean comparatorExpected,
+        List<String> expectedOrder,
+        Supplier<SortedSet<String>> factory) {
+      this.name = name;
+      this.expectedType = expectedType;
+      this.comparatorExpected = comparatorExpected;
+      this.expectedOrder = expectedOrder;
+      this.factory = factory;
+    }
+  }
+
+  private static final class PriorityQueueConstructorCase {
+    private final String name;
+    private final boolean comparatorExpected;
+    private final List<String> expectedOrder;
+    private final Supplier<PriorityQueue<String>> factory;
+
+    private PriorityQueueConstructorCase(
+        String name,
+        boolean comparatorExpected,
+        List<String> expectedOrder,
+        Supplier<PriorityQueue<String>> factory) {
+      this.name = name;
+      this.comparatorExpected = comparatorExpected;
+      this.expectedOrder = expectedOrder;
+      this.factory = factory;
+    }
+  }
 
   @Test(dataProvider = "referenceTrackingConfig")
   public void testBasicList(boolean referenceTrackingConfig) {
@@ -243,6 +295,97 @@ public class CollectionSerializersTest extends 
ForyTestBase {
     }
   }
 
+  @Test(dataProvider = "referenceTrackingConfig")
+  public void testTreeSetConstructorMatrix(boolean referenceTrackingConfig) {
+    Fory fory =
+        Fory.builder()
+            .withLanguage(Language.JAVA)
+            .withRefTracking(referenceTrackingConfig)
+            .requireClassRegistration(false)
+            .build();
+    for (SortedSetConstructorCase testCase : treeSetConstructorCases()) {
+      SortedSet<String> original = testCase.factory.get();
+      assertSortedSetState(testCase, original);
+      SortedSet<String> deserialized = serDe(fory, original);
+      assertSortedSetState(testCase, deserialized);
+      Assert.assertEquals(deserialized, original, testCase.name);
+      Assert.assertNotSame(deserialized, original, testCase.name);
+    }
+  }
+
+  @Test(dataProvider = "foryCopyConfig")
+  public void testTreeSetConstructorMatrix(Fory fory) {
+    for (SortedSetConstructorCase testCase : treeSetConstructorCases()) {
+      SortedSet<String> original = testCase.factory.get();
+      assertSortedSetState(testCase, original);
+      SortedSet<String> copy = fory.copy(original);
+      assertSortedSetState(testCase, copy);
+      Assert.assertEquals(copy, original, testCase.name);
+      Assert.assertNotSame(copy, original, testCase.name);
+    }
+  }
+
+  @Test(dataProvider = "referenceTrackingConfig")
+  public void testConcurrentSkipListSetConstructorMatrix(boolean 
referenceTrackingConfig) {
+    Fory fory =
+        Fory.builder()
+            .withLanguage(Language.JAVA)
+            .withRefTracking(referenceTrackingConfig)
+            .requireClassRegistration(false)
+            .build();
+    for (SortedSetConstructorCase testCase : 
concurrentSkipListSetConstructorCases()) {
+      SortedSet<String> original = testCase.factory.get();
+      assertSortedSetState(testCase, original);
+      SortedSet<String> deserialized = serDe(fory, original);
+      assertSortedSetState(testCase, deserialized);
+      Assert.assertEquals(deserialized, original, testCase.name);
+      Assert.assertNotSame(deserialized, original, testCase.name);
+    }
+  }
+
+  @Test(dataProvider = "foryCopyConfig")
+  public void testConcurrentSkipListSetConstructorMatrix(Fory fory) {
+    for (SortedSetConstructorCase testCase : 
concurrentSkipListSetConstructorCases()) {
+      SortedSet<String> original = testCase.factory.get();
+      assertSortedSetState(testCase, original);
+      SortedSet<String> copy = fory.copy(original);
+      assertSortedSetState(testCase, copy);
+      Assert.assertEquals(copy, original, testCase.name);
+      Assert.assertNotSame(copy, original, testCase.name);
+    }
+  }
+
+  @Test(dataProvider = "referenceTrackingConfig")
+  public void testPriorityQueueConstructorMatrix(boolean 
referenceTrackingConfig) {
+    Fory fory =
+        Fory.builder()
+            .withLanguage(Language.JAVA)
+            .withRefTracking(referenceTrackingConfig)
+            .requireClassRegistration(false)
+            .build();
+    for (PriorityQueueConstructorCase testCase : 
priorityQueueConstructorCases()) {
+      PriorityQueue<String> original = testCase.factory.get();
+      assertPriorityQueueState(testCase, original);
+      PriorityQueue<String> deserialized = serDe(fory, original);
+      assertPriorityQueueState(testCase, deserialized);
+      Assert.assertEquals(
+          drainPriorityQueue(deserialized), drainPriorityQueue(original), 
testCase.name);
+      Assert.assertNotSame(deserialized, original, testCase.name);
+    }
+  }
+
+  @Test(dataProvider = "foryCopyConfig")
+  public void testPriorityQueueConstructorMatrix(Fory fory) {
+    for (PriorityQueueConstructorCase testCase : 
priorityQueueConstructorCases()) {
+      PriorityQueue<String> original = testCase.factory.get();
+      assertPriorityQueueState(testCase, original);
+      PriorityQueue<String> copy = fory.copy(original);
+      assertPriorityQueueState(testCase, copy);
+      Assert.assertEquals(drainPriorityQueue(copy), 
drainPriorityQueue(original), testCase.name);
+      Assert.assertNotSame(copy, original, testCase.name);
+    }
+  }
+
   // TreeSet subclass without a Comparator constructor (natural ordering only)
   public static class ChildTreeSet extends TreeSet<String> {
     public ChildTreeSet() {
@@ -505,6 +648,185 @@ public class CollectionSerializersTest extends 
ForyTestBase {
     Assert.assertEquals(ImmutableList.sortedCopyOf(copy), list);
   }
 
+  private List<SortedSetConstructorCase> treeSetConstructorCases() {
+    return Arrays.asList(
+        new SortedSetConstructorCase(
+            "TreeSet()",
+            TreeSet.class,
+            false,
+            NATURAL_SORT_ORDER,
+            () -> {
+              TreeSet<String> set = new TreeSet<>();
+              set.addAll(sortedCollectionInput());
+              return set;
+            }),
+        new SortedSetConstructorCase(
+            "TreeSet(Comparator)",
+            TreeSet.class,
+            true,
+            LENGTH_SORT_ORDER,
+            () -> {
+              TreeSet<String> set = new TreeSet<>(new 
LengthThenNaturalComparator());
+              set.addAll(sortedCollectionInput());
+              return set;
+            }),
+        new SortedSetConstructorCase(
+            "TreeSet(Collection)",
+            TreeSet.class,
+            false,
+            NATURAL_SORT_ORDER,
+            () -> new TreeSet<>(new ArrayList<>(sortedCollectionInput()))),
+        new SortedSetConstructorCase(
+            "TreeSet(SortedSet)",
+            TreeSet.class,
+            true,
+            LENGTH_SORT_ORDER,
+            () -> new TreeSet<>(newComparatorSortedSetSource())));
+  }
+
+  private List<SortedSetConstructorCase> 
concurrentSkipListSetConstructorCases() {
+    return Arrays.asList(
+        new SortedSetConstructorCase(
+            "ConcurrentSkipListSet()",
+            ConcurrentSkipListSet.class,
+            false,
+            NATURAL_SORT_ORDER,
+            () -> {
+              ConcurrentSkipListSet<String> set = new 
ConcurrentSkipListSet<>();
+              set.addAll(sortedCollectionInput());
+              return set;
+            }),
+        new SortedSetConstructorCase(
+            "ConcurrentSkipListSet(Comparator)",
+            ConcurrentSkipListSet.class,
+            true,
+            LENGTH_SORT_ORDER,
+            () -> {
+              ConcurrentSkipListSet<String> set =
+                  new ConcurrentSkipListSet<>(new 
LengthThenNaturalComparator());
+              set.addAll(sortedCollectionInput());
+              return set;
+            }),
+        new SortedSetConstructorCase(
+            "ConcurrentSkipListSet(Collection)",
+            ConcurrentSkipListSet.class,
+            false,
+            NATURAL_SORT_ORDER,
+            () -> new ConcurrentSkipListSet<>(new 
ArrayList<>(sortedCollectionInput()))),
+        new SortedSetConstructorCase(
+            "ConcurrentSkipListSet(SortedSet)",
+            ConcurrentSkipListSet.class,
+            true,
+            LENGTH_SORT_ORDER,
+            () -> new 
ConcurrentSkipListSet<>(newComparatorSortedSetSource())));
+  }
+
+  private List<PriorityQueueConstructorCase> priorityQueueConstructorCases() {
+    return Arrays.asList(
+        new PriorityQueueConstructorCase(
+            "PriorityQueue()",
+            false,
+            NATURAL_SORT_ORDER,
+            () -> {
+              PriorityQueue<String> queue = new PriorityQueue<>();
+              queue.addAll(sortedCollectionInput());
+              return queue;
+            }),
+        new PriorityQueueConstructorCase(
+            "PriorityQueue(int)",
+            false,
+            NATURAL_SORT_ORDER,
+            () -> {
+              PriorityQueue<String> queue = new PriorityQueue<>(32);
+              queue.addAll(sortedCollectionInput());
+              return queue;
+            }),
+        new PriorityQueueConstructorCase(
+            "PriorityQueue(Comparator)",
+            true,
+            LENGTH_SORT_ORDER,
+            () -> {
+              PriorityQueue<String> queue = new PriorityQueue<>(new 
LengthThenNaturalComparator());
+              queue.addAll(sortedCollectionInput());
+              return queue;
+            }),
+        new PriorityQueueConstructorCase(
+            "PriorityQueue(int, Comparator)",
+            true,
+            LENGTH_SORT_ORDER,
+            () -> {
+              PriorityQueue<String> queue =
+                  new PriorityQueue<>(32, new LengthThenNaturalComparator());
+              queue.addAll(sortedCollectionInput());
+              return queue;
+            }),
+        new PriorityQueueConstructorCase(
+            "PriorityQueue(Collection)",
+            false,
+            NATURAL_SORT_ORDER,
+            () -> new PriorityQueue<>(new 
ArrayList<>(sortedCollectionInput()))),
+        new PriorityQueueConstructorCase(
+            "PriorityQueue(PriorityQueue)",
+            true,
+            LENGTH_SORT_ORDER,
+            () -> new PriorityQueue<>(newComparatorPriorityQueueSource())),
+        new PriorityQueueConstructorCase(
+            "PriorityQueue(SortedSet)",
+            true,
+            LENGTH_SORT_ORDER,
+            () -> new PriorityQueue<>(newComparatorSortedSetSource())));
+  }
+
+  private void assertSortedSetState(SortedSetConstructorCase testCase, 
SortedSet<String> set) {
+    Assert.assertEquals(set.getClass(), testCase.expectedType, testCase.name);
+    Assert.assertEquals(new ArrayList<>(set), testCase.expectedOrder, 
testCase.name);
+    if (testCase.comparatorExpected) {
+      Assert.assertNotNull(set.comparator(), testCase.name);
+      Assert.assertEquals(
+          set.comparator().getClass(), LengthThenNaturalComparator.class, 
testCase.name);
+    } else {
+      Assert.assertNull(set.comparator(), testCase.name);
+    }
+  }
+
+  private void assertPriorityQueueState(
+      PriorityQueueConstructorCase testCase, PriorityQueue<String> queue) {
+    Assert.assertEquals(queue.getClass(), PriorityQueue.class, testCase.name);
+    Assert.assertEquals(drainPriorityQueue(queue), testCase.expectedOrder, 
testCase.name);
+    if (testCase.comparatorExpected) {
+      Assert.assertNotNull(queue.comparator(), testCase.name);
+      Assert.assertEquals(
+          queue.comparator().getClass(), LengthThenNaturalComparator.class, 
testCase.name);
+    } else {
+      Assert.assertNull(queue.comparator(), testCase.name);
+    }
+  }
+
+  private static PriorityQueue<String> newComparatorPriorityQueueSource() {
+    PriorityQueue<String> queue = new PriorityQueue<>(new 
LengthThenNaturalComparator());
+    queue.addAll(sortedCollectionInput());
+    return queue;
+  }
+
+  private static SortedSet<String> newComparatorSortedSetSource() {
+    SortedSet<String> set = new TreeSet<>(new LengthThenNaturalComparator());
+    set.addAll(sortedCollectionInput());
+    return set;
+  }
+
+  private static List<String> drainPriorityQueue(PriorityQueue<String> queue) {
+    PriorityQueue<String> copy = new PriorityQueue<>(queue);
+    List<String> ordered = new ArrayList<>(copy.size());
+    while (!copy.isEmpty()) {
+      ordered.add(copy.poll());
+    }
+    return ordered;
+  }
+
+  private static List<String> sortedCollectionInput() {
+    return new ArrayList<>(SORTED_COLLECTION_INPUT);
+  }
+
   @Test
   public void testCopyOnWriteArrayList() {
     final CopyOnWriteArrayList<String> list =
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 ca52e0e47..ed909fe6e 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
@@ -41,9 +41,11 @@ import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
+import java.util.SortedMap;
 import java.util.TreeMap;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentSkipListMap;
+import java.util.function.Supplier;
 import lombok.AllArgsConstructor;
 import lombok.Data;
 import org.apache.fory.Fory;
@@ -64,6 +66,39 @@ import org.testng.Assert;
 import org.testng.annotations.Test;
 
 public class MapSerializersTest extends ForyTestBase {
+  private static final List<String> SORTED_MAP_INPUT_KEYS = 
Arrays.asList("bbb", "a", "cc");
+  private static final List<String> NATURAL_KEY_ORDER = Arrays.asList("a", 
"bbb", "cc");
+  private static final List<String> LENGTH_KEY_ORDER = Arrays.asList("a", 
"cc", "bbb");
+
+  private static final class LengthThenNaturalComparator
+      implements Comparator<String>, Serializable {
+    @Override
+    public int compare(String left, String right) {
+      int delta = left.length() - right.length();
+      return delta != 0 ? delta : left.compareTo(right);
+    }
+  }
+
+  private static final class SortedMapConstructorCase {
+    private final String name;
+    private final Class<?> expectedType;
+    private final boolean comparatorExpected;
+    private final List<String> expectedKeyOrder;
+    private final Supplier<SortedMap<String, Integer>> factory;
+
+    private SortedMapConstructorCase(
+        String name,
+        Class<?> expectedType,
+        boolean comparatorExpected,
+        List<String> expectedKeyOrder,
+        Supplier<SortedMap<String, Integer>> factory) {
+      this.name = name;
+      this.expectedType = expectedType;
+      this.comparatorExpected = comparatorExpected;
+      this.expectedKeyOrder = expectedKeyOrder;
+      this.factory = factory;
+    }
+  }
 
   @Test(dataProvider = "basicMultiConfigFory")
   public void basicTestCaseWithMultiConfig(
@@ -341,6 +376,66 @@ public class MapSerializersTest extends ForyTestBase {
     copyCheck(fory, beanForMap);
   }
 
+  @Test(dataProvider = "referenceTrackingConfig")
+  public void testTreeMapConstructorMatrix(boolean referenceTrackingConfig) {
+    Fory fory =
+        builder()
+            .withLanguage(Language.JAVA)
+            .withRefTracking(referenceTrackingConfig)
+            .requireClassRegistration(false)
+            .build();
+    for (SortedMapConstructorCase testCase : treeMapConstructorCases()) {
+      SortedMap<String, Integer> original = testCase.factory.get();
+      assertSortedMapState(testCase, original);
+      SortedMap<String, Integer> deserialized = serDe(fory, original);
+      assertSortedMapState(testCase, deserialized);
+      Assert.assertEquals(deserialized, original, testCase.name);
+      Assert.assertNotSame(deserialized, original, testCase.name);
+    }
+  }
+
+  @Test(dataProvider = "foryCopyConfig")
+  public void testTreeMapConstructorMatrix(Fory fory) {
+    for (SortedMapConstructorCase testCase : treeMapConstructorCases()) {
+      SortedMap<String, Integer> original = testCase.factory.get();
+      assertSortedMapState(testCase, original);
+      SortedMap<String, Integer> copy = fory.copy(original);
+      assertSortedMapState(testCase, copy);
+      Assert.assertEquals(copy, original, testCase.name);
+      Assert.assertNotSame(copy, original, testCase.name);
+    }
+  }
+
+  @Test(dataProvider = "referenceTrackingConfig")
+  public void testConcurrentSkipListMapConstructorMatrix(boolean 
referenceTrackingConfig) {
+    Fory fory =
+        builder()
+            .withLanguage(Language.JAVA)
+            .withRefTracking(referenceTrackingConfig)
+            .requireClassRegistration(false)
+            .build();
+    for (SortedMapConstructorCase testCase : 
concurrentSkipListMapConstructorCases()) {
+      SortedMap<String, Integer> original = testCase.factory.get();
+      assertSortedMapState(testCase, original);
+      SortedMap<String, Integer> deserialized = serDe(fory, original);
+      assertSortedMapState(testCase, deserialized);
+      Assert.assertEquals(deserialized, original, testCase.name);
+      Assert.assertNotSame(deserialized, original, testCase.name);
+    }
+  }
+
+  @Test(dataProvider = "foryCopyConfig")
+  public void testConcurrentSkipListMapConstructorMatrix(Fory fory) {
+    for (SortedMapConstructorCase testCase : 
concurrentSkipListMapConstructorCases()) {
+      SortedMap<String, Integer> original = testCase.factory.get();
+      assertSortedMapState(testCase, original);
+      SortedMap<String, Integer> copy = fory.copy(original);
+      assertSortedMapState(testCase, copy);
+      Assert.assertEquals(copy, original, testCase.name);
+      Assert.assertNotSame(copy, original, testCase.name);
+    }
+  }
+
   // TreeMap subclass without a Comparator constructor (natural ordering only)
   public static class ChildTreeMap extends TreeMap<String, String> {
     public ChildTreeMap() {
@@ -470,6 +565,106 @@ public class MapSerializersTest extends ForyTestBase {
     copyCheck(fory, new ConcurrentSkipListMap<>(data));
   }
 
+  private List<SortedMapConstructorCase> treeMapConstructorCases() {
+    return Arrays.asList(
+        new SortedMapConstructorCase(
+            "TreeMap()",
+            TreeMap.class,
+            false,
+            NATURAL_KEY_ORDER,
+            () -> {
+              TreeMap<String, Integer> map = new TreeMap<>();
+              map.putAll(sortedMapInput());
+              return map;
+            }),
+        new SortedMapConstructorCase(
+            "TreeMap(Comparator)",
+            TreeMap.class,
+            true,
+            LENGTH_KEY_ORDER,
+            () -> {
+              TreeMap<String, Integer> map = new TreeMap<>(new 
LengthThenNaturalComparator());
+              map.putAll(sortedMapInput());
+              return map;
+            }),
+        new SortedMapConstructorCase(
+            "TreeMap(Map)",
+            TreeMap.class,
+            false,
+            NATURAL_KEY_ORDER,
+            () -> new TreeMap<>(new LinkedHashMap<>(sortedMapInput()))),
+        new SortedMapConstructorCase(
+            "TreeMap(SortedMap)",
+            TreeMap.class,
+            true,
+            LENGTH_KEY_ORDER,
+            () -> new TreeMap<>(newComparatorSortedMapSource())));
+  }
+
+  private List<SortedMapConstructorCase> 
concurrentSkipListMapConstructorCases() {
+    return Arrays.asList(
+        new SortedMapConstructorCase(
+            "ConcurrentSkipListMap()",
+            ConcurrentSkipListMap.class,
+            false,
+            NATURAL_KEY_ORDER,
+            () -> {
+              ConcurrentSkipListMap<String, Integer> map = new 
ConcurrentSkipListMap<>();
+              map.putAll(sortedMapInput());
+              return map;
+            }),
+        new SortedMapConstructorCase(
+            "ConcurrentSkipListMap(Comparator)",
+            ConcurrentSkipListMap.class,
+            true,
+            LENGTH_KEY_ORDER,
+            () -> {
+              ConcurrentSkipListMap<String, Integer> map =
+                  new ConcurrentSkipListMap<>(new 
LengthThenNaturalComparator());
+              map.putAll(sortedMapInput());
+              return map;
+            }),
+        new SortedMapConstructorCase(
+            "ConcurrentSkipListMap(Map)",
+            ConcurrentSkipListMap.class,
+            false,
+            NATURAL_KEY_ORDER,
+            () -> new ConcurrentSkipListMap<>(new 
LinkedHashMap<>(sortedMapInput()))),
+        new SortedMapConstructorCase(
+            "ConcurrentSkipListMap(SortedMap)",
+            ConcurrentSkipListMap.class,
+            true,
+            LENGTH_KEY_ORDER,
+            () -> new 
ConcurrentSkipListMap<>(newComparatorSortedMapSource())));
+  }
+
+  private void assertSortedMapState(
+      SortedMapConstructorCase testCase, SortedMap<String, Integer> map) {
+    Assert.assertEquals(map.getClass(), testCase.expectedType, testCase.name);
+    Assert.assertEquals(new ArrayList<>(map.keySet()), 
testCase.expectedKeyOrder, testCase.name);
+    if (testCase.comparatorExpected) {
+      Assert.assertNotNull(map.comparator(), testCase.name);
+      Assert.assertEquals(
+          map.comparator().getClass(), LengthThenNaturalComparator.class, 
testCase.name);
+    } else {
+      Assert.assertNull(map.comparator(), testCase.name);
+    }
+  }
+
+  private static SortedMap<String, Integer> newComparatorSortedMapSource() {
+    SortedMap<String, Integer> map = new TreeMap<>(new 
LengthThenNaturalComparator());
+    map.putAll(sortedMapInput());
+    return map;
+  }
+
+  private static Map<String, Integer> sortedMapInput() {
+    LinkedHashMap<String, Integer> map = new LinkedHashMap<>();
+    for (String key : SORTED_MAP_INPUT_KEYS) {
+      map.put(key, key.length());
+    }
+    return map;
+  }
+
   @Test
   public void testEnumMap() {
     EnumMap<TestEnum, Object> enumMap = new EnumMap<>(TestEnum.class);


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to