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/incubator-fury.git


The following commit(s) were added to refs/heads/main by this push:
     new 18527e30 fix(java): fix polymorphic array serialization (#1324)
18527e30 is described below

commit 18527e30decd9d580ecb6fc9805f418072791ea1
Author: Shawn Yang <[email protected]>
AuthorDate: Wed Jan 10 14:44:10 2024 +0800

    fix(java): fix polymorphic array serialization (#1324)
    
    `Object[]` can be assigned from `String[]` and other arrays, we take it
    as a final type before, so we skip writing classinfo for such types and
    lost object type too.
    
    This PR fix this polymorphic array serialization bug by checking array
    type polymorphism by component type.
    
    Closes #1323
---
 .../fury/builder/BaseObjectCodecBuilder.java       | 35 +++++----
 .../java/org/apache/fury/builder/CodecBuilder.java |  4 +-
 .../fury/builder/CompatibleCodecBuilder.java       | 16 ++--
 .../apache/fury/builder/ObjectCodecBuilder.java    |  4 +-
 .../apache/fury/builder/ObjectCodecOptimizer.java  |  4 +-
 .../org/apache/fury/resolver/ClassResolver.java    | 32 +++-----
 .../org/apache/fury/resolver/FieldResolver.java    | 19 ++---
 .../apache/fury/serializer/ArraySerializers.java   |  4 +-
 .../fury/serializer/MetaSharedSerializer.java      |  2 +-
 .../apache/fury/serializer/ObjectSerializer.java   |  4 +-
 .../collection/AbstractCollectionSerializer.java   | 12 +--
 .../collection/AbstractMapSerializer.java          |  8 +-
 .../main/java/org/apache/fury/type/ClassDef.java   | 36 +++++----
 .../main/java/org/apache/fury/type/Descriptor.java |  4 +
 .../org/apache/fury/type/DescriptorGrouper.java    |  4 +-
 .../java/org/apache/fury/type/GenericType.java     | 18 ++---
 .../java/org/apache/fury/util/ReflectionUtils.java | 14 +++-
 .../fury/serializer/ArraySerializersTest.java      | 66 ++++++++++++++++
 .../fury/serializer/MetaSharedCompatibleTest.java  | 88 +++++++++++++++++++---
 19 files changed, 262 insertions(+), 112 deletions(-)

diff --git 
a/java/fury-core/src/main/java/org/apache/fury/builder/BaseObjectCodecBuilder.java
 
b/java/fury-core/src/main/java/org/apache/fury/builder/BaseObjectCodecBuilder.java
index 75078105..875d851a 100644
--- 
a/java/fury-core/src/main/java/org/apache/fury/builder/BaseObjectCodecBuilder.java
+++ 
b/java/fury-core/src/main/java/org/apache/fury/builder/BaseObjectCodecBuilder.java
@@ -51,7 +51,6 @@ import static 
org.apache.fury.util.Preconditions.checkArgument;
 
 import com.google.common.collect.ImmutableSet;
 import com.google.common.reflect.TypeToken;
-import java.lang.reflect.Modifier;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -418,7 +417,7 @@ public abstract class BaseObjectCodecBuilder extends 
CodecBuilder {
    * the method can still return false. For example, we return false in meta 
share mode to write
    * class defs for the non-inner final types.
    */
-  protected abstract boolean isFinal(Class<?> clz);
+  protected abstract boolean isMonomorphic(Class<?> clz);
 
   protected Expression serializeForNotNullObject(
       Expression inputObject, Expression buffer, TypeToken<?> typeToken, 
Expression serializer) {
@@ -426,7 +425,7 @@ public abstract class BaseObjectCodecBuilder extends 
CodecBuilder {
     if (serializer != null) {
       return new Invoke(serializer, "write", buffer, inputObject);
     }
-    if (isFinal(clz)) {
+    if (isMonomorphic(clz)) {
       serializer = getOrCreateSerializer(clz);
       return new Invoke(serializer, "write", buffer, inputObject);
     } else {
@@ -473,7 +472,7 @@ public abstract class BaseObjectCodecBuilder extends 
CodecBuilder {
    */
   protected Expression getOrCreateSerializer(Class<?> cls) {
     // Not need to check cls final, take collection writeSameTypeElements as 
an example.
-    // Preconditions.checkArgument(isFinal(cls), cls);
+    // Preconditions.checkArgument(isMonomorphic(cls), cls);
     Reference serializerRef = serializerMap.get(cls);
     if (serializerRef == null) {
       // potential recursive call for seq codec generation is handled in 
`getSerializerClass`.
@@ -552,7 +551,7 @@ public abstract class BaseObjectCodecBuilder extends 
CodecBuilder {
    */
   protected Tuple2<Reference, Boolean> addClassInfoField(Class<?> cls) {
     Expression classInfoExpr;
-    boolean needUpdate = !ReflectionUtils.isFinal(cls);
+    boolean needUpdate = !ReflectionUtils.isMonomorphic(cls);
     String key;
     if (!needUpdate) {
       key = "classInfo:" + cls;
@@ -572,7 +571,7 @@ public abstract class BaseObjectCodecBuilder extends 
CodecBuilder {
       classInfoRef = Tuple2.of(fieldRef(name, classInfoTypeToken), false);
     } else {
       classInfoExpr = inlineInvoke(classResolverRef, "nilClassInfo", 
classInfoTypeToken);
-      String name = ctx.newName(StringUtils.uncapitalize(cls.getSimpleName()) 
+ "ClassInfo");
+      String name = ctx.newName(cls, "ClassInfo");
       ctx.addField(false, ctx.type(ClassInfo.class), name, classInfoExpr);
       // Can't use fieldRef, since the field is not final.
       classInfoRef = Tuple2.of(new Reference(name, classInfoTypeToken), true);
@@ -584,7 +583,7 @@ public abstract class BaseObjectCodecBuilder extends 
CodecBuilder {
   protected Reference addClassInfoHolderField(Class<?> cls) {
     // Final type need to write classinfo when meta share enabled.
     String key;
-    if (Modifier.isFinal(cls.getModifiers())) {
+    if (ReflectionUtils.isMonomorphic(cls)) {
       key = "classInfoHolder:" + cls;
     } else {
       key = "classInfoHolder:" + cls + walkPath;
@@ -608,7 +607,7 @@ public abstract class BaseObjectCodecBuilder extends 
CodecBuilder {
   }
 
   protected Expression readClassInfo(Class<?> cls, Expression buffer, boolean 
inlineReadClassInfo) {
-    if (Modifier.isFinal(cls.getModifiers())) {
+    if (ReflectionUtils.isMonomorphic(cls)) {
       Reference classInfoRef = addClassInfoField(cls).f0;
       if (inlineReadClassInfo) {
         return inlineInvoke(
@@ -665,7 +664,7 @@ public abstract class BaseObjectCodecBuilder extends 
CodecBuilder {
     // get serializer, write class info if necessary.
     if (serializer == null) {
       Class<?> clz = getRawType(typeToken);
-      if (isFinal(clz)) {
+      if (isMonomorphic(clz)) {
         serializer = getOrCreateSerializer(clz);
       } else {
         ListExpression writeClassAction = new ListExpression();
@@ -730,7 +729,7 @@ public abstract class BaseObjectCodecBuilder extends 
CodecBuilder {
         writeElementsHeader(elemClass, trackingRef, serializer, buffer, 
collection);
     Expression flags = writeElementsHeader.f0;
     builder.add(flags);
-    boolean finalType = isFinal(elemClass);
+    boolean finalType = isMonomorphic(elemClass);
     if (finalType) {
       if (trackingRef) {
         builder.add(
@@ -816,7 +815,7 @@ public abstract class BaseObjectCodecBuilder extends 
CodecBuilder {
       Expression collectionSerializer,
       Expression buffer,
       Expression value) {
-    if (isFinal(elementType)) {
+    if (isMonomorphic(elementType)) {
       Expression bitmap;
       if (trackingRef) {
         bitmap =
@@ -922,7 +921,7 @@ public abstract class BaseObjectCodecBuilder extends 
CodecBuilder {
     boolean generateNewMethod =
         useCollectionSerialization(elementType) || 
useMapSerialization(elementType);
     Class<?> rawType = getRawType(elementType);
-    boolean finalType = isFinal(rawType);
+    boolean finalType = isMonomorphic(rawType);
     elem = tryCastIfPublic(elem, elementType);
     Expression write;
     if (finalType) {
@@ -968,7 +967,7 @@ public abstract class BaseObjectCodecBuilder extends 
CodecBuilder {
       boolean generateNewMethod) {
     if (serializer == null) {
       Class<?> clz = getRawType(typeToken);
-      if (isFinal(clz)) {
+      if (isMonomorphic(clz)) {
         serializer = getOrCreateSerializer(clz);
       } else {
         ListExpression writeClassAction = new ListExpression();
@@ -1163,7 +1162,7 @@ public abstract class BaseObjectCodecBuilder extends 
CodecBuilder {
       } else if (useMapSerialization(typeToken)) {
         obj = deserializeForMap(buffer, typeToken, serializer, cutPoint);
       } else {
-        if (isFinal(cls)) {
+        if (isMonomorphic(cls)) {
           Preconditions.checkState(serializer == null);
           serializer = getOrCreateSerializer(cls);
           Class<?> returnType =
@@ -1195,7 +1194,7 @@ public abstract class BaseObjectCodecBuilder extends 
CodecBuilder {
     TypeToken<?> elementType = getElementType(typeToken);
     if (serializer == null) {
       Class<?> cls = getRawType(typeToken);
-      if (isFinal(cls)) {
+      if (isMonomorphic(cls)) {
         serializer = getOrCreateSerializer(cls);
       } else {
         Expression classInfo = readClassInfo(cls, buffer);
@@ -1244,7 +1243,7 @@ public abstract class BaseObjectCodecBuilder extends 
CodecBuilder {
     builder.add(flags);
     Class<?> elemClass = TypeUtils.getRawType(elementType);
     walkPath.add(elementType.toString());
-    boolean finalType = isFinal(elemClass);
+    boolean finalType = isMonomorphic(elemClass);
     boolean trackingRef = visitFury(fury -> 
fury.getClassResolver().needToWriteRef(elemClass));
     if (finalType) {
       if (trackingRef) {
@@ -1376,7 +1375,7 @@ public abstract class BaseObjectCodecBuilder extends 
CodecBuilder {
         useCollectionSerialization(elementType) || 
useMapSerialization(elementType);
     CutPoint cutPoint = new CutPoint(genNewMethod, buffer);
     Class<?> rawType = getRawType(elementType);
-    boolean finalType = isFinal(rawType);
+    boolean finalType = isMonomorphic(rawType);
     Expression read;
     if (finalType) {
       if (trackingRef) {
@@ -1426,7 +1425,7 @@ public abstract class BaseObjectCodecBuilder extends 
CodecBuilder {
     TypeToken<?> valueType = keyValueType.f1;
     if (serializer == null) {
       Class<?> cls = getRawType(typeToken);
-      if (isFinal(cls)) {
+      if (isMonomorphic(cls)) {
         serializer = getOrCreateSerializer(cls);
       } else {
         Expression classInfo = readClassInfo(cls, buffer);
diff --git 
a/java/fury-core/src/main/java/org/apache/fury/builder/CodecBuilder.java 
b/java/fury-core/src/main/java/org/apache/fury/builder/CodecBuilder.java
index 91b1d98b..3ce014bc 100644
--- a/java/fury-core/src/main/java/org/apache/fury/builder/CodecBuilder.java
+++ b/java/fury-core/src/main/java/org/apache/fury/builder/CodecBuilder.java
@@ -345,12 +345,12 @@ public abstract class CodecBuilder {
     if (duplicatedFields.contains(fieldName) || 
!sourcePublicAccessible(beanClass)) {
       return unsafeSetField(bean, d, value);
     }
-    if (!Modifier.isFinal(d.getModifiers()) && 
Modifier.isPublic(d.getModifiers())) {
+    if (!d.isFinalField() && Modifier.isPublic(d.getModifiers())) {
       return new Expression.SetField(bean, fieldName, value);
     } else if (d.getWriteMethod() != null && 
Modifier.isPublic(d.getWriteMethod().getModifiers())) {
       return new Invoke(bean, d.getWriteMethod().getName(), value);
     } else {
-      if (!Modifier.isFinal(d.getModifiers()) && 
!Modifier.isPrivate(d.getModifiers())) {
+      if (!d.isFinalField() && !Modifier.isPrivate(d.getModifiers())) {
         if (AccessorHelper.defineSetter(d.getField())) {
           Class<?> accessorClass = 
AccessorHelper.getAccessorClass(d.getField());
           if (!value.type().equals(d.getTypeToken())) {
diff --git 
a/java/fury-core/src/main/java/org/apache/fury/builder/CompatibleCodecBuilder.java
 
b/java/fury-core/src/main/java/org/apache/fury/builder/CompatibleCodecBuilder.java
index 55a774b3..50117db0 100644
--- 
a/java/fury-core/src/main/java/org/apache/fury/builder/CompatibleCodecBuilder.java
+++ 
b/java/fury-core/src/main/java/org/apache/fury/builder/CompatibleCodecBuilder.java
@@ -36,7 +36,6 @@ import static org.apache.fury.type.TypeUtils.getRawType;
 import com.google.common.reflect.TypeToken;
 import java.lang.reflect.Field;
 import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
@@ -78,6 +77,7 @@ import org.apache.fury.type.Descriptor;
 import org.apache.fury.type.TypeUtils;
 import org.apache.fury.util.Platform;
 import org.apache.fury.util.Preconditions;
+import org.apache.fury.util.ReflectionUtils;
 import org.apache.fury.util.function.SerializableSupplier;
 import org.apache.fury.util.record.RecordUtils;
 
@@ -135,8 +135,8 @@ public class CompatibleCodecBuilder extends 
BaseObjectCodecBuilder {
   }
 
   @Override
-  protected boolean isFinal(Class<?> clz) {
-    return Modifier.isFinal(clz.getModifiers());
+  protected boolean isMonomorphic(Class<?> clz) {
+    return ReflectionUtils.isMonomorphic(clz);
   }
 
   private Descriptor createDescriptor(FieldInfo fieldInfo) {
@@ -315,7 +315,7 @@ public class CompatibleCodecBuilder extends 
BaseObjectCodecBuilder {
                                                 buffer, 
mapFieldInfo.getValueType()));
                                       }
                                       Class<?> clz = descriptor.getRawType();
-                                      if 
(Modifier.isFinal(clz.getModifiers())) {
+                                      if (ReflectionUtils.isMonomorphic(clz)) {
                                         // serializeForNotNull won't write 
field type if it's final,
                                         // but the type is useful if peer 
doesn't have this field.
                                         
writeFieldValue.add(writeFinalClassInfo(buffer, clz));
@@ -837,7 +837,7 @@ public class CompatibleCodecBuilder extends 
BaseObjectCodecBuilder {
                       skipFinalClassInfo(((MapFieldInfo) 
fieldInfo).getValueType(), buffer));
                 }
                 Class<?> clz = getRawType(typeToken);
-                if (Modifier.isFinal(clz.getModifiers())) {
+                if (ReflectionUtils.isMonomorphic(clz)) {
                   // deserializeForNotNull won't read field type if it's final
                   deserializedValue.add(skipFinalClassInfo(clz, buffer));
                 }
@@ -870,14 +870,14 @@ public class CompatibleCodecBuilder extends 
BaseObjectCodecBuilder {
   }
 
   protected Expression getFinalClassInfo(Class<?> cls) {
-    Preconditions.checkArgument(Modifier.isFinal(cls.getModifiers()));
+    Preconditions.checkArgument(ReflectionUtils.isMonomorphic(cls));
     Tuple2<Reference, Boolean> classInfoRef = addClassInfoField(cls);
     Preconditions.checkArgument(!classInfoRef.f1);
     return classInfoRef.f0;
   }
 
   protected Expression writeFinalClassInfo(Expression buffer, Class<?> cls) {
-    Preconditions.checkArgument(Modifier.isFinal(cls.getModifiers()));
+    Preconditions.checkArgument(ReflectionUtils.isMonomorphic(cls));
     ClassInfo classInfo = visitFury(f -> 
f.getClassResolver().getClassInfo(cls, false));
     if (classInfo != null && classInfo.getClassId() != 
ClassResolver.NO_CLASS_ID) {
       return fury.getClassResolver().writeClassExpr(buffer, 
classInfo.getClassId());
@@ -887,7 +887,7 @@ public class CompatibleCodecBuilder extends 
BaseObjectCodecBuilder {
   }
 
   protected Expression skipFinalClassInfo(Class<?> cls, Expression buffer) {
-    Preconditions.checkArgument(Modifier.isFinal(cls.getModifiers()));
+    Preconditions.checkArgument(ReflectionUtils.isMonomorphic(cls));
     ClassInfo classInfo = visitFury(f -> 
f.getClassResolver().getClassInfo(cls, false));
     if (classInfo != null && classInfo.getClassId() != 
ClassResolver.NO_CLASS_ID) {
       return fury.getClassResolver().skipRegisteredClassExpr(buffer);
diff --git 
a/java/fury-core/src/main/java/org/apache/fury/builder/ObjectCodecBuilder.java 
b/java/fury-core/src/main/java/org/apache/fury/builder/ObjectCodecBuilder.java
index 305183f2..37365f9d 100644
--- 
a/java/fury-core/src/main/java/org/apache/fury/builder/ObjectCodecBuilder.java
+++ 
b/java/fury-core/src/main/java/org/apache/fury/builder/ObjectCodecBuilder.java
@@ -128,8 +128,8 @@ public class ObjectCodecBuilder extends 
BaseObjectCodecBuilder {
 
   /** Mark non-inner registered final types as non-final to write class def 
for those types. */
   @Override
-  protected boolean isFinal(Class<?> clz) {
-    return visitFury(f -> f.getClassResolver().isFinal(clz));
+  protected boolean isMonomorphic(Class<?> clz) {
+    return visitFury(f -> f.getClassResolver().isMonomorphic(clz));
   }
 
   /**
diff --git 
a/java/fury-core/src/main/java/org/apache/fury/builder/ObjectCodecOptimizer.java
 
b/java/fury-core/src/main/java/org/apache/fury/builder/ObjectCodecOptimizer.java
index 9678cfd9..bceac14e 100644
--- 
a/java/fury-core/src/main/java/org/apache/fury/builder/ObjectCodecOptimizer.java
+++ 
b/java/fury-core/src/main/java/org/apache/fury/builder/ObjectCodecOptimizer.java
@@ -95,8 +95,8 @@ public class ObjectCodecOptimizer extends ExpressionOptimizer 
{
     // Note get field value also took some byte code if not public.
     List<Descriptor> primitiveDescriptorsList =
         new ArrayList<>(descriptorGrouper.getPrimitiveDescriptors());
-    while (primitiveDescriptorsList.size() > 0) {
-      int endIndex = Math.min(24, primitiveDescriptorsList.size());
+    while (!primitiveDescriptorsList.isEmpty()) {
+      int endIndex = Math.min(20, primitiveDescriptorsList.size());
       primitiveGroups.add(primitiveDescriptorsList.subList(0, endIndex));
       primitiveDescriptorsList =
           primitiveDescriptorsList.subList(endIndex, 
primitiveDescriptorsList.size());
diff --git 
a/java/fury-core/src/main/java/org/apache/fury/resolver/ClassResolver.java 
b/java/fury-core/src/main/java/org/apache/fury/resolver/ClassResolver.java
index 1317690d..1d376dfd 100644
--- a/java/fury-core/src/main/java/org/apache/fury/resolver/ClassResolver.java
+++ b/java/fury-core/src/main/java/org/apache/fury/resolver/ClassResolver.java
@@ -34,7 +34,6 @@ import java.io.Externalizable;
 import java.io.IOException;
 import java.io.Serializable;
 import java.lang.reflect.Field;
-import java.lang.reflect.Modifier;
 import java.lang.reflect.Type;
 import java.math.BigDecimal;
 import java.math.BigInteger;
@@ -514,22 +513,15 @@ public class ClassResolver {
    * a class is registered but not an inner class with inner serializer, it 
will still be taken as
    * non-final to write class def, so that it can be deserialized by the peer 
still..
    */
-  public boolean isFinal(Class<?> clz) {
-    if (Modifier.isFinal(clz.getModifiers())) {
-      if (fury.getConfig().shareMetaContext()) {
-        boolean isInnerClass = isInnerClass(clz);
-        if (!isInnerClass) {
-          return false;
-        } else {
-          // can't create final map/collection type using 
TypeUtils.mapOf(TypeToken<K>,
-          // TypeToken<V>)
-          return !Map.class.isAssignableFrom(clz) && 
!Collection.class.isAssignableFrom(clz);
-        }
-      } else {
-        return true;
-      }
+  public boolean isMonomorphic(Class<?> clz) {
+    if (fury.getConfig().shareMetaContext()) {
+      // can't create final map/collection type using 
TypeUtils.mapOf(TypeToken<K>,
+      // TypeToken<V>)
+      return ReflectionUtils.isMonomorphic(clz)
+          && isInnerClass(clz)
+          && (!Map.class.isAssignableFrom(clz) && 
!Collection.class.isAssignableFrom(clz));
     }
-    return false;
+    return ReflectionUtils.isMonomorphic(clz);
   }
 
   /** Returns true if <code>cls</code> is fury inner registered class. */
@@ -1752,9 +1744,9 @@ public class ClassResolver {
         typeToken.getType(),
         t -> {
           if (t.getClass() == Class.class) {
-            return isFinal((Class<?>) t);
+            return isMonomorphic((Class<?>) t);
           } else {
-            return isFinal(getRawType(t));
+            return isMonomorphic(getRawType(t));
           }
         });
   }
@@ -1764,9 +1756,9 @@ public class ClassResolver {
         type,
         t -> {
           if (t.getClass() == Class.class) {
-            return isFinal((Class<?>) t);
+            return isMonomorphic((Class<?>) t);
           } else {
-            return isFinal(getRawType(t));
+            return isMonomorphic(getRawType(t));
           }
         });
   }
diff --git 
a/java/fury-core/src/main/java/org/apache/fury/resolver/FieldResolver.java 
b/java/fury-core/src/main/java/org/apache/fury/resolver/FieldResolver.java
index 4529c901..cf9128c6 100644
--- a/java/fury-core/src/main/java/org/apache/fury/resolver/FieldResolver.java
+++ b/java/fury-core/src/main/java/org/apache/fury/resolver/FieldResolver.java
@@ -29,7 +29,6 @@ import static org.apache.fury.type.TypeUtils.getRawType;
 
 import com.google.common.reflect.TypeToken;
 import java.lang.reflect.Field;
-import java.lang.reflect.Modifier;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -261,7 +260,9 @@ public class FieldResolver {
       Short classId = classResolver.getRegisteredClassId(fieldType);
       // try to encode 6 bit for a char if field name is ascii.
       // then 7 byte can encode 9 char, remains 2 bits can be used as flag 
bits or just left.
-      if (ReflectionUtils.isFinal(fieldType) && classId != null && classId < 
MAX_EMBED_CLASS_ID) {
+      if (ReflectionUtils.isMonomorphic(fieldType)
+          && classId != null
+          && classId < MAX_EMBED_CLASS_ID) {
         if (fieldNameLen <= 3 && classId <= 63) { // at most 4 chars
           // little-endian reversed bits: 24 bits field name + 6 bits class id 
+ bit `1 0`.
           int encodedFieldInfo = (int) encodeFieldNameAsLong(fieldName);
@@ -744,7 +745,7 @@ public class FieldResolver {
         TypeToken<?> elementTypeToken =
             TypeUtils.getElementType(TypeToken.of(field.getGenericType()));
         byte fieldType =
-            Modifier.isFinal(getRawType(elementTypeToken).getModifiers())
+            ReflectionUtils.isMonomorphic(getRawType(elementTypeToken))
                 ? FieldTypes.COLLECTION_ELEMENT_FINAL
                 : FieldTypes.OBJECT;
         return new CollectionFieldInfo(
@@ -755,12 +756,12 @@ public class FieldResolver {
         TypeToken<?> keyTypeToken = kvType.f0;
         TypeToken<?> valueTypeToken = kvType.f1;
         byte fieldType;
-        if (Modifier.isFinal(getRawType(keyTypeToken).getModifiers())
-            && Modifier.isFinal(getRawType(valueTypeToken).getModifiers())) {
+        if (ReflectionUtils.isMonomorphic(getRawType(keyTypeToken))
+            && ReflectionUtils.isMonomorphic(getRawType(valueTypeToken))) {
           fieldType = FieldTypes.MAP_KV_FINAL;
-        } else if (Modifier.isFinal(getRawType(keyTypeToken).getModifiers())) {
+        } else if (ReflectionUtils.isMonomorphic(getRawType(keyTypeToken))) {
           fieldType = FieldTypes.MAP_KEY_FINAL;
-        } else if 
(Modifier.isFinal(getRawType(valueTypeToken).getModifiers())) {
+        } else if (ReflectionUtils.isMonomorphic(getRawType(valueTypeToken))) {
           fieldType = FieldTypes.MAP_VALUE_FINAL;
         } else {
           fieldType = FieldTypes.OBJECT;
@@ -930,10 +931,10 @@ public class FieldResolver {
       this.keyTypeToken = keyTypeToken;
       this.valueTypeToken = valueTypeToken;
       keyType = getRawType(keyTypeToken);
-      isKeyTypeFinal = Modifier.isFinal(keyType.getModifiers());
+      isKeyTypeFinal = ReflectionUtils.isMonomorphic(keyType);
       keyClassInfoHolder = classResolver.nilClassInfoHolder();
       valueType = getRawType(valueTypeToken);
-      isValueTypeFinal = Modifier.isFinal(valueType.getModifiers());
+      isValueTypeFinal = ReflectionUtils.isMonomorphic(valueType);
       valueClassInfoHolder = classResolver.nilClassInfoHolder();
     }
 
diff --git 
a/java/fury-core/src/main/java/org/apache/fury/serializer/ArraySerializers.java 
b/java/fury-core/src/main/java/org/apache/fury/serializer/ArraySerializers.java
index 52591797..27f33fff 100644
--- 
a/java/fury-core/src/main/java/org/apache/fury/serializer/ArraySerializers.java
+++ 
b/java/fury-core/src/main/java/org/apache/fury/serializer/ArraySerializers.java
@@ -20,7 +20,6 @@
 package org.apache.fury.serializer;
 
 import java.lang.reflect.Array;
-import java.lang.reflect.Modifier;
 import java.util.IdentityHashMap;
 import org.apache.fury.Fury;
 import org.apache.fury.memory.MemoryBuffer;
@@ -35,6 +34,7 @@ import org.apache.fury.type.Type;
 import org.apache.fury.type.TypeUtils;
 import org.apache.fury.util.Platform;
 import org.apache.fury.util.Preconditions;
+import org.apache.fury.util.ReflectionUtils;
 
 /** Serializers for array types. */
 public class ArraySerializers {
@@ -64,7 +64,7 @@ public class ArraySerializers {
       this.dimension = dimension;
       this.innerType = (Class<T>) innerType;
       Class<?> componentType = cls.getComponentType();
-      if (Modifier.isFinal(componentType.getModifiers())) {
+      if (ReflectionUtils.isMonomorphic(componentType)) {
         this.componentTypeSerializer = 
fury.getClassResolver().getSerializer(componentType);
       } else {
         // TODO add ClassInfo cache for non-final component type.
diff --git 
a/java/fury-core/src/main/java/org/apache/fury/serializer/MetaSharedSerializer.java
 
b/java/fury-core/src/main/java/org/apache/fury/serializer/MetaSharedSerializer.java
index 21e97543..4d51af9d 100644
--- 
a/java/fury-core/src/main/java/org/apache/fury/serializer/MetaSharedSerializer.java
+++ 
b/java/fury-core/src/main/java/org/apache/fury/serializer/MetaSharedSerializer.java
@@ -73,7 +73,7 @@ public class MetaSharedSerializer<T> extends Serializer<T> {
   /**
    * Whether write class def for non-inner final types.
    *
-   * @see ClassResolver#isFinal(Class)
+   * @see ClassResolver#isMonomorphic(Class)
    */
   private final boolean[] isFinal;
 
diff --git 
a/java/fury-core/src/main/java/org/apache/fury/serializer/ObjectSerializer.java 
b/java/fury-core/src/main/java/org/apache/fury/serializer/ObjectSerializer.java
index 218bf0d1..d652878a 100644
--- 
a/java/fury-core/src/main/java/org/apache/fury/serializer/ObjectSerializer.java
+++ 
b/java/fury-core/src/main/java/org/apache/fury/serializer/ObjectSerializer.java
@@ -74,7 +74,7 @@ public final class ObjectSerializer<T> extends Serializer<T> {
   /**
    * Whether write class def for non-inner final types.
    *
-   * @see ClassResolver#isFinal(Class)
+   * @see ClassResolver#isMonomorphic(Class)
    */
   private final boolean[] isFinal;
 
@@ -149,7 +149,7 @@ public final class ObjectSerializer<T> extends 
Serializer<T> {
     boolean[] isFinal = new boolean[finalFields.length];
     for (int i = 0; i < isFinal.length; i++) {
       ClassInfo classInfo = finalFields[i].classInfo;
-      isFinal[i] = classInfo != null && 
fury.getClassResolver().isFinal(classInfo.getCls());
+      isFinal[i] = classInfo != null && 
fury.getClassResolver().isMonomorphic(classInfo.getCls());
     }
     cnt = 0;
     GenericTypeField[] otherFields = new 
GenericTypeField[grouper.getOtherDescriptors().size()];
diff --git 
a/java/fury-core/src/main/java/org/apache/fury/serializer/collection/AbstractCollectionSerializer.java
 
b/java/fury-core/src/main/java/org/apache/fury/serializer/collection/AbstractCollectionSerializer.java
index 93baa9b9..e519d83f 100644
--- 
a/java/fury-core/src/main/java/org/apache/fury/serializer/collection/AbstractCollectionSerializer.java
+++ 
b/java/fury-core/src/main/java/org/apache/fury/serializer/collection/AbstractCollectionSerializer.java
@@ -116,7 +116,7 @@ public abstract class AbstractCollectionSerializer<T> 
extends Serializer<T> {
     GenericType elemGenericType = getElementGenericType(fury);
     if (elemGenericType != null) {
       boolean trackingRef = 
elemGenericType.trackingRef(fury.getClassResolver());
-      if (elemGenericType.isFinal()) {
+      if (elemGenericType.isMonomorphic()) {
         if (trackingRef) {
           buffer.writeByte(CollectionFlags.TRACKING_REF);
           return CollectionFlags.TRACKING_REF;
@@ -242,7 +242,7 @@ public abstract class AbstractCollectionSerializer<T> 
extends Serializer<T> {
   }
 
   /**
-   * Element type is not final by {@link ClassResolver#isFinal}, need to write 
element type.
+   * Element type is not final by {@link ClassResolver#isMonomorphic}, need to 
write element type.
    * Elements ref tracking is disabled, write whether any elements is null.
    */
   @CodegenInvoke
@@ -355,7 +355,7 @@ public abstract class AbstractCollectionSerializer<T> 
extends Serializer<T> {
     }
     // Note: ObjectSerializer should mark `FinalElemType` in 
`Collection<FinalElemType>`
     // as non-final to write class def when meta share is enabled.
-    if (elemGenericType.isFinal()) {
+    if (elemGenericType.isMonomorphic()) {
       Serializer serializer = 
elemGenericType.getSerializer(fury.getClassResolver());
       writeSameTypeElements(fury, buffer, serializer, flags, collection);
     } else {
@@ -450,7 +450,7 @@ public abstract class AbstractCollectionSerializer<T> 
extends Serializer<T> {
       if (hasGenericParameters) {
         fury.getGenerics().pushGenericType(elemGenericType);
       }
-      if (elemGenericType.isFinal()) {
+      if (elemGenericType.isMonomorphic()) {
         Serializer elemSerializer = 
elemGenericType.getSerializer(fury.getClassResolver());
         for (Object elem : value) {
           fury.xwriteRef(buffer, elem, elemSerializer);
@@ -586,7 +586,7 @@ public abstract class AbstractCollectionSerializer<T> 
extends Serializer<T> {
     if (hasGenericParameters) {
       fury.getGenerics().pushGenericType(elemGenericType);
     }
-    if (elemGenericType.isFinal()) {
+    if (elemGenericType.isMonomorphic()) {
       Serializer serializer = 
elemGenericType.getSerializer(fury.getClassResolver());
       readSameTypeElements(fury, buffer, serializer, flags, collection, 
numElements);
     } else {
@@ -686,7 +686,7 @@ public abstract class AbstractCollectionSerializer<T> 
extends Serializer<T> {
       if (hasGenericParameters) {
         fury.getGenerics().pushGenericType(elemGenericType);
       }
-      if (elemGenericType.isFinal()) {
+      if (elemGenericType.isMonomorphic()) {
         Serializer elemSerializer = 
elemGenericType.getSerializer(fury.getClassResolver());
         for (int i = 0; i < numElements; i++) {
           Object elem = fury.xreadRefByNullableSerializer(buffer, 
elemSerializer);
diff --git 
a/java/fury-core/src/main/java/org/apache/fury/serializer/collection/AbstractMapSerializer.java
 
b/java/fury-core/src/main/java/org/apache/fury/serializer/collection/AbstractMapSerializer.java
index 7f5de403..7da5217d 100644
--- 
a/java/fury-core/src/main/java/org/apache/fury/serializer/collection/AbstractMapSerializer.java
+++ 
b/java/fury-core/src/main/java/org/apache/fury/serializer/collection/AbstractMapSerializer.java
@@ -188,8 +188,8 @@ public abstract class AbstractMapSerializer<T> extends 
Serializer<T> {
       // generics.pushGenericType(keyGenericType);
       // fury.setDepth(depth);
       // generics.pushGenericType(valueGenericType);
-      boolean keyGenericTypeFinal = keyGenericType.isFinal();
-      boolean valueGenericTypeFinal = valueGenericType.isFinal();
+      boolean keyGenericTypeFinal = keyGenericType.isMonomorphic();
+      boolean valueGenericTypeFinal = valueGenericType.isMonomorphic();
       if (keyGenericTypeFinal && valueGenericTypeFinal) {
         javaKVTypesFinalWrite(fury, buffer, map, keyGenericType, 
valueGenericType, generics);
       } else if (keyGenericTypeFinal) {
@@ -453,8 +453,8 @@ public abstract class AbstractMapSerializer<T> extends 
Serializer<T> {
         keyGenericType = kvGenericType.f0;
         valueGenericType = kvGenericType.f1;
       }
-      boolean keyGenericTypeFinal = keyGenericType.isFinal();
-      boolean valueGenericTypeFinal = valueGenericType.isFinal();
+      boolean keyGenericTypeFinal = keyGenericType.isMonomorphic();
+      boolean valueGenericTypeFinal = valueGenericType.isMonomorphic();
       if (keyGenericTypeFinal && valueGenericTypeFinal) {
         javaKVTypesFinalRead(fury, buffer, map, keyGenericType, 
valueGenericType, generics, size);
       } else if (keyGenericTypeFinal) {
diff --git a/java/fury-core/src/main/java/org/apache/fury/type/ClassDef.java 
b/java/fury-core/src/main/java/org/apache/fury/type/ClassDef.java
index 21153b29..fa22d0af 100644
--- a/java/fury-core/src/main/java/org/apache/fury/type/ClassDef.java
+++ b/java/fury-core/src/main/java/org/apache/fury/type/ClassDef.java
@@ -366,14 +366,14 @@ public class ClassDef implements Serializable {
   }
 
   public abstract static class FieldType implements Serializable {
-    public FieldType(boolean isFinal) {
-      this.isFinal = isFinal;
+    public FieldType(boolean isMonomorphic) {
+      this.isMonomorphic = isMonomorphic;
     }
 
-    private final boolean isFinal;
+    private final boolean isMonomorphic;
 
-    public boolean isFinal() {
-      return isFinal;
+    public boolean isMonomorphic() {
+      return isMonomorphic;
     }
 
     /**
@@ -394,16 +394,16 @@ public class ClassDef implements Serializable {
         return false;
       }
       FieldType fieldType = (FieldType) o;
-      return isFinal == fieldType.isFinal;
+      return isMonomorphic == fieldType.isMonomorphic;
     }
 
     @Override
     public int hashCode() {
-      return Objects.hash(isFinal);
+      return Objects.hash(isMonomorphic);
     }
 
     public void write(MemoryBuffer buffer) {
-      buffer.writeBoolean(isFinal);
+      buffer.writeBoolean(isMonomorphic);
       if (this instanceof RegisteredFieldType) {
         buffer.writeByte(0);
         buffer.writeShort(((RegisteredFieldType) this).getClassId());
@@ -479,7 +479,12 @@ public class ClassDef implements Serializable {
 
     @Override
     public String toString() {
-      return "RegisteredFieldType{" + "isFinal=" + isFinal() + ", classId=" + 
classId + '}';
+      return "RegisteredFieldType{"
+          + "isMonomorphic="
+          + isMonomorphic()
+          + ", classId="
+          + classId
+          + '}';
     }
   }
 
@@ -530,7 +535,12 @@ public class ClassDef implements Serializable {
 
     @Override
     public String toString() {
-      return "CollectionFieldType{" + "elementType=" + elementType + ", 
isFinal=" + isFinal() + '}';
+      return "CollectionFieldType{"
+          + "elementType="
+          + elementType
+          + ", isFinal="
+          + isMonomorphic()
+          + '}';
     }
   }
 
@@ -593,7 +603,7 @@ public class ClassDef implements Serializable {
           + ", valueType="
           + valueType
           + ", isFinal="
-          + isFinal()
+          + isMonomorphic()
           + '}';
     }
   }
@@ -607,7 +617,7 @@ public class ClassDef implements Serializable {
 
     @Override
     public TypeToken<?> toTypeToken(ClassResolver classResolver) {
-      return isFinal() ? TypeToken.of(FinalObjectTypeStub.class) : 
TypeToken.of(Object.class);
+      return isMonomorphic() ? TypeToken.of(FinalObjectTypeStub.class) : 
TypeToken.of(Object.class);
     }
 
     @Override
@@ -662,7 +672,7 @@ public class ClassDef implements Serializable {
   /** Build field type from generics, nested generics will be extracted too. */
   private static FieldType buildFieldType(ClassResolver classResolver, 
GenericType genericType) {
     Preconditions.checkNotNull(genericType);
-    boolean isFinal = genericType.isFinal();
+    boolean isFinal = genericType.isMonomorphic();
     if (COLLECTION_TYPE.isSupertypeOf(genericType.typeToken)) {
       return new CollectionFieldType(
           isFinal,
diff --git a/java/fury-core/src/main/java/org/apache/fury/type/Descriptor.java 
b/java/fury-core/src/main/java/org/apache/fury/type/Descriptor.java
index 0f12f17d..35c339e4 100644
--- a/java/fury-core/src/main/java/org/apache/fury/type/Descriptor.java
+++ b/java/fury-core/src/main/java/org/apache/fury/type/Descriptor.java
@@ -143,6 +143,10 @@ public class Descriptor {
     return modifier;
   }
 
+  public boolean isFinalField() {
+    return Modifier.isFinal(modifier);
+  }
+
   public String getDeclaringClass() {
     return declaringClass;
   }
diff --git 
a/java/fury-core/src/main/java/org/apache/fury/type/DescriptorGrouper.java 
b/java/fury-core/src/main/java/org/apache/fury/type/DescriptorGrouper.java
index cbd39fa2..3a3f4273 100644
--- a/java/fury-core/src/main/java/org/apache/fury/type/DescriptorGrouper.java
+++ b/java/fury-core/src/main/java/org/apache/fury/type/DescriptorGrouper.java
@@ -22,13 +22,13 @@ package org.apache.fury.type;
 import static org.apache.fury.type.TypeUtils.getSizeOfPrimitiveType;
 
 import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Comparator;
 import java.util.List;
 import java.util.TreeSet;
 import java.util.function.Function;
+import org.apache.fury.util.ReflectionUtils;
 import org.apache.fury.util.record.RecordUtils;
 
 /**
@@ -163,7 +163,7 @@ public class DescriptorGrouper {
         collectionDescriptors.add(descriptorUpdator.apply(descriptor));
       } else if (TypeUtils.isMap(descriptor.getRawType())) {
         mapDescriptors.add(descriptorUpdator.apply(descriptor));
-      } else if (Modifier.isFinal(descriptor.getRawType().getModifiers())) {
+      } else if (ReflectionUtils.isMonomorphic(descriptor.getRawType())) {
         finalDescriptors.add(descriptorUpdator.apply(descriptor));
       } else {
         otherDescriptors.add(descriptorUpdator.apply(descriptor));
diff --git a/java/fury-core/src/main/java/org/apache/fury/type/GenericType.java 
b/java/fury-core/src/main/java/org/apache/fury/type/GenericType.java
index 7f1c55a4..84f2802a 100644
--- a/java/fury-core/src/main/java/org/apache/fury/type/GenericType.java
+++ b/java/fury-core/src/main/java/org/apache/fury/type/GenericType.java
@@ -23,7 +23,6 @@ import static org.apache.fury.type.TypeUtils.getRawType;
 
 import com.google.common.reflect.TypeToken;
 import java.lang.reflect.GenericArrayType;
-import java.lang.reflect.Modifier;
 import java.lang.reflect.ParameterizedType;
 import java.lang.reflect.Type;
 import java.lang.reflect.TypeVariable;
@@ -33,6 +32,7 @@ import java.util.List;
 import java.util.function.Predicate;
 import org.apache.fury.resolver.ClassResolver;
 import org.apache.fury.serializer.Serializer;
+import org.apache.fury.util.ReflectionUtils;
 
 /** GenericType for building java generics as a tree and binding with fury 
serializers. */
 // TODO(chaokunyang) refine generics which can be inspired by spring 
ResolvableType.
@@ -41,9 +41,9 @@ public class GenericType {
   static final Predicate<Type> defaultFinalPredicate =
       type -> {
         if (type.getClass() == Class.class) {
-          return Modifier.isFinal(((Class<?>) type).getModifiers());
+          return ReflectionUtils.isMonomorphic(((Class<?>) type));
         } else {
-          return Modifier.isFinal((getRawType(type)).getModifiers());
+          return ReflectionUtils.isMonomorphic((getRawType(type)));
         }
       };
 
@@ -54,18 +54,18 @@ public class GenericType {
   final GenericType typeParameter0;
   final GenericType typeParameter1;
   final boolean hasGenericParameters;
-  final boolean isFinal;
+  final boolean isMonomorphic;
   // Used to cache serializer for final class to avoid hash lookup for 
serializer.
   Serializer<?> serializer;
   private Boolean trackingRef;
 
-  public GenericType(TypeToken<?> typeToken, boolean isFinal, GenericType... 
typeParameters) {
+  public GenericType(TypeToken<?> typeToken, boolean isMonomorphic, 
GenericType... typeParameters) {
     this.typeToken = typeToken;
     this.cls = getRawType(typeToken);
     this.typeParameters = typeParameters;
     typeParametersCount = typeParameters.length;
     hasGenericParameters = typeParameters.length > 0;
-    this.isFinal = isFinal;
+    this.isMonomorphic = isMonomorphic;
     if (typeParameters.length > 0) {
       typeParameter0 = typeParameters[0];
     } else {
@@ -187,12 +187,12 @@ public class GenericType {
     return serializer;
   }
 
-  public boolean isFinal() {
-    return isFinal;
+  public boolean isMonomorphic() {
+    return isMonomorphic;
   }
 
   public Serializer<?> getSerializerOrNull(ClassResolver classResolver) {
-    if (isFinal) {
+    if (isMonomorphic) {
       return getSerializer(classResolver);
     } else {
       return null;
diff --git 
a/java/fury-core/src/main/java/org/apache/fury/util/ReflectionUtils.java 
b/java/fury-core/src/main/java/org/apache/fury/util/ReflectionUtils.java
index 30101631..d423f432 100644
--- a/java/fury-core/src/main/java/org/apache/fury/util/ReflectionUtils.java
+++ b/java/fury-core/src/main/java/org/apache/fury/util/ReflectionUtils.java
@@ -414,7 +414,19 @@ public class ReflectionUtils {
     return Modifier.isPrivate(cls.getModifiers());
   }
 
-  public static boolean isFinal(Class<?> targetType) {
+  /** Returns true if this type can be assigned from other types. */
+  public static boolean isPolyMorphic(Class<?> targetType) {
+    if (targetType.isArray()) {
+      return isPolyMorphic(targetType.getComponentType());
+    }
+    return !Modifier.isFinal(targetType.getModifiers());
+  }
+
+  /** Returns true if this type can't be assigned from other types. */
+  public static boolean isMonomorphic(Class<?> targetType) {
+    if (targetType.isArray()) {
+      return isMonomorphic(targetType.getComponentType());
+    }
     return Modifier.isFinal(targetType.getModifiers());
   }
 
diff --git 
a/java/fury-core/src/test/java/org/apache/fury/serializer/ArraySerializersTest.java
 
b/java/fury-core/src/test/java/org/apache/fury/serializer/ArraySerializersTest.java
index 2dba0908..7c226752 100644
--- 
a/java/fury-core/src/test/java/org/apache/fury/serializer/ArraySerializersTest.java
+++ 
b/java/fury-core/src/test/java/org/apache/fury/serializer/ArraySerializersTest.java
@@ -22,11 +22,14 @@ package org.apache.fury.serializer;
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertTrue;
 
+import java.lang.reflect.Array;
 import java.lang.reflect.Field;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicInteger;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
 import org.apache.fury.Fury;
 import org.apache.fury.FuryTestBase;
 import org.apache.fury.config.FuryBuilder;
@@ -34,6 +37,7 @@ import org.apache.fury.config.Language;
 import org.apache.fury.test.bean.ArraysData;
 import org.apache.fury.type.Descriptor;
 import org.apache.fury.util.ReflectionUtils;
+import org.testng.Assert;
 import org.testng.annotations.Test;
 
 public class ArraySerializersTest extends FuryTestBase {
@@ -153,4 +157,66 @@ public class ArraySerializersTest extends FuryTestBase {
       assertEquals(arraysData, serDeOutOfBand(counter, fury1, fury2, 
arraysData));
     }
   }
+
+  @EqualsAndHashCode
+  static class A {
+    final int f1;
+
+    A(int f1) {
+      this.f1 = f1;
+    }
+  }
+
+  @EqualsAndHashCode(callSuper = true)
+  static class B extends A {
+    final String f2;
+
+    B(int f1, String f2) {
+      super(f1);
+      this.f2 = f2;
+    }
+  }
+
+  @Data
+  static class Struct {
+    A[] arr;
+
+    public Struct(A[] arr) {
+      this.arr = arr;
+    }
+  }
+
+  static class GenericArrayWrapper<T> {
+    private final T[] array;
+
+    @SuppressWarnings("unchecked")
+    public GenericArrayWrapper(Class<T> clazz, int capacity) {
+      this.array = (T[]) Array.newInstance(clazz, capacity);
+    }
+  }
+
+  @SuppressWarnings("unchecked")
+  @Test(dataProvider = "enableCodegen")
+  public void testArrayPolyMorphic(boolean enableCodegen) {
+    Fury fury = 
Fury.builder().requireClassRegistration(false).withCodegen(enableCodegen).build();
+    Object[] arr = new String[] {"a", "b"};
+    serDeCheck(fury, arr);
+
+    A[] arr1 = new B[] {new B(1, "a"), new B(2, "b")};
+    serDeCheck(fury, arr1);
+
+    Struct struct1 = new Struct(arr1);
+    serDeCheck(fury, struct1);
+    A[] arr2 = new A[] {new A(1), new B(2, "b")};
+    Struct struct2 = new Struct(arr2);
+    serDeCheck(fury, struct2);
+
+    final GenericArrayWrapper<String> wrapper = new 
GenericArrayWrapper<>(String.class, 2);
+    wrapper.array[0] = "Hello";
+    final byte[] bytes = fury.serialize(wrapper);
+    final GenericArrayWrapper<String> deserialized =
+        (GenericArrayWrapper<String>) fury.deserialize(bytes);
+    deserialized.array[1] = "World";
+    Assert.assertEquals(deserialized.array, new String[] {"Hello", "World"});
+  }
 }
diff --git 
a/java/fury-core/src/test/java/org/apache/fury/serializer/MetaSharedCompatibleTest.java
 
b/java/fury-core/src/test/java/org/apache/fury/serializer/MetaSharedCompatibleTest.java
index 19320ba5..9d2f87ef 100644
--- 
a/java/fury-core/src/test/java/org/apache/fury/serializer/MetaSharedCompatibleTest.java
+++ 
b/java/fury-core/src/test/java/org/apache/fury/serializer/MetaSharedCompatibleTest.java
@@ -186,6 +186,71 @@ public class MetaSharedCompatibleTest extends FuryTestBase 
{
     }
   }
 
+  @Test
+  public void testWriteCompatibleCollectionSimple() throws Exception {
+    BeanA beanA = BeanA.createBeanA(2);
+    String pkg = BeanA.class.getPackage().getName();
+    String code =
+        ""
+            + "package "
+            + pkg
+            + ";\n"
+            + "import java.util.*;\n"
+            + "import java.math.*;\n"
+            + "public class BeanA {\n"
+            + "  private List<Double> doubleList;\n"
+            + "  private Iterable<BeanB> beanBIterable;\n"
+            + "  private List<BeanB> beanBList;\n"
+            + "}";
+    Class<?> cls1 =
+        loadClass(
+            BeanA.class,
+            code,
+            MetaSharedCompatibleTest.class + 
"testWriteCompatibleCollectionBasic_1");
+    Fury fury1 =
+        Fury.builder()
+            .withCodegen(false)
+            .withMetaContextShare(true)
+            .withCompatibleMode(CompatibleMode.COMPATIBLE)
+            .requireClassRegistration(false)
+            .withClassLoader(cls1.getClassLoader())
+            .build();
+    code =
+        ""
+            + "package "
+            + pkg
+            + ";\n"
+            + "import java.util.*;\n"
+            + "import java.math.*;\n"
+            + "public class BeanA {\n"
+            + "  private List<Double> doubleList;\n"
+            + "  private Iterable<BeanB> beanBIterable;\n"
+            + "}";
+    Class<?> cls2 =
+        loadClass(
+            BeanA.class,
+            code,
+            MetaSharedCompatibleTest.class + 
"testWriteCompatibleCollectionBasic_2");
+    Object o2 = cls2.newInstance();
+    ReflectionUtils.unsafeCopy(beanA, o2);
+    Fury fury2 =
+        Fury.builder()
+            .withCodegen(false)
+            .withMetaContextShare(true)
+            .withCompatibleMode(CompatibleMode.COMPATIBLE)
+            .requireClassRegistration(false)
+            .withClassLoader(cls2.getClassLoader())
+            .build();
+
+    MetaContext context1 = new MetaContext();
+    MetaContext context2 = new MetaContext();
+    fury1.getSerializationContext().setMetaContext(context1);
+    byte[] objBytes = fury1.serialize(beanA);
+    fury2.getSerializationContext().setMetaContext(context2);
+    Object obj2 = fury2.deserialize(objBytes);
+    Assert.assertTrue(ReflectionUtils.objectCommonFieldsEquals(obj2, o2));
+  }
+
   @Test(dataProvider = "config3")
   public void testWriteCompatibleCollectionBasic(
       boolean referenceTracking,
@@ -195,16 +260,6 @@ public class MetaSharedCompatibleTest extends FuryTestBase 
{
       boolean enableCodegen3)
       throws Exception {
     BeanA beanA = BeanA.createBeanA(2);
-    Fury fury =
-        Fury.builder()
-            .withLanguage(Language.JAVA)
-            .withRefTracking(referenceTracking)
-            .withNumberCompressed(compressNumber)
-            .withCodegen(enableCodegen1)
-            .withMetaContextShare(true)
-            .withCompatibleMode(CompatibleMode.COMPATIBLE)
-            .requireClassRegistration(false)
-            .build();
     String pkg = BeanA.class.getPackage().getName();
     String code =
         ""
@@ -234,7 +289,6 @@ public class MetaSharedCompatibleTest extends FuryTestBase {
             .requireClassRegistration(false)
             .withClassLoader(cls1.getClassLoader())
             .build();
-    MetaContext context1 = new MetaContext();
     code =
         ""
             + "package "
@@ -264,6 +318,8 @@ public class MetaSharedCompatibleTest extends FuryTestBase {
             .requireClassRegistration(false)
             .withClassLoader(cls2.getClassLoader())
             .build();
+
+    MetaContext context1 = new MetaContext();
     MetaContext context2 = new MetaContext();
     fury2.getSerializationContext().setMetaContext(context2);
     byte[] bytes2 = fury2.serialize(o2);
@@ -282,6 +338,16 @@ public class MetaSharedCompatibleTest extends FuryTestBase 
{
     Object obj2 = fury2.deserialize(objBytes);
     Assert.assertTrue(ReflectionUtils.objectCommonFieldsEquals(obj2, o2));
 
+    Fury fury =
+        Fury.builder()
+            .withLanguage(Language.JAVA)
+            .withRefTracking(referenceTracking)
+            .withNumberCompressed(compressNumber)
+            .withCodegen(enableCodegen1)
+            .withMetaContextShare(true)
+            .withCompatibleMode(CompatibleMode.COMPATIBLE)
+            .requireClassRegistration(false)
+            .build();
     // fury <-> fury2 is a new channel, which needs a new context.
     MetaContext context = new MetaContext();
     MetaContext ctx2 = new MetaContext();


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

Reply via email to