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]