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 c55a1c37 enhance(java): Support customized serializer for abstract or
interface.
c55a1c37 is described below
commit c55a1c378cad7fcdae6bb8fdc420a4cbe3eea185
Author: hongwen.chw <[email protected]>
AuthorDate: Sat Jun 7 23:05:32 2025 +0800
enhance(java): Support customized serializer for abstract or interface.
---
.../org/apache/fory/resolver/ClassResolver.java | 32 ++++-
.../apache/fory/resolver/ClassResolverTest.java | 146 +++++++++++++++++++++
2 files changed, 173 insertions(+), 5 deletions(-)
diff --git
a/java/fory-core/src/main/java/org/apache/fory/resolver/ClassResolver.java
b/java/fory-core/src/main/java/org/apache/fory/resolver/ClassResolver.java
index 852340ef..32940271 100644
--- a/java/fory-core/src/main/java/org/apache/fory/resolver/ClassResolver.java
+++ b/java/fory-core/src/main/java/org/apache/fory/resolver/ClassResolver.java
@@ -255,6 +255,9 @@ public class ClassResolver implements TypeResolver {
new IdentityMap<>(estimatedNumRegistered);
private final BiMap<String, Class<?>> registeredClasses =
HashBiMap.create(estimatedNumRegistered);
+ // cache absClassInfo, support customized serializer for abstract or
interface.
+ private final IdentityMap<Class<?>, ClassInfo> absClassInfo =
+ new IdentityMap<>(estimatedNumRegistered, foryMapLoadFactor);
// avoid potential recursive call for seq codec generation.
// ex. A->field1: B, B.field1: A
private final Set<Class<?>> getClassCtx = new HashSet<>();
@@ -466,7 +469,7 @@ public class ClassResolver implements TypeResolver {
classInfo = new ClassInfo(this, cls, null, id, NOT_SUPPORT_XLANG);
// make `extRegistry.registeredClassIdMap` and `classInfoMap` share same
classInfo
// instances.
- classInfoMap.put(cls, classInfo);
+ setClassInfo(cls, classInfo);
}
// serializer will be set lazily in `addSerializer` method if it's null.
registeredId2ClassInfo[id] = classInfo;
@@ -514,7 +517,7 @@ public class ClassResolver implements TypeResolver {
MetaStringBytes nameBytes =
metaStringResolver.getOrCreateMetaStringBytes(encodeTypeName(name));
ClassInfo classInfo =
new ClassInfo(cls, fullNameBytes, nsBytes, nameBytes, false, null,
NO_CLASS_ID, (short) -1);
- classInfoMap.put(cls, classInfo);
+ setClassInfo(cls, classInfo);
compositeNameBytes2ClassInfo.put(
new TypeNameBytes(nsBytes.hashCode, nameBytes.hashCode), classInfo);
extRegistry.registeredClasses.put(fullname, cls);
@@ -857,7 +860,7 @@ public class ClassResolver implements TypeResolver {
if (classInfo == null || classId != classInfo.classId) {
classInfo = new ClassInfo(this, type, null, classId, (short) 0);
- classInfoMap.put(type, classInfo);
+ setClassInfo(type, classInfo);
if (registered) {
registeredId2ClassInfo[classId] = classInfo;
}
@@ -1294,6 +1297,10 @@ public class ClassResolver implements TypeResolver {
void setClassInfo(Class<?> cls, ClassInfo classInfo) {
classInfoMap.put(cls, classInfo);
+ // in order to support customized serializer for abstract or interface.
+ if (!cls.isPrimitive() && (ReflectionUtils.isAbstract(cls) ||
cls.isInterface())) {
+ extRegistry.absClassInfo.put(cls, classInfo);
+ }
}
@Internal
@@ -1363,6 +1370,21 @@ public class ClassResolver implements TypeResolver {
return shimSerializer;
}
+ // support customized serializer for abstract or interface.
+ Class<?> tmpCls = cls;
+ while (tmpCls != null && tmpCls != Object.class) {
+ ClassInfo absClass = null;
+ if ((absClass = extRegistry.absClassInfo.get(tmpCls.getSuperclass())) !=
null) {
+ return absClass.serializer;
+ }
+ for (Class<?> tmpI : tmpCls.getInterfaces()) {
+ if ((absClass = extRegistry.absClassInfo.get(tmpI)) != null) {
+ return absClass.serializer;
+ }
+ }
+ tmpCls = tmpCls.getSuperclass();
+ }
+
Class<? extends Serializer> serializerClass = getSerializerClass(cls);
Serializer serializer = Serializers.newSerializer(fory, cls,
serializerClass);
if (ForyCopyable.class.isAssignableFrom(cls)) {
@@ -1772,7 +1794,7 @@ public class ClassResolver implements TypeResolver {
classInfo =
new ClassInfo(
this, cls, null, classId == null ? NO_CLASS_ID : classId,
NOT_SUPPORT_XLANG);
- classInfoMap.put(cls, classInfo);
+ setClassInfo(cls, classInfo);
}
writeClassInternal(buffer, classInfo);
}
@@ -1953,7 +1975,7 @@ public class ClassResolver implements TypeResolver {
// don't create serializer here, if the class is an interface,
// there won't be serializer since interface has no instance.
if (!classInfoMap.containsKey(cls)) {
- classInfoMap.put(cls, classInfo);
+ setClassInfo(cls, classInfo);
}
}
compositeNameBytes2ClassInfo.put(typeNameBytes, classInfo);
diff --git
a/java/fory-core/src/test/java/org/apache/fory/resolver/ClassResolverTest.java
b/java/fory-core/src/test/java/org/apache/fory/resolver/ClassResolverTest.java
index 4559e579..61d0251d 100644
---
a/java/fory-core/src/test/java/org/apache/fory/resolver/ClassResolverTest.java
+++
b/java/fory-core/src/test/java/org/apache/fory/resolver/ClassResolverTest.java
@@ -423,4 +423,150 @@ public class ClassResolverTest extends ForyTestBase {
return null;
});
}
+
+ interface ITest {
+ int getF1();
+
+ void setF1(int f1);
+ }
+
+ @ToString
+ @EqualsAndHashCode
+ static class ImplTest implements ITest {
+ int f1;
+
+ @Override
+ public int getF1() {
+ return f1;
+ }
+
+ @Override
+ public void setF1(int f1) {
+ this.f1 = f1;
+ }
+ }
+
+ static class InterfaceCustomSerializer extends Serializer<ITest> {
+
+ public InterfaceCustomSerializer(Fory fory, Class<ITest> type) {
+ super(fory, type);
+ }
+
+ @Override
+ public void write(MemoryBuffer buffer, ITest value) {
+ buffer.writeInt32(value.getF1());
+ }
+
+ @Override
+ public ITest read(MemoryBuffer buffer) {
+ final ITest iTest = new ImplTest();
+ iTest.setF1(buffer.readInt32());
+ return iTest;
+ }
+ }
+
+ @Test
+ public void testInterfaceCustomSerializer() {
+ ThreadSafeFory threadSafeFory =
+ Fory.builder()
+ .withLanguage(Language.JAVA)
+ .requireClassRegistration(false)
+ .buildThreadSafeFory();
+ threadSafeFory.registerSerializer(
+ ITest.class, f -> new InterfaceCustomSerializer(f, ITest.class));
+ final ITest iTest = new ImplTest();
+ iTest.setF1(100);
+
+ threadSafeFory.execute(
+ fory -> {
+ Assert.assertEquals(iTest, serDe(fory, iTest));
+ return null;
+ });
+ threadSafeFory.execute(
+ fory -> {
+ Assert.assertEquals(
+
fory.getClassResolver().getSerializer(iTest.getClass()).getClass(),
+ InterfaceCustomSerializer.class);
+ return null;
+ });
+ }
+
+ @Data
+ abstract static class AbsTest {
+ int f1;
+ }
+
+ @EqualsAndHashCode(callSuper = true)
+ @ToString
+ static class SubAbsTest extends AbsTest {
+ long f2;
+ }
+
+ @EqualsAndHashCode(callSuper = true)
+ @ToString
+ static class Sub2AbsTest extends SubAbsTest {
+ Object f3;
+ }
+
+ static class AbstractCustomSerializer extends Serializer<AbsTest> {
+
+ public AbstractCustomSerializer(Fory fory, Class<AbsTest> type) {
+ super(fory, type);
+ }
+
+ @Override
+ public void write(MemoryBuffer buffer, AbsTest value) {
+ buffer.writeInt32(value.getF1());
+ }
+
+ @Override
+ public AbsTest read(MemoryBuffer buffer) {
+ // TODO maybe new SubAbsTest or Sub2AbsTest
+ final AbsTest absTest = new SubAbsTest();
+ absTest.setF1(buffer.readInt32());
+ return absTest;
+ }
+ }
+
+ @Test
+ public void testAbstractCustomSerializer() {
+ ThreadSafeFory threadSafeFory =
+ Fory.builder()
+ .withLanguage(Language.JAVA)
+ .requireClassRegistration(false)
+ .buildThreadSafeFory();
+ threadSafeFory.registerSerializer(
+ AbsTest.class, f -> new AbstractCustomSerializer(f, AbsTest.class));
+ final AbsTest absTest = new SubAbsTest();
+ absTest.setF1(100);
+
+ threadSafeFory.execute(
+ fory -> {
+ Assert.assertEquals(absTest, serDe(fory, absTest));
+ return null;
+ });
+ threadSafeFory.execute(
+ fory -> {
+ Assert.assertEquals(
+
fory.getClassResolver().getSerializer(absTest.getClass()).getClass(),
+ AbstractCustomSerializer.class);
+ return null;
+ });
+
+ final AbsTest abs2Test = new Sub2AbsTest();
+ abs2Test.setF1(100);
+
+ threadSafeFory.execute(
+ fory -> {
+ Assert.assertEquals(abs2Test.getF1(), serDe(fory, abs2Test).getF1());
+ return null;
+ });
+ threadSafeFory.execute(
+ fory -> {
+ Assert.assertEquals(
+
fory.getClassResolver().getSerializer(abs2Test.getClass()).getClass(),
+ AbstractCustomSerializer.class);
+ return null;
+ });
+ }
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]