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 3a0e410c feat(java): support nonexistent class deserialization in meta 
share mode (#1646)
3a0e410c is described below

commit 3a0e410cb83756d3b139a9fbed01a9a64dbb2970
Author: Shawn Yang <[email protected]>
AuthorDate: Mon May 27 10:30:06 2024 +0800

    feat(java): support nonexistent class deserialization in meta share mode 
(#1646)
    
    ## What does this PR do?
    
    support nonexistent class deserialization in meta share mode
    
    ## Related issues
    
    <!--
    Is there any related issue? Please attach here.
    
    - #xxxx0
    - #xxxx1
    - #xxxx2
    -->
    
    
    ## Does this PR introduce any user-facing change?
    
    <!--
    If any user-facing interface changes, please [open an
    issue](https://github.com/apache/incubator-fury/issues/new/choose)
    describing the need to do so and update the document if necessary.
    -->
    
    - [ ] Does this PR introduce any public API change?
    - [ ] Does this PR introduce any binary protocol compatibility change?
    
    
    ## Benchmark
    
    <!--
    When the PR has an impact on performance (if you don't know whether the
    PR will have an impact on performance, you can submit the PR first, and
    if it will have impact on performance, the code reviewer will explain
    it), be sure to attach a benchmark data here.
    -->
---
 docs/guide/java_serialization_guide.md             |   6 +-
 .../main/java/org/apache/fury/config/Config.java   |  14 +-
 .../java/org/apache/fury/config/FuryBuilder.java   |  16 +-
 .../main/java/org/apache/fury/meta/ClassDef.java   | 197 +++++++++++++++------
 .../java/org/apache/fury/meta/ClassDefEncoder.java |  25 +--
 .../org/apache/fury/resolver/ClassResolver.java    |  91 +++++-----
 .../apache/fury/serializer/ArraySerializers.java   |  59 +++---
 .../apache/fury/serializer/NonexistentClass.java   | 167 +++++++++++++++++
 ...izers.java => NonexistentClassSerializers.java} |  86 ++++-----
 .../main/java/org/apache/fury/type/Descriptor.java |   5 +-
 .../main/java/org/apache/fury/type/TypeUtils.java  |  14 ++
 ...t.java => NonexistentClassSerializersTest.java} |  54 +++---
 .../collection/ChildContainerSerializersTest.java  |   2 +-
 .../fury-testsuite/src/test/java/org/test/Org.java |   2 +-
 14 files changed, 496 insertions(+), 242 deletions(-)

diff --git a/docs/guide/java_serialization_guide.md 
b/docs/guide/java_serialization_guide.md
index a86c39e4..ec9fdf38 100644
--- a/docs/guide/java_serialization_guide.md
+++ b/docs/guide/java_serialization_guide.md
@@ -110,7 +110,7 @@ public class Example {
 | `requireClassRegistration`          | Disabling may allow unknown classes to 
be deserialized, potentially causing security risks.                            
                                                                                
                                                                                
                                                                                
                                                                                
              [...]
 | `suppressClassRegistrationWarnings` | Whether to suppress class registration 
warnings. The warnings can be used for security audit, but may be annoying, 
this suppression will be enabled by default.                                    
                                                                                
                                                                                
                                                                                
                  [...]
 | `shareMetaContext`                  | Enables or disables meta share mode.   
                                                                                
                                                                                
                                                                                
                                                                                
                                                                                
              [...]
-| `deserializeUnexistedClass`         | Enables or disables 
deserialization/skipping of data for non-existent classes.                      
                                                                                
                                                                                
                                                                                
                                                                                
                                 [...]
+| `deserializeNonexistentClass`       | Enables or disables 
deserialization/skipping of data for non-existent classes.                      
                                                                                
                                                                                
                                                                                
                                                                                
                                 [...]
 | `codeGenEnabled`                    | Disabling may result in faster initial 
serialization but slower subsequent serializations.                             
                                                                                
                                                                                
                                                                                
                                                                                
              [...]
 | `asyncCompilationEnabled`           | If enabled, serialization uses 
interpreter mode first and switches to JIT serialization after async serializer 
JIT for a class is finished.                                                    
                                                                                
                                                                                
                                                                                
                      [...]
 | `scalaOptimizationEnabled`          | Enables or disables Scala-specific 
serialization optimization.                                                     
                                                                                
                                                                                
                                                                                
                                                                                
                  [...]
@@ -363,13 +363,13 @@ MetaContext context=xxx;
 ### Deserialize non-existent classes
 
 Fury support deserializing non-existent classes, this feature can be enabled
-by `FuryBuilder#deserializeUnexistedClass(true)`. When enabled, and metadata 
sharing enabled, Fury will store
+by `FuryBuilder#deserializeNonexistentClass(true)`. When enabled, and metadata 
sharing enabled, Fury will store
 the deserialized data of this type in a lazy subclass of Map. By using the 
lazy map implemented by Fury, the rebalance
 cost of filling map during deserialization can be avoided, which further 
improves performance. If this data is sent to
 another process and the class exists in this process, the data will be 
deserialized into the object of this type without
 losing any information.
 
-If metadata sharing is not enabled, the new class data will be skipped and an 
`UnexistedSkipClass` stub object will be
+If metadata sharing is not enabled, the new class data will be skipped and an 
`NonexistentSkipClass` stub object will be
 returned.
 
 ## Migration
diff --git a/java/fury-core/src/main/java/org/apache/fury/config/Config.java 
b/java/fury-core/src/main/java/org/apache/fury/config/Config.java
index 2e7deb60..cb563853 100644
--- a/java/fury-core/src/main/java/org/apache/fury/config/Config.java
+++ b/java/fury-core/src/main/java/org/apache/fury/config/Config.java
@@ -51,7 +51,7 @@ public class Config implements Serializable {
   private final boolean registerGuavaTypes;
   private final boolean shareMetaContext;
   private final boolean asyncCompilationEnabled;
-  private final boolean deserializeUnexistedClass;
+  private final boolean deserializeNonexistentClass;
   private final boolean scalaOptimizationEnabled;
   private transient int configHash;
   private final boolean deserializeNonexistentEnumValueAsNull;
@@ -75,8 +75,8 @@ public class Config implements Serializable {
     checkJdkClassSerializable = builder.checkJdkClassSerializable;
     defaultJDKStreamSerializerType = builder.defaultJDKStreamSerializerType;
     shareMetaContext = builder.shareMetaContext;
-    deserializeUnexistedClass = builder.deserializeUnexistedClass;
-    if (deserializeUnexistedClass) {
+    deserializeNonexistentClass = builder.deserializeNonexistentClass;
+    if (deserializeNonexistentClass) {
       // Only in meta share mode or compatibleMode, fury knows how to 
deserialize
       // unexisted class by type info in data.
       Preconditions.checkArgument(shareMetaContext || compatibleMode == 
CompatibleMode.COMPATIBLE);
@@ -186,8 +186,8 @@ public class Config implements Serializable {
    * Whether deserialize/skip data of un-existed class. If not enabled, an 
exception will be thrown
    * if class not exist.
    */
-  public boolean deserializeUnexistedClass() {
-    return deserializeUnexistedClass;
+  public boolean deserializeNonexistentClass() {
+    return deserializeNonexistentClass;
   }
 
   /**
@@ -237,7 +237,7 @@ public class Config implements Serializable {
         && registerGuavaTypes == config.registerGuavaTypes
         && shareMetaContext == config.shareMetaContext
         && asyncCompilationEnabled == config.asyncCompilationEnabled
-        && deserializeUnexistedClass == config.deserializeUnexistedClass
+        && deserializeNonexistentClass == config.deserializeNonexistentClass
         && scalaOptimizationEnabled == config.scalaOptimizationEnabled
         && language == config.language
         && compatibleMode == config.compatibleMode
@@ -267,7 +267,7 @@ public class Config implements Serializable {
         registerGuavaTypes,
         shareMetaContext,
         asyncCompilationEnabled,
-        deserializeUnexistedClass,
+        deserializeNonexistentClass,
         scalaOptimizationEnabled);
   }
 
diff --git 
a/java/fury-core/src/main/java/org/apache/fury/config/FuryBuilder.java 
b/java/fury-core/src/main/java/org/apache/fury/config/FuryBuilder.java
index 9df183a9..8b582682 100644
--- a/java/fury-core/src/main/java/org/apache/fury/config/FuryBuilder.java
+++ b/java/fury-core/src/main/java/org/apache/fury/config/FuryBuilder.java
@@ -70,7 +70,7 @@ public final class FuryBuilder {
   boolean requireClassRegistration = true;
   boolean shareMetaContext = false;
   boolean codeGenEnabled = true;
-  Boolean deserializeUnexistedClass;
+  Boolean deserializeNonexistentClass;
   boolean asyncCompilationEnabled = false;
   boolean registerGuavaTypes = true;
   boolean scalaOptimizationEnabled = false;
@@ -241,10 +241,10 @@ public final class FuryBuilder {
   /**
    * Whether deserialize/skip data of un-existed class.
    *
-   * @see Config#deserializeUnexistedClass()
+   * @see Config#deserializeNonexistentClass()
    */
-  public FuryBuilder withDeserializeUnexistedClass(boolean 
deserializeUnexistedClass) {
-    this.deserializeUnexistedClass = deserializeUnexistedClass;
+  public FuryBuilder withDeserializeNonexistentClass(boolean 
deserializeNonexistentClass) {
+    this.deserializeNonexistentClass = deserializeNonexistentClass;
     return this;
   }
 
@@ -301,12 +301,12 @@ public final class FuryBuilder {
     }
     if (compatibleMode == CompatibleMode.COMPATIBLE) {
       checkClassVersion = false;
-      if (deserializeUnexistedClass == null) {
-        deserializeUnexistedClass = true;
+      if (deserializeNonexistentClass == null) {
+        deserializeNonexistentClass = true;
       }
     } else {
-      if (deserializeUnexistedClass == null) {
-        deserializeUnexistedClass = false;
+      if (deserializeNonexistentClass == null) {
+        deserializeNonexistentClass = false;
       }
     }
     if (!requireClassRegistration) {
diff --git a/java/fury-core/src/main/java/org/apache/fury/meta/ClassDef.java 
b/java/fury-core/src/main/java/org/apache/fury/meta/ClassDef.java
index 24897ae1..ccea625e 100644
--- a/java/fury-core/src/main/java/org/apache/fury/meta/ClassDef.java
+++ b/java/fury-core/src/main/java/org/apache/fury/meta/ClassDef.java
@@ -27,9 +27,9 @@ import static org.apache.fury.type.TypeUtils.mapOf;
 
 import java.io.ObjectStreamClass;
 import java.io.Serializable;
+import java.lang.reflect.Array;
 import java.lang.reflect.Field;
 import java.util.ArrayList;
-import java.util.Collection;
 import java.util.Comparator;
 import java.util.HashMap;
 import java.util.List;
@@ -38,6 +38,7 @@ import java.util.Objects;
 import java.util.SortedMap;
 import org.apache.fury.Fury;
 import org.apache.fury.builder.MetaSharedCodecBuilder;
+import org.apache.fury.collection.Tuple2;
 import org.apache.fury.config.CompatibleMode;
 import org.apache.fury.config.FuryBuilder;
 import org.apache.fury.logging.Logger;
@@ -48,9 +49,11 @@ import org.apache.fury.reflect.ReflectionUtils;
 import org.apache.fury.reflect.TypeRef;
 import org.apache.fury.resolver.ClassResolver;
 import org.apache.fury.serializer.CompatibleSerializer;
+import org.apache.fury.serializer.NonexistentClass;
 import org.apache.fury.type.Descriptor;
 import org.apache.fury.type.FinalObjectTypeStub;
 import org.apache.fury.type.GenericType;
+import org.apache.fury.type.TypeUtils;
 import org.apache.fury.util.Preconditions;
 
 /**
@@ -332,7 +335,7 @@ public class ClassDef implements Serializable {
       this.isMonomorphic = isMonomorphic;
     }
 
-    private final boolean isMonomorphic;
+    protected final boolean isMonomorphic;
 
     public boolean isMonomorphic() {
       return isMonomorphic;
@@ -364,31 +367,47 @@ public class ClassDef implements Serializable {
       return Objects.hash(isMonomorphic);
     }
 
-    public void write(MemoryBuffer buffer) {
+    /** Write field type info. */
+    public void write(MemoryBuffer buffer, boolean writeMonomorphicFlag) {
       byte header = (byte) (isMonomorphic ? 1 : 0);
-      if (this instanceof RegisteredFieldType) {
-        short classId = ((RegisteredFieldType) this).getClassId();
-        buffer.writeVarUint32Small7(((3 + classId) << 1) | header);
-      } else if (this instanceof CollectionFieldType) {
-        buffer.writeVarUint32Small7((2 << 1) | header);
-        ((CollectionFieldType) this).elementType.write(buffer);
-      } else if (this instanceof MapFieldType) {
-        buffer.writeVarUint32Small7((1 << 1) | header);
-        MapFieldType mapFieldType = (MapFieldType) this;
-        mapFieldType.keyType.write(buffer);
-        mapFieldType.valueType.write(buffer);
+      if (this instanceof ClassDef.RegisteredFieldType) {
+        short classId = ((ClassDef.RegisteredFieldType) this).getClassId();
+        buffer.writeVarUint32Small7(
+            writeMonomorphicFlag ? ((5 + classId) << 1) | header : 5 + 
classId);
+      } else if (this instanceof ClassDef.EnumFieldType) {
+        buffer.writeVarUint32Small7(writeMonomorphicFlag ? ((4) << 1) | header 
: 4);
+      } else if (this instanceof ClassDef.ArrayFieldType) {
+        ClassDef.ArrayFieldType arrayFieldType = (ClassDef.ArrayFieldType) 
this;
+        buffer.writeVarUint32Small7(writeMonomorphicFlag ? ((3) << 1) | header 
: 3);
+        buffer.writeVarUint32Small7(arrayFieldType.getDimensions());
+        (arrayFieldType).getComponentType().write(buffer);
+      } else if (this instanceof ClassDef.CollectionFieldType) {
+        buffer.writeVarUint32Small7(writeMonomorphicFlag ? ((2) << 1) | header 
: 2);
+        // TODO remove it when new collection deserialization jit finished.
+        ((ClassDef.CollectionFieldType) this).getElementType().write(buffer);
+      } else if (this instanceof ClassDef.MapFieldType) {
+        buffer.writeVarUint32Small7(writeMonomorphicFlag ? ((1) << 1) | header 
: 1);
+        // TODO remove it when new map deserialization jit finished.
+        ClassDef.MapFieldType mapFieldType = (ClassDef.MapFieldType) this;
+        mapFieldType.getKeyType().write(buffer);
+        mapFieldType.getValueType().write(buffer);
       } else {
-        Preconditions.checkArgument(this instanceof ObjectFieldType);
-        buffer.writeVarUint32Small7(header);
+        Preconditions.checkArgument(this instanceof ClassDef.ObjectFieldType);
+        buffer.writeVarUint32Small7(writeMonomorphicFlag ? header : 0);
       }
     }
 
+    public void write(MemoryBuffer buffer) {
+      write(buffer, true);
+    }
+
     public static FieldType read(MemoryBuffer buffer) {
       int header = buffer.readVarUint32Small7();
       boolean isMonomorphic = (header & 0b1) != 0;
       return read(buffer, isMonomorphic, header >>> 1);
     }
 
+    /** Read field type info. */
     public static FieldType read(MemoryBuffer buffer, boolean isFinal, int 
typeId) {
       if (typeId == 0) {
         return new ObjectFieldType(isFinal);
@@ -396,8 +415,13 @@ public class ClassDef implements Serializable {
         return new MapFieldType(isFinal, read(buffer), read(buffer));
       } else if (typeId == 2) {
         return new CollectionFieldType(isFinal, read(buffer));
+      } else if (typeId == 3) {
+        int dims = buffer.readVarUint32Small7();
+        return new ArrayFieldType(isFinal, read(buffer), dims);
+      } else if (typeId == 4) {
+        return EnumFieldType.getInstance();
       } else {
-        return new RegisteredFieldType(isFinal, (short) (typeId - 3));
+        return new RegisteredFieldType(isFinal, (short) (typeId - 5));
       }
     }
   }
@@ -571,6 +595,89 @@ public class ClassDef implements Serializable {
     }
   }
 
+  public static class EnumFieldType extends FieldType {
+    private static final EnumFieldType INSTANCE = new EnumFieldType();
+
+    private EnumFieldType() {
+      super(true);
+    }
+
+    @Override
+    public TypeRef<?> toTypeToken(ClassResolver classResolver) {
+      return TypeRef.of(NonexistentClass.NonexistentEnum.class);
+    }
+
+    public static EnumFieldType getInstance() {
+      return INSTANCE;
+    }
+  }
+
+  public static class ArrayFieldType extends FieldType {
+    private final FieldType componentType;
+    private final int dimensions;
+
+    public ArrayFieldType(boolean isMonomorphic, FieldType componentType, int 
dimensions) {
+      super(isMonomorphic);
+      this.componentType = componentType;
+      this.dimensions = dimensions;
+    }
+
+    @Override
+    public TypeRef<?> toTypeToken(ClassResolver classResolver) {
+      TypeRef<?> componentTypeRef = componentType.toTypeToken(classResolver);
+      Class<?> componentRawType = componentTypeRef.getRawType();
+      if (NonexistentClass.class.isAssignableFrom(componentRawType)) {
+        return TypeRef.of(
+            // We embed `isMonomorphic` flag in ObjectArraySerializer, so this 
flag can be ignored
+            // here.
+            NonexistentClass.getUnexistentClass(
+                componentType instanceof EnumFieldType, dimensions, true));
+      } else {
+        return TypeRef.of(Array.newInstance(componentRawType, new 
int[dimensions]).getClass());
+      }
+    }
+
+    public int getDimensions() {
+      return dimensions;
+    }
+
+    public FieldType getComponentType() {
+      return componentType;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) {
+        return true;
+      }
+      if (o == null || getClass() != o.getClass()) {
+        return false;
+      }
+      if (!super.equals(o)) {
+        return false;
+      }
+      ArrayFieldType that = (ArrayFieldType) o;
+      return dimensions == that.dimensions && Objects.equals(componentType, 
that.componentType);
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(super.hashCode(), componentType, dimensions);
+    }
+
+    @Override
+    public String toString() {
+      return "ArrayFieldType{"
+          + "componentType="
+          + componentType
+          + ", dimensions="
+          + dimensions
+          + ", isMonomorphic="
+          + isMonomorphic
+          + '}';
+    }
+  }
+
   /** Class for field type which isn't registered and not collection/map type 
too. */
   public static class ObjectFieldType extends FieldType {
 
@@ -597,48 +704,18 @@ public class ClassDef implements Serializable {
   /** Build field type from generics, nested generics will be extracted too. */
   static FieldType buildFieldType(ClassResolver classResolver, Field field) {
     Preconditions.checkNotNull(field);
-    Class<?> rawType = field.getType();
-    boolean isFinal = classResolver.isMonomorphic(rawType);
-    if (Collection.class.isAssignableFrom(rawType)) {
-      GenericType genericType = GenericType.build(field.getGenericType());
-      return new CollectionFieldType(
-          isFinal,
-          buildFieldType(
-              classResolver,
-              genericType.getTypeParameter0() == null
-                  ? GenericType.build(Object.class)
-                  : genericType.getTypeParameter0()));
-    } else if (Map.class.isAssignableFrom(rawType)) {
-      GenericType genericType = GenericType.build(field.getGenericType());
-      return new MapFieldType(
-          isFinal,
-          buildFieldType(
-              classResolver,
-              genericType.getTypeParameter0() == null
-                  ? GenericType.build(Object.class)
-                  : genericType.getTypeParameter0()),
-          buildFieldType(
-              classResolver,
-              genericType.getTypeParameter1() == null
-                  ? GenericType.build(Object.class)
-                  : genericType.getTypeParameter1()));
-    } else {
-      Short classId = classResolver.getRegisteredClassId(rawType);
-      if (classId != null && classId != ClassResolver.NO_CLASS_ID) {
-        return new RegisteredFieldType(isFinal, classId);
-      } else {
-        return new ObjectFieldType(isFinal);
-      }
-    }
+    GenericType genericType = GenericType.build(field.getGenericType());
+    return buildFieldType(classResolver, genericType);
   }
 
   /** 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.isMonomorphic();
+    Class<?> rawType = genericType.getCls();
+    boolean isMonomorphic = genericType.isMonomorphic();
     if (COLLECTION_TYPE.isSupertypeOf(genericType.getTypeRef())) {
       return new CollectionFieldType(
-          isFinal,
+          isMonomorphic,
           buildFieldType(
               classResolver,
               genericType.getTypeParameter0() == null
@@ -646,7 +723,7 @@ public class ClassDef implements Serializable {
                   : genericType.getTypeParameter0()));
     } else if (MAP_TYPE.isSupertypeOf(genericType.getTypeRef())) {
       return new MapFieldType(
-          isFinal,
+          isMonomorphic,
           buildFieldType(
               classResolver,
               genericType.getTypeParameter0() == null
@@ -658,11 +735,19 @@ public class ClassDef implements Serializable {
                   ? GenericType.build(Object.class)
                   : genericType.getTypeParameter1()));
     } else {
-      Short classId = classResolver.getRegisteredClassId(genericType.getCls());
+      Short classId = classResolver.getRegisteredClassId(rawType);
       if (classId != null && classId != ClassResolver.NO_CLASS_ID) {
-        return new RegisteredFieldType(isFinal, classId);
+        return new RegisteredFieldType(isMonomorphic, classId);
       } else {
-        return new ObjectFieldType(isFinal);
+        if (rawType.isEnum()) {
+          return EnumFieldType.getInstance();
+        }
+        if (rawType.isArray()) {
+          Tuple2<Class<?>, Integer> info = 
TypeUtils.getArrayComponentInfo(rawType);
+          return new ArrayFieldType(
+              isMonomorphic, buildFieldType(classResolver, 
GenericType.build(info.f0)), info.f1);
+        }
+        return new ObjectFieldType(isMonomorphic);
       }
     }
   }
diff --git 
a/java/fury-core/src/main/java/org/apache/fury/meta/ClassDefEncoder.java 
b/java/fury-core/src/main/java/org/apache/fury/meta/ClassDefEncoder.java
index 1d0c2e4a..652d96b0 100644
--- a/java/fury-core/src/main/java/org/apache/fury/meta/ClassDefEncoder.java
+++ b/java/fury-core/src/main/java/org/apache/fury/meta/ClassDefEncoder.java
@@ -40,12 +40,12 @@ import org.apache.fury.memory.MemoryBuffer;
 import org.apache.fury.memory.MemoryUtils;
 import org.apache.fury.memory.Platform;
 import org.apache.fury.meta.ClassDef.FieldInfo;
+import org.apache.fury.meta.ClassDef.FieldType;
 import org.apache.fury.reflect.ReflectionUtils;
 import org.apache.fury.resolver.ClassResolver;
 import org.apache.fury.type.Descriptor;
 import org.apache.fury.type.DescriptorGrouper;
 import org.apache.fury.util.MurmurHash3;
-import org.apache.fury.util.Preconditions;
 
 /**
  * An encoder which encode {@link ClassDef} into binary. See spec 
documentation:
@@ -216,9 +216,10 @@ class ClassDefEncoder {
     return classFields;
   }
 
-  private static void writeFieldsInfo(MemoryBuffer buffer, List<FieldInfo> 
fields) {
+  /** Write field type and name info. */
+  static void writeFieldsInfo(MemoryBuffer buffer, List<FieldInfo> fields) {
     for (FieldInfo fieldInfo : fields) {
-      ClassDef.FieldType fieldType = fieldInfo.getFieldType();
+      FieldType fieldType = fieldInfo.getFieldType();
       // `3 bits size + 2 bits field name encoding + polymorphism flag + 
nullability flag + ref
       // tracking flag`
       int header = ((fieldType.isMonomorphic() ? 1 : 0) << 2);
@@ -244,23 +245,7 @@ class ClassDefEncoder {
       if (!fieldInfo.hasTypeTag()) {
         buffer.writeBytes(encoded);
       }
-      if (fieldType instanceof ClassDef.RegisteredFieldType) {
-        short classId = ((ClassDef.RegisteredFieldType) 
fieldType).getClassId();
-        buffer.writeVarUint32Small7(3 + classId);
-      } else if (fieldType instanceof ClassDef.CollectionFieldType) {
-        buffer.writeVarUint32Small7(2);
-        // TODO remove it when new collection deserialization jit finished.
-        ((ClassDef.CollectionFieldType) 
fieldType).getElementType().write(buffer);
-      } else if (fieldType instanceof ClassDef.MapFieldType) {
-        buffer.writeVarUint32Small7(1);
-        // TODO remove it when new map deserialization jit finished.
-        ClassDef.MapFieldType mapFieldType = (ClassDef.MapFieldType) fieldType;
-        mapFieldType.getKeyType().write(buffer);
-        mapFieldType.getValueType().write(buffer);
-      } else {
-        Preconditions.checkArgument(fieldType instanceof 
ClassDef.ObjectFieldType);
-        buffer.writeVarUint32Small7(0);
-      }
+      fieldType.write(buffer, false);
     }
   }
 
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 30ec3b9b..23e3ccdf 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
@@ -102,8 +102,6 @@ import org.apache.fury.meta.MetaString;
 import org.apache.fury.reflect.ReflectionUtils;
 import org.apache.fury.reflect.TypeRef;
 import org.apache.fury.serializer.ArraySerializers;
-import 
org.apache.fury.serializer.ArraySerializers.UnexistedArrayClassSerializer;
-import 
org.apache.fury.serializer.ArraySerializers.UnexistedEnumArrayClassSerializer;
 import org.apache.fury.serializer.BufferSerializers;
 import org.apache.fury.serializer.CodegenSerializer.LazyInitBeanSerializer;
 import org.apache.fury.serializer.CompatibleSerializer;
@@ -114,6 +112,11 @@ import org.apache.fury.serializer.JdkProxySerializer;
 import org.apache.fury.serializer.LambdaSerializer;
 import org.apache.fury.serializer.LocaleSerializer;
 import org.apache.fury.serializer.MetaSharedSerializer;
+import org.apache.fury.serializer.NonexistentClass;
+import org.apache.fury.serializer.NonexistentClass.NonexistentMetaShared;
+import org.apache.fury.serializer.NonexistentClass.NonexistentSkip;
+import org.apache.fury.serializer.NonexistentClassSerializers;
+import 
org.apache.fury.serializer.NonexistentClassSerializers.NonexistentClassSerializer;
 import org.apache.fury.serializer.ObjectSerializer;
 import org.apache.fury.serializer.OptionalSerializers;
 import org.apache.fury.serializer.PrimitiveSerializers;
@@ -124,13 +127,6 @@ import org.apache.fury.serializer.Serializers;
 import org.apache.fury.serializer.StringSerializer;
 import org.apache.fury.serializer.StructSerializer;
 import org.apache.fury.serializer.TimeSerializers;
-import 
org.apache.fury.serializer.UnexistedClassSerializers.UnexistedArrayClass;
-import 
org.apache.fury.serializer.UnexistedClassSerializers.UnexistedClassSerializer;
-import 
org.apache.fury.serializer.UnexistedClassSerializers.UnexistedEnumArrayClass;
-import 
org.apache.fury.serializer.UnexistedClassSerializers.UnexistedEnumClassSerializer;
-import 
org.apache.fury.serializer.UnexistedClassSerializers.UnexistedMetaSharedClass;
-import org.apache.fury.serializer.UnexistedClassSerializers.UnexistedSkipClass;
-import 
org.apache.fury.serializer.UnexistedClassSerializers.UnexistedSkipEnumClass;
 import org.apache.fury.serializer.collection.ChildContainerSerializers;
 import org.apache.fury.serializer.collection.CollectionSerializer;
 import org.apache.fury.serializer.collection.CollectionSerializers;
@@ -337,18 +333,18 @@ public class ClassResolver {
     if (fury.getConfig().registerGuavaTypes()) {
       GuavaCollectionSerializers.registerDefaultSerializers(fury);
     }
-    if (fury.getConfig().deserializeUnexistedClass()) {
+    if (fury.getConfig().deserializeNonexistentClass()) {
       if (metaContextShareEnabled) {
         addDefaultSerializer(
-            UnexistedMetaSharedClass.class, new UnexistedClassSerializer(fury, 
null));
+            NonexistentMetaShared.class, new NonexistentClassSerializer(fury, 
null));
         // Those class id must be known in advance, here is two bytes, so
-        // `UnexistedClassSerializer.writeClassDef`
+        // `NonexistentClassSerializer.writeClassDef`
         // can overwrite written classinfo and replace with real classinfo.
         short classId =
-            
Objects.requireNonNull(classInfoMap.get(UnexistedMetaSharedClass.class)).classId;
+            
Objects.requireNonNull(classInfoMap.get(NonexistentMetaShared.class)).classId;
         Preconditions.checkArgument(classId > 63 && classId < 8192, classId);
       } else {
-        register(UnexistedSkipClass.class);
+        register(NonexistentSkip.class);
       }
     }
   }
@@ -519,9 +515,17 @@ public class ClassResolver {
     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));
+      if (!ReflectionUtils.isMonomorphic(clz)) {
+        return false;
+      }
+      if (Map.class.isAssignableFrom(clz) || 
Collection.class.isAssignableFrom(clz)) {
+        return false;
+      }
+      if (clz.isArray()) {
+        Class<?> component = TypeUtils.getArrayComponent(clz);
+        return isMonomorphic(component);
+      }
+      return (isInnerClass(clz) || clz.isEnum());
     }
     return ReflectionUtils.isMonomorphic(clz);
   }
@@ -985,7 +989,9 @@ public class ClassResolver {
         }
       }
     } else {
-      LOG.info("Object of type {} can't be serialized by jit", cls);
+      if (fury.getConfig().isCodeGenEnabled()) {
+        LOG.info("Object of type {} can't be serialized by jit", cls);
+      }
       switch (fury.getCompatibleMode()) {
         case SCHEMA_CONSISTENT:
           return ObjectSerializer.class;
@@ -1276,7 +1282,7 @@ public class ClassResolver {
       buffer.writeVarUint32(newId);
       ClassDef classDef;
       Serializer<?> serializer = classInfo.serializer;
-      Preconditions.checkArgument(serializer.getClass() != 
UnexistedClassSerializer.class);
+      Preconditions.checkArgument(serializer.getClass() != 
NonexistentClassSerializer.class);
       if (fury.getConfig().getCompatibleMode() == CompatibleMode.COMPATIBLE
           && (serializer instanceof Generated.GeneratedObjectSerializer
               // May already switched to MetaSharedSerializer when update 
class info cache.
@@ -1363,18 +1369,23 @@ public class ClassResolver {
   // TODO(chaokunyang) if ClassDef is consistent with class in this process,
   //  use existing serializer instead.
   private ClassInfo getMetaSharedClassInfo(ClassDef classDef, Class<?> clz) {
-    if (clz == UnexistedSkipClass.class) {
-      clz = UnexistedMetaSharedClass.class;
+    if (clz == NonexistentSkip.class) {
+      clz = NonexistentMetaShared.class;
     }
     Class<?> cls = clz;
     Short classId = extRegistry.registeredClassIdMap.get(cls);
     ClassInfo classInfo =
         new ClassInfo(this, cls, null, null, classId == null ? NO_CLASS_ID : 
classId);
-    if (cls == UnexistedMetaSharedClass.class) {
-      classInfo.serializer = new UnexistedClassSerializer(fury, classDef);
-      // ensure `UnexistedMetaSharedClass` registered to write fixed-length 
class def,
-      // so we can rewrite it in `UnexistedClassSerializer`.
-      Preconditions.checkNotNull(classId);
+    if 
(NonexistentClass.class.isAssignableFrom(TypeUtils.getComponentIfArray(cls))) {
+      if (cls == NonexistentMetaShared.class) {
+        classInfo.serializer = new NonexistentClassSerializer(fury, classDef);
+        // ensure `NonexistentMetaSharedClass` registered to write 
fixed-length class def,
+        // so we can rewrite it in `NonexistentClassSerializer`.
+        Preconditions.checkNotNull(classId);
+      } else {
+        classInfo.serializer =
+            NonexistentClassSerializers.getSerializer(fury, 
classDef.getClassName(), cls);
+      }
       return classInfo;
     }
     if (clz.isArray() || cls.isEnum()) {
@@ -1655,8 +1666,8 @@ public class ClassResolver {
     String rawPkg = packageName;
     String className = simpleClassNameBytes.decode(TYPE_NAME_DECODER);
     boolean isArray = className.startsWith(ClassInfo.ARRAY_PREFIX);
+    int dimension = 0;
     if (isArray) {
-      int dimension = 0;
       while (className.charAt(dimension) == ClassInfo.ARRAY_PREFIX.charAt(0)) {
         dimension++;
       }
@@ -1680,7 +1691,7 @@ public class ClassResolver {
     MetaStringBytes fullClassNameBytes =
         metaStringResolver.getOrCreateMetaStringBytes(
             PACKAGE_ENCODER.encode(entireClassName, 
MetaString.Encoding.UTF_8));
-    Class<?> cls = loadClass(entireClassName, isArray, isEnum);
+    Class<?> cls = loadClass(entireClassName, isEnum, dimension);
     ClassInfo classInfo =
         new ClassInfo(
             cls,
@@ -1691,12 +1702,8 @@ public class ClassResolver {
             null,
             null,
             NO_CLASS_ID);
-    if (cls == UnexistedSkipEnumClass.class) {
-      classInfo.serializer = new UnexistedEnumClassSerializer(fury);
-    } else if (cls == UnexistedArrayClass.class) {
-      classInfo.serializer = new UnexistedArrayClassSerializer(fury, 
entireClassName);
-    } else if (cls == UnexistedEnumArrayClass.class) {
-      classInfo.serializer = new UnexistedEnumArrayClassSerializer(fury, 
entireClassName);
+    if 
(NonexistentClass.class.isAssignableFrom(TypeUtils.getComponentIfArray(cls))) {
+      classInfo.serializer = NonexistentClassSerializers.getSerializer(fury, 
entireClassName, cls);
     } else {
       // don't create serializer here, if the class is an interface,
       // there won't be serializer since interface has no instance.
@@ -1738,10 +1745,10 @@ public class ClassResolver {
   }
 
   private Class<?> loadClass(String className) {
-    return loadClass(className, false, false);
+    return loadClass(className, false, 0);
   }
 
-  private Class<?> loadClass(String className, boolean isArray, boolean 
isEnum) {
+  private Class<?> loadClass(String className, boolean isEnum, int arrayDims) {
     extRegistry.classChecker.checkClass(this, className);
     try {
       return Class.forName(className, false, fury.getClassLoader());
@@ -1753,16 +1760,10 @@ public class ClassResolver {
             String.format(
                 "Class %s not found from classloaders [%s, %s]",
                 className, fury.getClassLoader(), 
Thread.currentThread().getContextClassLoader());
-        if (fury.getConfig().deserializeUnexistedClass()) {
+        if (fury.getConfig().deserializeNonexistentClass()) {
           LOG.warn(msg);
-          if (isArray) {
-            return isEnum ? UnexistedEnumArrayClass.class : 
UnexistedArrayClass.class;
-          } else if (isEnum) {
-            return UnexistedSkipEnumClass.class;
-          } else {
-            // FIXME create a subclass dynamically may be better?
-            return UnexistedSkipClass.class;
-          }
+          return NonexistentClass.getUnexistentClass(
+              className, isEnum, arrayDims, metaContextShareEnabled);
         }
         throw new IllegalStateException(msg, ex);
       }
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 1cd663d0..6fc5632a 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
@@ -22,9 +22,9 @@ package org.apache.fury.serializer;
 import java.lang.reflect.Array;
 import java.util.IdentityHashMap;
 import org.apache.fury.Fury;
+import org.apache.fury.config.CompatibleMode;
 import org.apache.fury.memory.MemoryBuffer;
 import org.apache.fury.memory.Platform;
-import org.apache.fury.reflect.ReflectionUtils;
 import org.apache.fury.resolver.ClassInfo;
 import org.apache.fury.resolver.ClassInfoHolder;
 import org.apache.fury.resolver.ClassResolver;
@@ -48,6 +48,7 @@ public class ArraySerializers {
 
     public ObjectArraySerializer(Fury fury, Class<T[]> cls) {
       super(fury, cls);
+      fury.getClassResolver().setSerializer(cls, this);
       Preconditions.checkArgument(cls.isArray());
       Class<?> t = cls;
       Class<?> innerType = cls;
@@ -61,7 +62,7 @@ public class ArraySerializers {
       }
       this.innerType = (Class<T>) innerType;
       Class<?> componentType = cls.getComponentType();
-      if (ReflectionUtils.isMonomorphic(componentType)) {
+      if (fury.getClassResolver().isMonomorphic(componentType)) {
         this.componentTypeSerializer = 
fury.getClassResolver().getSerializer(componentType);
       } else {
         // TODO add ClassInfo cache for non-final component type.
@@ -128,6 +129,9 @@ public class ArraySerializers {
       refResolver.reference(value);
       if (isFinal) {
         final Serializer componentTypeSerializer = 
this.componentTypeSerializer;
+        if (componentTypeSerializer == null) {
+          System.out.println("=======");
+        }
         for (int i = 0; i < numElements; i++) {
           Object elem;
           int nextReadRefId = refResolver.tryPreserveRefId(buffer);
@@ -706,15 +710,15 @@ public class ArraySerializers {
         new int[] {Platform.DOUBLE_ARRAY_OFFSET, 8, 
Type.FURY_PRIMITIVE_DOUBLE_ARRAY.getId()});
   }
 
-  public abstract static class AbstractedUnexistedArrayClassSerializer extends 
Serializer {
-    private final String className;
+  public abstract static class AbstractedNonexistentArrayClassSerializer 
extends Serializer {
+    protected final String className;
     private final int dims;
 
-    public AbstractedUnexistedArrayClassSerializer(
+    public AbstractedNonexistentArrayClassSerializer(
         Fury fury, String className, Class<?> stubClass) {
       super(fury, stubClass);
       this.className = className;
-      this.dims = TypeUtils.getArrayDimensions(className);
+      this.dims = TypeUtils.getArrayDimensions(stubClass);
     }
 
     @Override
@@ -741,6 +745,7 @@ public class ArraySerializers {
       RefResolver refResolver = fury.getRefResolver();
       Object[] value = new Object[numElements];
       refResolver.reference(value);
+
       if (isFinal) {
         for (int i = 0; i < numElements; i++) {
           Object elem;
@@ -816,33 +821,31 @@ public class ArraySerializers {
     }
   }
 
-  public static final class UnexistedEnumArrayClassSerializer
-      extends AbstractedUnexistedArrayClassSerializer {
-    public UnexistedEnumArrayClassSerializer(Fury fury, String className) {
-      super(fury, className, 
UnexistedClassSerializers.UnexistedEnumArrayClass.class);
-    }
-
-    @Override
-    protected Object readInnerElement(MemoryBuffer buffer) {
-      return buffer.readVarUint32Small7();
-    }
-  }
+  @SuppressWarnings("rawtypes")
+  public static final class NonexistentArrayClassSerializer
+      extends AbstractedNonexistentArrayClassSerializer {
+    private final Serializer componentSerializer;
 
-  public static final class UnexistedArrayClassSerializer
-      extends AbstractedUnexistedArrayClassSerializer {
-
-    private final 
CompatibleSerializer<UnexistedClassSerializers.UnexistedSkipClass>
-        componentSerializer;
-
-    public UnexistedArrayClassSerializer(Fury fury, String className) {
-      super(fury, className, 
UnexistedClassSerializers.UnexistedArrayClass.class);
-      // TODO(chaokunyang) meta share mode not supported currently.
-      componentSerializer =
-          new CompatibleSerializer<>(fury, 
UnexistedClassSerializers.UnexistedSkipClass.class);
+    public NonexistentArrayClassSerializer(Fury fury, String className, 
Class<?> cls) {
+      super(fury, className, cls);
+      if (TypeUtils.getArrayComponent(cls).isEnum()) {
+        componentSerializer = new 
NonexistentClassSerializers.NonexistentEnumClassSerializer(fury);
+      } else {
+        if (fury.getConfig().getCompatibleMode() == CompatibleMode.COMPATIBLE) 
{
+          componentSerializer =
+              new CompatibleSerializer<>(fury, 
NonexistentClass.NonexistentSkip.class);
+        } else {
+          componentSerializer = null;
+        }
+      }
     }
 
     @Override
     protected Object readInnerElement(MemoryBuffer buffer) {
+      if (componentSerializer == null) {
+        throw new IllegalStateException(
+            String.format("Class %s should serialize elements as non-morphic", 
className));
+      }
       return componentSerializer.read(buffer);
     }
   }
diff --git 
a/java/fury-core/src/main/java/org/apache/fury/serializer/NonexistentClass.java 
b/java/fury-core/src/main/java/org/apache/fury/serializer/NonexistentClass.java
new file mode 100644
index 00000000..1fb16ff3
--- /dev/null
+++ 
b/java/fury-core/src/main/java/org/apache/fury/serializer/NonexistentClass.java
@@ -0,0 +1,167 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.fury.serializer;
+
+import org.apache.fury.collection.LazyMap;
+import org.apache.fury.config.CompatibleMode;
+import org.apache.fury.config.Config;
+import org.apache.fury.meta.ClassDef;
+
+/**
+ * A class for hold deserialization data when the class doesn't exist in this 
process. When {@link
+ * CompatibleMode#COMPATIBLE} is enabled
+ *
+ * @see Config#shareMetaContext()
+ */
+public interface NonexistentClass {
+  // @formatter:off
+
+  enum NonexistentEnum implements NonexistentClass {
+    V0,
+    V1,
+    V2,
+    V3,
+    V4,
+    V5,
+    V6,
+    V7,
+    V8,
+    V9,
+    V10,
+    V11,
+    V12,
+    V13,
+    V14,
+    V15,
+    V16,
+    V17,
+    V18,
+    V19,
+    V20,
+    V21,
+    V22,
+    V23,
+    V24,
+    V25,
+    V26,
+    V27,
+    V28,
+    V29,
+    V30,
+    V31,
+    V32,
+    V33,
+    V34,
+    V35,
+    V36,
+    V37,
+    V38,
+    V39,
+    V40,
+    V41,
+    V42,
+    V43,
+    V44,
+    V45,
+    V46,
+    V47,
+    V48,
+    V49,
+    V50,
+    V51,
+    V52,
+    V53,
+    V54,
+    V55,
+    V56,
+    V57,
+    V58,
+    V59,
+    V60,
+    V61,
+    V62,
+    V63,
+    UNKNOWN
+  }
+
+  /** Ensure no fields here to avoid conflicts with peer class fields. */
+  class NonexistentSkip implements NonexistentClass {}
+
+  class NonexistentMetaShared extends LazyMap implements NonexistentClass {
+    final ClassDef classDef;
+
+    public NonexistentMetaShared(ClassDef classDef) {
+      this.classDef = classDef;
+    }
+  }
+
+  Class<?> NonexistentEnum1DArray = NonexistentEnum[].class;
+  Class<?> NonexistentEnum2DArray = NonexistentEnum[][].class;
+  Class<?> NonexistentEnum3DArray = NonexistentEnum[][][].class;
+  Class<?> NonexistentSkip1DArray = NonexistentSkip[].class;
+  Class<?> NonexistentSkip2DArray = NonexistentSkip[][].class;
+  Class<?> NonexistentSkip3DArray = NonexistentSkip[][][].class;
+  Class<?> Nonexistent1DArray = NonexistentMetaShared[].class;
+  Class<?> Nonexistent2DArray = NonexistentMetaShared[][].class;
+  Class<?> Nonexistent3DArray = NonexistentMetaShared[][][].class;
+
+  static Class<?> getUnexistentClass(boolean isEnum, int arrayDims, boolean 
shareMeta) {
+    return getUnexistentClass("Unknown", isEnum, arrayDims, shareMeta);
+  }
+
+  static Class<?> getUnexistentClass(
+      String className, boolean isEnum, int arrayDims, boolean shareMeta) {
+    if (arrayDims != 0) {
+      if (isEnum) {
+        switch (arrayDims) {
+          case 1:
+            return NonexistentEnum1DArray;
+          case 2:
+            return NonexistentEnum2DArray;
+          case 3:
+            return NonexistentEnum3DArray;
+          default:
+            throw new UnsupportedOperationException(
+                String.format(
+                    "Unsupported array dimensions %s for nonexistent class %s",
+                    arrayDims, className));
+        }
+      } else {
+        switch (arrayDims) {
+          case 1:
+            return shareMeta ? Nonexistent1DArray : NonexistentSkip1DArray;
+          case 2:
+            return shareMeta ? Nonexistent2DArray : NonexistentSkip2DArray;
+          case 3:
+            return shareMeta ? Nonexistent3DArray : NonexistentSkip3DArray;
+          default:
+            throw new UnsupportedOperationException(
+                String.format(
+                    "Unsupported array dimensions %s for nonexistent class %s",
+                    arrayDims, className));
+        }
+      }
+    } else if (isEnum) {
+      return NonexistentEnum.class;
+    } else {
+      return shareMeta ? NonexistentMetaShared.class : NonexistentSkip.class;
+    }
+  }
+}
diff --git 
a/java/fury-core/src/main/java/org/apache/fury/serializer/UnexistedClassSerializers.java
 
b/java/fury-core/src/main/java/org/apache/fury/serializer/NonexistentClassSerializers.java
similarity index 80%
rename from 
java/fury-core/src/main/java/org/apache/fury/serializer/UnexistedClassSerializers.java
rename to 
java/fury-core/src/main/java/org/apache/fury/serializer/NonexistentClassSerializers.java
index fe469982..ffe5f6d7 100644
--- 
a/java/fury-core/src/main/java/org/apache/fury/serializer/UnexistedClassSerializers.java
+++ 
b/java/fury-core/src/main/java/org/apache/fury/serializer/NonexistentClassSerializers.java
@@ -24,13 +24,10 @@ import java.util.Collection;
 import java.util.List;
 import org.apache.fury.Fury;
 import org.apache.fury.collection.IdentityObjectIntMap;
-import org.apache.fury.collection.LazyMap;
 import org.apache.fury.collection.LongMap;
 import org.apache.fury.collection.MapEntry;
 import org.apache.fury.collection.Tuple2;
 import org.apache.fury.collection.Tuple3;
-import org.apache.fury.config.CompatibleMode;
-import org.apache.fury.config.Config;
 import org.apache.fury.memory.MemoryBuffer;
 import org.apache.fury.meta.ClassDef;
 import org.apache.fury.resolver.ClassInfo;
@@ -38,37 +35,14 @@ import org.apache.fury.resolver.ClassInfoHolder;
 import org.apache.fury.resolver.ClassResolver;
 import org.apache.fury.resolver.MetaContext;
 import org.apache.fury.resolver.RefResolver;
+import org.apache.fury.serializer.NonexistentClass.NonexistentEnum;
 import org.apache.fury.type.Descriptor;
 import org.apache.fury.type.DescriptorGrouper;
 import org.apache.fury.type.Generics;
 import org.apache.fury.util.Preconditions;
 
 @SuppressWarnings({"rawtypes", "unchecked"})
-public final class UnexistedClassSerializers {
-  /**
-   * A class for hold deserialization data when the class doesn't exist in 
this process. When {@link
-   * CompatibleMode#COMPATIBLE} is enabled
-   *
-   * @see Config#shareMetaContext()
-   */
-  public interface UnexistedClass {}
-
-  /** Ensure no fields here to avoid conflicts with peer class fields. */
-  public static class UnexistedSkipClass implements UnexistedClass {}
-
-  public static class UnexistedArrayClass implements UnexistedClass {}
-
-  public static class UnexistedEnumArrayClass implements UnexistedClass {}
-
-  public static class UnexistedSkipEnumClass implements UnexistedClass {}
-
-  public static class UnexistedMetaSharedClass extends LazyMap implements 
UnexistedClass {
-    private final ClassDef classDef;
-
-    public UnexistedMetaSharedClass(ClassDef classDef) {
-      this.classDef = classDef;
-    }
-  }
+public final class NonexistentClassSerializers {
 
   private static final class ClassFieldsInfo {
     private final ObjectSerializer.FinalTypeField[] finalFields;
@@ -91,13 +65,13 @@ public final class UnexistedClassSerializers {
     }
   }
 
-  public static final class UnexistedClassSerializer extends Serializer {
+  public static final class NonexistentClassSerializer extends Serializer {
     private final ClassDef classDef;
     private final ClassInfoHolder classInfoHolder;
     private final LongMap<ClassFieldsInfo> fieldsInfoMap;
 
-    public UnexistedClassSerializer(Fury fury, ClassDef classDef) {
-      super(fury, UnexistedMetaSharedClass.class);
+    public NonexistentClassSerializer(Fury fury, ClassDef classDef) {
+      super(fury, NonexistentClass.NonexistentMetaShared.class);
       this.classDef = classDef;
       classInfoHolder = fury.getClassResolver().nilClassInfoHolder();
       fieldsInfoMap = new LongMap<>();
@@ -105,11 +79,11 @@ public final class UnexistedClassSerializers {
     }
 
     /**
-     * Multiple un existed class will correspond to this 
`UnexistedMetaSharedClass`. When querying
-     * classinfo by `class`, it may dispatch to same 
`UnexistedClassSerializer`, so we can't use
-     * `classDef` in this serializer, but use `classDef` in 
`UnexistedMetaSharedClass` instead.
+     * Multiple un existed class will correspond to this 
`NonexistentMetaSharedClass`. When querying
+     * classinfo by `class`, it may dispatch to same 
`NonexistentClassSerializer`, so we can't use
+     * `classDef` in this serializer, but use `classDef` in 
`NonexistentMetaSharedClass` instead.
      */
-    private void writeClassDef(MemoryBuffer buffer, UnexistedMetaSharedClass 
value) {
+    private void writeClassDef(MemoryBuffer buffer, 
NonexistentClass.NonexistentMetaShared value) {
       // Register NotFoundClass ahead to skip write meta shared info,
       // then revert written class id to write class info here,
       // since it's the only place to hold class def for not found class.
@@ -130,7 +104,7 @@ public final class UnexistedClassSerializers {
 
     @Override
     public void write(MemoryBuffer buffer, Object v) {
-      UnexistedMetaSharedClass value = (UnexistedMetaSharedClass) v;
+      NonexistentClass.NonexistentMetaShared value = 
(NonexistentClass.NonexistentMetaShared) v;
       writeClassDef(buffer, value);
       ClassDef classDef = value.classDef;
       ClassFieldsInfo fieldsInfo = getClassFieldsInfo(classDef);
@@ -179,10 +153,10 @@ public final class UnexistedClassSerializers {
     private ClassFieldsInfo getClassFieldsInfo(ClassDef classDef) {
       ClassFieldsInfo fieldsInfo = fieldsInfoMap.get(classDef.getId());
       if (fieldsInfo == null) {
-        // Use `UnexistedSkipClass` since it doesn't have any field.
+        // Use `NonexistentSkipClass` since it doesn't have any field.
         Collection<Descriptor> descriptors =
             MetaSharedSerializer.consolidateFields(
-                fury.getClassResolver(), UnexistedSkipClass.class, classDef);
+                fury.getClassResolver(), 
NonexistentClass.NonexistentSkip.class, classDef);
         DescriptorGrouper descriptorGrouper =
             DescriptorGrouper.createDescriptorGrouper(
                 fury.getClassResolver()::isMonomorphic,
@@ -208,7 +182,8 @@ public final class UnexistedClassSerializers {
 
     @Override
     public Object read(MemoryBuffer buffer) {
-      UnexistedMetaSharedClass obj = new UnexistedMetaSharedClass(classDef);
+      NonexistentClass.NonexistentMetaShared obj =
+          new NonexistentClass.NonexistentMetaShared(classDef);
       Fury fury = this.fury;
       RefResolver refResolver = fury.getRefResolver();
       ClassResolver classResolver = fury.getClassResolver();
@@ -250,15 +225,40 @@ public final class UnexistedClassSerializers {
     }
   }
 
-  public static final class UnexistedEnumClassSerializer extends Serializer {
+  public static final class NonexistentEnumClassSerializer extends Serializer {
+    private final NonexistentEnum[] enumConstants;
 
-    public UnexistedEnumClassSerializer(Fury fury) {
-      super(fury, UnexistedSkipEnumClass.class);
+    public NonexistentEnumClassSerializer(Fury fury) {
+      super(fury, NonexistentEnum.class);
+      enumConstants = NonexistentEnum.class.getEnumConstants();
     }
 
     @Override
     public Object read(MemoryBuffer buffer) {
-      return buffer.readVarUint32Small7();
+      int ordinal = buffer.readVarUint32Small7();
+      if (ordinal >= enumConstants.length) {
+        ordinal = enumConstants.length - 1;
+      }
+      return enumConstants[ordinal];
+    }
+  }
+
+  public static Serializer getSerializer(Fury fury, String className, Class<?> 
cls) {
+    if (cls.isArray()) {
+      return new ArraySerializers.NonexistentArrayClassSerializer(fury, 
className, cls);
+    } else {
+      if (cls.isEnum()) {
+        return new NonexistentEnumClassSerializer(fury);
+      } else {
+        if (fury.getConfig().shareMetaContext()) {
+          throw new IllegalStateException(
+              String.format(
+                  "Serializer of class %s should be set in 
ClassResolver#getMetaSharedClassInfo",
+                  className));
+        } else {
+          return new CompatibleSerializer(fury, cls);
+        }
+      }
     }
   }
 }
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 8c3f4b8f..ade3bbaf 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
@@ -57,7 +57,6 @@ import org.apache.fury.util.record.RecordUtils;
  *
  * @see Ignore
  */
-@SuppressWarnings("UnstableApiUsage")
 public class Descriptor {
   private static Cache<Class<?>, Tuple2<SortedMap<Field, Descriptor>, 
SortedMap<Field, Descriptor>>>
       descCache = 
CacheBuilder.newBuilder().weakKeys().softValues().concurrencyLevel(64).build();
@@ -203,8 +202,8 @@ public class Descriptor {
   public String toString() {
     final StringBuilder sb = new StringBuilder("Descriptor{");
     sb.append("typeName=").append(typeName);
-    sb.append("name=").append(name);
-    sb.append("modifier=").append(modifier);
+    sb.append(", name=").append(name);
+    sb.append(", modifier=").append(modifier);
     if (field != null) {
       sb.append(", 
field=").append(field.getDeclaringClass().getSimpleName()).append('.');
     }
diff --git a/java/fury-core/src/main/java/org/apache/fury/type/TypeUtils.java 
b/java/fury-core/src/main/java/org/apache/fury/type/TypeUtils.java
index 99ce7b4c..b634d194 100644
--- a/java/fury-core/src/main/java/org/apache/fury/type/TypeUtils.java
+++ b/java/fury-core/src/main/java/org/apache/fury/type/TypeUtils.java
@@ -371,6 +371,13 @@ public class TypeUtils {
     return dimension;
   }
 
+  public static Class<?> getComponentIfArray(Class<?> type) {
+    if (type.isArray()) {
+      return getArrayComponent(type);
+    }
+    return type;
+  }
+
   public static Class<?> getArrayComponent(Class<?> type) {
     return getArrayComponentInfo(type).f0;
   }
@@ -711,4 +718,11 @@ public class TypeUtils {
 
     return new ArrayList<>(allTypeArguments);
   }
+
+  public static boolean isEnumArray(Class<?> clz) {
+    if (!clz.isArray()) {
+      return false;
+    }
+    return getArrayComponent(clz).isEnum();
+  }
 }
diff --git 
a/java/fury-core/src/test/java/org/apache/fury/serializer/UnexistedClassSerializersTest.java
 
b/java/fury-core/src/test/java/org/apache/fury/serializer/NonexistentClassSerializersTest.java
similarity index 86%
rename from 
java/fury-core/src/test/java/org/apache/fury/serializer/UnexistedClassSerializersTest.java
rename to 
java/fury-core/src/test/java/org/apache/fury/serializer/NonexistentClassSerializersTest.java
index c2a1c62a..3f146e7a 100644
--- 
a/java/fury-core/src/test/java/org/apache/fury/serializer/UnexistedClassSerializersTest.java
+++ 
b/java/fury-core/src/test/java/org/apache/fury/serializer/NonexistentClassSerializersTest.java
@@ -40,7 +40,7 @@ import org.testng.Assert;
 import org.testng.annotations.DataProvider;
 import org.testng.annotations.Test;
 
-public class UnexistedClassSerializersTest extends FuryTestBase {
+public class NonexistentClassSerializersTest extends FuryTestBase {
   @DataProvider
   public static Object[][] config() {
     return Sets.cartesianProduct(
@@ -70,11 +70,11 @@ public class UnexistedClassSerializersTest extends 
FuryTestBase {
         .withLanguage(Language.JAVA)
         .withCompatibleMode(CompatibleMode.COMPATIBLE)
         .requireClassRegistration(false)
-        .withDeserializeUnexistedClass(true);
+        .withDeserializeNonexistentClass(true);
   }
 
   @Test(dataProvider = "config")
-  public void testSkipUnexisted(
+  public void testSkipNonexistent(
       boolean referenceTracking, boolean enableCodegen1, boolean 
enableCodegen2) {
     Fury fury =
         furyBuilder()
@@ -85,8 +85,8 @@ public class UnexistedClassSerializersTest extends 
FuryTestBase {
     ClassLoader classLoader = getClass().getClassLoader();
     for (Class<?> structClass :
         new Class<?>[] {
-          Struct.createNumberStructClass("TestSkipUnexistedClass1", 2),
-          Struct.createStructClass("TestSkipUnexistedClass1", 2)
+          Struct.createNumberStructClass("TestSkipNonexistentClass1", 2),
+          Struct.createStructClass("TestSkipNonexistentClass1", 2)
         }) {
       Object pojo = Struct.createPOJO(structClass);
       byte[] bytes = fury.serialize(pojo);
@@ -97,13 +97,13 @@ public class UnexistedClassSerializersTest extends 
FuryTestBase {
               .withClassLoader(classLoader)
               .build();
       Object o = fury2.deserialize(bytes);
-      assertEquals(o.getClass(), 
UnexistedClassSerializers.UnexistedSkipClass.class);
+      assertEquals(o.getClass(), NonexistentClass.NonexistentSkip.class);
     }
   }
 
   @Test
-  public void testSkipUnexistedEnum() {
-    Fury fury1 = furyBuilder().withDeserializeUnexistedClass(true).build();
+  public void testSkipNonexistentEnum() {
+    Fury fury1 = furyBuilder().withDeserializeNonexistentClass(true).build();
     String enumCode = ("enum TestEnum {" + " A, B" + "}");
 
     Class<?> cls = JaninoUtils.compileClass(getClass().getClassLoader(), "", 
"TestEnum", enumCode);
@@ -111,13 +111,13 @@ public class UnexistedClassSerializersTest extends 
FuryTestBase {
     assertEquals(c.toString(), "B");
     byte[] bytes = fury1.serialize(c);
     Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
-    Fury fury2 = furyBuilder().withDeserializeUnexistedClass(true).build();
+    Fury fury2 = furyBuilder().withDeserializeNonexistentClass(true).build();
     Object o = fury2.deserialize(bytes);
-    assertEquals(o, 1);
+    assertEquals(o, NonexistentClass.NonexistentEnum.V1);
   }
 
   @Test
-  public void testSkipUnexistedEnumAndArrayField() throws Exception {
+  public void testSkipNonexistentEnumAndArrayField() throws Exception {
     String enumStructCode1 =
         ("public class TestEnumStruct {\n"
             + "  public enum TestEnum {\n"
@@ -145,7 +145,7 @@ public class UnexistedClassSerializersTest extends 
FuryTestBase {
     ReflectionUtils.setObjectFieldValue(o, "f4", enumArray2);
     Fury fury1 =
         furyBuilder()
-            .withDeserializeUnexistedClass(true)
+            .withDeserializeNonexistentClass(true)
             .withClassLoader(cls1.getClassLoader())
             .build();
     byte[] bytes = fury1.serialize(o);
@@ -162,7 +162,7 @@ public class UnexistedClassSerializersTest extends 
FuryTestBase {
                 "TestEnumStruct",
                 ("public class TestEnumStruct {" + " public String f1;" + 
"}")));
     Fury fury2 =
-        
furyBuilder().withDeserializeUnexistedClass(true).withClassLoader(classLoader).build();
+        
furyBuilder().withDeserializeNonexistentClass(true).withClassLoader(classLoader).build();
     Object o1 = fury2.deserialize(bytes);
     Assert.assertEquals(ReflectionUtils.getObjectFieldValue(o1, "f1"), "str");
   }
@@ -173,7 +173,7 @@ public class UnexistedClassSerializersTest extends 
FuryTestBase {
   }
 
   @Test(dataProvider = "componentFinal")
-  public void testSkipUnexistedObjectArrayField(boolean componentFinal) throws 
Exception {
+  public void testSkipNonexistentObjectArrayField(boolean componentFinal) 
throws Exception {
     String enumStructCode1 =
         ("public class TestArrayStruct {\n"
             + "  public static "
@@ -204,7 +204,7 @@ public class UnexistedClassSerializersTest extends 
FuryTestBase {
     ReflectionUtils.setObjectFieldValue(o, "f4", arr2D);
     Fury fury1 =
         furyBuilder()
-            .withDeserializeUnexistedClass(true)
+            .withDeserializeNonexistentClass(true)
             .withClassLoader(cls1.getClassLoader())
             .build();
     byte[] bytes = fury1.serialize(o);
@@ -221,13 +221,13 @@ public class UnexistedClassSerializersTest extends 
FuryTestBase {
                 "TestArrayStruct",
                 ("public class TestArrayStruct {" + " public String f1;" + 
"}")));
     Fury fury2 =
-        
furyBuilder().withDeserializeUnexistedClass(true).withClassLoader(classLoader).build();
+        
furyBuilder().withDeserializeNonexistentClass(true).withClassLoader(classLoader).build();
     Object o1 = fury2.deserialize(bytes);
     Assert.assertEquals(ReflectionUtils.getObjectFieldValue(o1, "f1"), "str");
   }
 
   @Test(dataProvider = "metaShareConfig")
-  public void testDeserializeUnexistedNewFury(
+  public void testDeserializeNonexistentNewFury(
       boolean referenceTracking,
       boolean enableCodegen1,
       boolean enableCodegen2,
@@ -241,8 +241,8 @@ public class UnexistedClassSerializersTest extends 
FuryTestBase {
     ClassLoader classLoader = getClass().getClassLoader();
     for (Class<?> structClass :
         new Class<?>[] {
-          Struct.createNumberStructClass("TestSkipUnexistedClass2", 2),
-          Struct.createStructClass("TestSkipUnexistedClass2", 2)
+          Struct.createNumberStructClass("TestSkipNonexistentClass2", 2),
+          Struct.createStructClass("TestSkipNonexistentClass2", 2)
         }) {
       Object pojo = Struct.createPOJO(structClass);
       MetaContext context1 = new MetaContext();
@@ -258,7 +258,7 @@ public class UnexistedClassSerializersTest extends 
FuryTestBase {
       MetaContext context2 = new MetaContext();
       fury2.getSerializationContext().setMetaContext(context2);
       Object o2 = fury2.deserialize(bytes);
-      assertEquals(o2.getClass(), 
UnexistedClassSerializers.UnexistedMetaSharedClass.class);
+      assertEquals(o2.getClass(), 
NonexistentClass.NonexistentMetaShared.class);
       fury2.getSerializationContext().setMetaContext(context2);
       byte[] bytes2 = fury2.serialize(o2);
       Fury fury3 =
@@ -277,7 +277,7 @@ public class UnexistedClassSerializersTest extends 
FuryTestBase {
   }
 
   @Test(dataProvider = "metaShareConfig")
-  public void testDeserializeUnexisted(
+  public void testDeserializeNonexistent(
       boolean referenceTracking,
       boolean enableCodegen1,
       boolean enableCodegen2,
@@ -294,8 +294,8 @@ public class UnexistedClassSerializersTest extends 
FuryTestBase {
     ClassLoader classLoader = getClass().getClassLoader();
     for (Class<?> structClass :
         new Class<?>[] {
-          Struct.createNumberStructClass("TestSkipUnexistedClass3", 2),
-          Struct.createStructClass("TestSkipUnexistedClass3", 2)
+          Struct.createNumberStructClass("TestSkipNonexistentClass3", 2),
+          Struct.createStructClass("TestSkipNonexistentClass3", 2)
         }) {
       Fury fury2 =
           furyBuilder()
@@ -318,7 +318,7 @@ public class UnexistedClassSerializersTest extends 
FuryTestBase {
 
         fury2.getSerializationContext().setMetaContext(context2);
         Object o2 = fury2.deserialize(bytes);
-        assertEquals(o2.getClass(), 
UnexistedClassSerializers.UnexistedMetaSharedClass.class);
+        assertEquals(o2.getClass(), 
NonexistentClass.NonexistentMetaShared.class);
         fury2.getSerializationContext().setMetaContext(context2);
         byte[] bytes2 = fury2.serialize(o2);
 
@@ -332,12 +332,12 @@ public class UnexistedClassSerializersTest extends 
FuryTestBase {
 
   @Test
   public void testThrowExceptionIfClassNotExist() {
-    Fury fury = furyBuilder().withDeserializeUnexistedClass(false).build();
+    Fury fury = furyBuilder().withDeserializeNonexistentClass(false).build();
     ClassLoader classLoader = getClass().getClassLoader();
-    Class<?> structClass = 
Struct.createNumberStructClass("TestSkipUnexistedClass1", 2);
+    Class<?> structClass = 
Struct.createNumberStructClass("TestSkipNonexistentClass1", 2);
     Object pojo = Struct.createPOJO(structClass);
     Fury fury2 =
-        
furyBuilder().withDeserializeUnexistedClass(false).withClassLoader(classLoader).build();
+        
furyBuilder().withDeserializeNonexistentClass(false).withClassLoader(classLoader).build();
     byte[] bytes = fury.serialize(pojo);
     Assert.assertThrows(RuntimeException.class, () -> 
fury2.deserialize(bytes));
   }
diff --git 
a/java/fury-core/src/test/java/org/apache/fury/serializer/collection/ChildContainerSerializersTest.java
 
b/java/fury-core/src/test/java/org/apache/fury/serializer/collection/ChildContainerSerializersTest.java
index 43a8b88d..042ba0dc 100644
--- 
a/java/fury-core/src/test/java/org/apache/fury/serializer/collection/ChildContainerSerializersTest.java
+++ 
b/java/fury-core/src/test/java/org/apache/fury/serializer/collection/ChildContainerSerializersTest.java
@@ -179,7 +179,7 @@ public class ChildContainerSerializersTest extends 
FuryTestBase {
         Fury.builder()
             .withLanguage(Language.JAVA)
             .withCompatibleMode(CompatibleMode.COMPATIBLE)
-            .withDeserializeUnexistedClass(true)
+            .withDeserializeNonexistentClass(true)
             .withMetaContextShare(true)
             .requireClassRegistration(false)
             .requireClassRegistration(false)
diff --git a/java/fury-testsuite/src/test/java/org/test/Org.java 
b/java/fury-testsuite/src/test/java/org/test/Org.java
index 0e893e5b..21cb611e 100644
--- a/java/fury-testsuite/src/test/java/org/test/Org.java
+++ b/java/fury-testsuite/src/test/java/org/test/Org.java
@@ -53,7 +53,7 @@ public class Org implements Serializable {
             .withRefTracking(true)
             // Allow to deserialize objects unknown types,more flexible but 
less secure.
             .requireClassRegistration(false)
-            .withDeserializeUnexistedClass(true)
+            .withDeserializeNonexistentClass(true)
             .withCompatibleMode(CompatibleMode.COMPATIBLE)
             .withRefTracking(true)
             .build();


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

Reply via email to