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]

Reply via email to