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 c6fb315c9 fix(java): support guava android collections (#3851)
c6fb315c9 is described below

commit c6fb315c964247cc1127e19bb782ed264bf50358
Author: Shawn Yang <[email protected]>
AuthorDate: Wed Jul 15 14:59:16 2026 +0530

    fix(java): support guava android collections (#3851)
    
    ## What does this PR do?
    
    - tolerate Guava variants that omit the singleton immutable list and
    bi-map implementation classes
    - preserve Guava internal type ID alignment with distinct fallback
    registrations
    - account for the actual materialized collection owners in graph-memory
    checks
    - document why missing singleton slots require placeholders and actual
    owner sizing
    
    Fixes #3847.
    
    ## Tests
    
    - `mvn -T16 -pl fory-core spotless:check`
    - `mvn -T16 -pl fory-core checkstyle:check`
    - `ENABLE_FORY_DEBUG_OUTPUT=1 mvn -T16 -pl fory-core
    
-Dtest=org.apache.fory.serializer.collection.CollectionSerializersTest#testGuavaListOwnerBudget+testGuavaBiMapOwnerBudget
    test`
---
 .../collection/GuavaCollectionSerializers.java     | 52 ++++++++++++++++--
 .../collection/CollectionSerializersTest.java      | 62 ++++++++++++++++++++++
 2 files changed, 110 insertions(+), 4 deletions(-)

diff --git 
a/java/fory-core/src/main/java/org/apache/fory/serializer/collection/GuavaCollectionSerializers.java
 
b/java/fory-core/src/main/java/org/apache/fory/serializer/collection/GuavaCollectionSerializers.java
index 7b948aaed..9cf3ab34f 100644
--- 
a/java/fory-core/src/main/java/org/apache/fory/serializer/collection/GuavaCollectionSerializers.java
+++ 
b/java/fory-core/src/main/java/org/apache/fory/serializer/collection/GuavaCollectionSerializers.java
@@ -72,6 +72,10 @@ public class GuavaCollectionSerializers {
           && isClassAvailable(IMMUTABLE_SET_CLASS_NAME)
           && isClassAvailable(IMMUTABLE_SORTED_MAP_CLASS_NAME)
           && isClassAvailable(IMMUTABLE_SORTED_SET_CLASS_NAME);
+  private static final boolean SINGLETON_IMMUTABLE_BI_MAP_AVAILABLE =
+      GUAVA_AVAILABLE && isClassAvailable(PKG + ".SingletonImmutableBiMap");
+  private static final boolean SINGLETON_IMMUTABLE_LIST_AVAILABLE =
+      GUAVA_AVAILABLE && isClassAvailable(PKG + ".SingletonImmutableList");
 
   private interface MapEntryBuilder {
     void put(Object key, Object value);
@@ -84,6 +88,11 @@ public class GuavaCollectionSerializers {
       typeResolver.setSerializer(cls, this);
     }
 
+    GuavaCollectionSerializer(TypeResolver typeResolver, Class<T> cls, int 
ownerBytes) {
+      super(typeResolver, cls, true, ownerBytes);
+      typeResolver.setSerializer(cls, this);
+    }
+
     protected abstract T xnewInstance(Collection collection);
   }
 
@@ -93,6 +102,10 @@ public class GuavaCollectionSerializers {
       super(typeResolver, cls);
     }
 
+    ImmutableListSerializer(TypeResolver typeResolver, Class<T> cls, int 
ownerBytes) {
+      super(typeResolver, cls, ownerBytes);
+    }
+
     @Override
     public Collection newCollection(ReadContext readContext) {
       MemoryBuffer buffer = readContext.getBuffer();
@@ -241,6 +254,11 @@ public class GuavaCollectionSerializers {
       typeResolver.setSerializer(cls, this);
     }
 
+    GuavaMapSerializer(TypeResolver typeResolver, Class<T> cls, int 
ownerBytes) {
+      super(typeResolver, cls, true, ownerBytes);
+      typeResolver.setSerializer(cls, this);
+    }
+
     protected abstract ImmutableMap.Builder makeBuilder(int size);
 
     @Override
@@ -370,6 +388,10 @@ public class GuavaCollectionSerializers {
       super(typeResolver, cls);
     }
 
+    ImmutableBiMapSerializer(TypeResolver typeResolver, Class<T> cls, int 
ownerBytes) {
+      super(typeResolver, cls, ownerBytes);
+    }
+
     @Override
     protected ImmutableMap.Builder makeBuilder(int size) {
       return newImmutableBiMapBuilder(size);
@@ -661,14 +683,36 @@ public class GuavaCollectionSerializers {
     Class cls =
         loadClass(PKG + ".RegularImmutableBiMap", ImmutableBiMap.of("k1", 1, 
"k2", 4).getClass());
     resolver.registerInternalSerializer(cls, new 
ImmutableBiMapSerializer(resolver, cls));
-    cls = loadClass(PKG + ".SingletonImmutableBiMap", ImmutableBiMap.of(1, 
2).getClass());
-    resolver.registerInternalSerializer(cls, new 
ImmutableBiMapSerializer(resolver, cls));
+    if (SINGLETON_IMMUTABLE_BI_MAP_AVAILABLE) {
+      cls = loadClass(PKG + ".SingletonImmutableBiMap", ImmutableBiMap.of(1, 
2).getClass());
+      resolver.registerInternalSerializer(cls, new 
ImmutableBiMapSerializer(resolver, cls));
+    } else {
+      // This class only preserves the missing singleton ID slot. Charge the 
concrete Guava owner
+      // returned by the local factory, which may be the regular 
implementation.
+      class GuavaSingletonBiMap {}
+
+      cls = GuavaSingletonBiMap.class;
+      int ownerBytes = 
GraphMemoryEstimates.shallowObjectBytes(ImmutableBiMap.of(1, 2).getClass());
+      resolver.registerInternalSerializer(
+          cls, new ImmutableBiMapSerializer(resolver, cls, ownerBytes));
+    }
     cls = loadClass(PKG + ".RegularImmutableMap", ImmutableMap.of("k1", 1, 
"k2", 2).getClass());
     resolver.registerInternalSerializer(cls, new 
ImmutableMapSerializer(resolver, cls));
     cls = loadClass(PKG + ".RegularImmutableList", 
ImmutableList.of().getClass());
     resolver.registerInternalSerializer(cls, new 
RegularImmutableListSerializer(resolver, cls));
-    cls = loadClass(PKG + ".SingletonImmutableList", 
ImmutableList.of(1).getClass());
-    resolver.registerInternalSerializer(cls, new 
ImmutableListSerializer(resolver, cls));
+    if (SINGLETON_IMMUTABLE_LIST_AVAILABLE) {
+      cls = loadClass(PKG + ".SingletonImmutableList", 
ImmutableList.of(1).getClass());
+      resolver.registerInternalSerializer(cls, new 
ImmutableListSerializer(resolver, cls));
+    } else {
+      // This class only preserves the missing singleton ID slot. Charge the 
concrete Guava owner
+      // returned by the local factory, which may be the regular 
implementation.
+      class GuavaSingletonList {}
+
+      cls = GuavaSingletonList.class;
+      int ownerBytes = 
GraphMemoryEstimates.shallowObjectBytes(ImmutableList.of(1).getClass());
+      resolver.registerInternalSerializer(
+          cls, new ImmutableListSerializer(resolver, cls, ownerBytes));
+    }
     cls = loadClass(PKG + ".RegularImmutableSet", ImmutableSet.of(1, 
2).getClass());
     resolver.registerInternalSerializer(cls, new 
ImmutableSetSerializer(resolver, cls));
     cls = loadClass(PKG + ".SingletonImmutableSet", 
ImmutableSet.of(1).getClass());
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 ff4261652..a7e0f9f0e 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
@@ -25,6 +25,7 @@ import static 
org.apache.fory.collection.Collections.ofHashSet;
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertSame;
 
+import com.google.common.collect.ImmutableBiMap;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.ImmutableSortedSet;
@@ -77,6 +78,7 @@ import org.apache.fory.config.Int64Encoding;
 import org.apache.fory.config.Language;
 import org.apache.fory.context.ReadContext;
 import org.apache.fory.exception.DeserializationException;
+import org.apache.fory.exception.InsecureException;
 import org.apache.fory.exception.SerializationException;
 import org.apache.fory.memory.MemoryBuffer;
 import org.apache.fory.memory.MemoryUtils;
@@ -85,6 +87,7 @@ import org.apache.fory.platform.JdkVersion;
 import org.apache.fory.reflect.FieldAccessor;
 import org.apache.fory.reflect.TypeRef;
 import org.apache.fory.resolver.TypeResolver;
+import org.apache.fory.serializer.GraphMemoryEstimates;
 import 
org.apache.fory.serializer.collection.CollectionSerializers.JDKCompatibleCollectionSerializer;
 import org.apache.fory.test.bean.Cyclic;
 import org.apache.fory.type.GenericType;
@@ -1811,6 +1814,65 @@ public class CollectionSerializersTest extends 
ForyTestBase {
     assertEquals(set, fory.deserialize(fory.serialize(buffer, set)));
   }
 
+  @Test
+  public void testGuavaListOwnerBudget() {
+    ImmutableList<String> list = ImmutableList.of("fory");
+    int ownerBytes = GraphMemoryEstimates.shallowObjectBytes(list.getClass());
+    long requiredBytes = ownerBytes + GraphMemoryEstimates.REFERENCE_BYTES;
+    MemoryBuffer buffer = MemoryUtils.buffer(32);
+    Fory fory = graphBudgetFory(requiredBytes);
+    GuavaCollectionSerializers.ImmutableListSerializer serializer =
+        new GuavaCollectionSerializers.ImmutableListSerializer(
+            fory.getTypeResolver(), SingletonListSlot.class, ownerBytes);
+    writeSerializer(fory, serializer, buffer, list);
+
+    Fory limitedFory = graphBudgetFory(requiredBytes - 1);
+    GuavaCollectionSerializers.ImmutableListSerializer limitedSerializer =
+        new GuavaCollectionSerializers.ImmutableListSerializer(
+            limitedFory.getTypeResolver(), SingletonListSlot.class, 
ownerBytes);
+    Assert.assertThrows(
+        InsecureException.class, () -> readSerializer(limitedFory, 
limitedSerializer, buffer));
+    buffer.readerIndex(0);
+    assertEquals(readSerializer(fory, serializer, buffer), list);
+  }
+
+  @Test
+  public void testGuavaBiMapOwnerBudget() {
+    ImmutableBiMap<String, Integer> biMap = ImmutableBiMap.of("fory", 1);
+    int ownerBytes = GraphMemoryEstimates.shallowObjectBytes(biMap.getClass());
+    long requiredBytes = ownerBytes + 2L * 
GraphMemoryEstimates.REFERENCE_BYTES;
+    MemoryBuffer buffer = MemoryUtils.buffer(32);
+    Fory fory = graphBudgetFory(requiredBytes);
+    GuavaCollectionSerializers.ImmutableBiMapSerializer serializer =
+        new GuavaCollectionSerializers.ImmutableBiMapSerializer(
+            fory.getTypeResolver(), SingletonBiMapSlot.class, ownerBytes);
+    writeSerializer(fory, serializer, buffer, biMap);
+
+    Fory limitedFory = graphBudgetFory(requiredBytes - 1);
+    GuavaCollectionSerializers.ImmutableBiMapSerializer limitedSerializer =
+        new GuavaCollectionSerializers.ImmutableBiMapSerializer(
+            limitedFory.getTypeResolver(), SingletonBiMapSlot.class, 
ownerBytes);
+    Assert.assertThrows(
+        InsecureException.class, () -> readSerializer(limitedFory, 
limitedSerializer, buffer));
+    buffer.readerIndex(0);
+    assertEquals(readSerializer(fory, serializer, buffer), biMap);
+  }
+
+  private static Fory graphBudgetFory(long maxGraphMemoryBytes) {
+    return Fory.builder()
+        .withXlang(false)
+        .registerGuavaTypes(true)
+        .requireClassRegistration(false)
+        .suppressClassRegistrationWarnings(true)
+        .withCompatible(false)
+        .withMaxGraphMemoryBytes(maxGraphMemoryBytes)
+        .build();
+  }
+
+  private static final class SingletonListSlot {}
+
+  private static final class SingletonBiMapSlot {}
+
   @Test(dataProvider = "foryCopyConfig")
   public void testJavaSerialization(Fory fory) {
     ImmutableSortedSet<Integer> set = ImmutableSortedSet.of(1, 2, 3);


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

Reply via email to