This is an automated email from the ASF dual-hosted git repository.

pandalee pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/fury.git


The following commit(s) were added to refs/heads/main by this push:
     new 8df1c7f4 feat(java): support passed tracking ref meta when building 
serializers (#2113)
8df1c7f4 is described below

commit 8df1c7f4d1f8ddb413f2f0a8d6c62996550a2950
Author: Shawn Yang <[email protected]>
AuthorDate: Sun Mar 23 02:58:38 2025 +0800

    feat(java): support passed tracking ref meta when building serializers 
(#2113)
    
    ## What does this PR do?
    
    This pr supports passed tracking ref meta when building serializers. The
    meta can be pssed by Type Annotation in #2036 or by classdef encoded
    into binary.
    
    ## Related issues
    
    
    ## Does this PR introduce any user-facing change?
    
    <!--
    If any user-facing interface changes, please [open an
    issue](https://github.com/apache/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.
    -->
---
 .../src/main/java/org/apache/fury/Fury.java        |  18 ++++
 .../fury/builder/BaseObjectCodecBuilder.java       |  33 +++---
 .../main/java/org/apache/fury/meta/ClassDef.java   | 118 +++++++++++++--------
 .../java/org/apache/fury/meta/ClassDefDecoder.java |   3 +-
 .../java/org/apache/fury/meta/ClassDefEncoder.java |   1 +
 .../java/org/apache/fury/meta/TypeExtMeta.java     |  37 +++++++
 .../main/java/org/apache/fury/reflect/TypeRef.java |  22 ++++
 .../org/apache/fury/resolver/ClassResolver.java    |  11 +-
 .../fury/serializer/AbstractObjectSerializer.java  |  31 +++---
 .../apache/fury/serializer/ObjectSerializer.java   |  15 ++-
 .../java/org/apache/fury/type/GenericType.java     |  17 +--
 .../main/java/org/apache/fury/type/TypeUtils.java  |  10 ++
 .../fury-core/native-image.properties              |   1 +
 .../java/org/apache/fury/meta/ClassDefTest.java    |  12 +++
 .../apache/fury/resolver/ClassResolverTest.java    |   4 +-
 15 files changed, 239 insertions(+), 94 deletions(-)

diff --git a/java/fury-core/src/main/java/org/apache/fury/Fury.java 
b/java/fury-core/src/main/java/org/apache/fury/Fury.java
index 5d7429e2..4127b516 100644
--- a/java/fury-core/src/main/java/org/apache/fury/Fury.java
+++ b/java/fury-core/src/main/java/org/apache/fury/Fury.java
@@ -475,6 +475,15 @@ public final class Fury implements BaseFury {
     }
   }
 
+  public void writeNullable(MemoryBuffer buffer, Object obj, Serializer 
serializer) {
+    if (obj == null) {
+      buffer.writeByte(Fury.NULL_FLAG);
+    } else {
+      buffer.writeByte(Fury.NOT_NULL_VALUE_FLAG);
+      serializer.write(buffer, obj);
+    }
+  }
+
   /** Write object class and data without tracking ref. */
   public void writeNullable(MemoryBuffer buffer, Object obj, ClassInfoHolder 
classInfoHolder) {
     if (obj == null) {
@@ -948,6 +957,15 @@ public final class Fury implements BaseFury {
     }
   }
 
+  public Object readNullable(MemoryBuffer buffer, Serializer serializer) {
+    byte headFlag = buffer.readByte();
+    if (headFlag == Fury.NULL_FLAG) {
+      return null;
+    } else {
+      return serializer.read(buffer);
+    }
+  }
+
   public Object readNullable(MemoryBuffer buffer, ClassInfoHolder 
classInfoHolder) {
     byte headFlag = buffer.readByte();
     if (headFlag == Fury.NULL_FLAG) {
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 ae044628..7dec0a0a 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
@@ -224,8 +224,8 @@ public abstract class BaseObjectCodecBuilder extends 
CodecBuilder {
     return fury.getJITContext().asyncVisitFury(function);
   }
 
-  private boolean needWriteRef(Class<?> cls) {
-    return visitFury(fury -> fury.getClassResolver().needToWriteRef(cls));
+  private boolean needWriteRef(TypeRef<?> type) {
+    return visitFury(fury -> fury.getClassResolver().needToWriteRef(type));
   }
 
   @Override
@@ -354,7 +354,7 @@ public abstract class BaseObjectCodecBuilder extends 
CodecBuilder {
       boolean generateNewMethod) {
     // access rawType without jit lock to reduce lock competition.
     Class<?> rawType = getRawType(typeRef);
-    if (needWriteRef(rawType)) {
+    if (needWriteRef(typeRef)) {
       return new If(
           not(writeRefOrNull(buffer, inputObject)),
           serializeForNotNull(inputObject, buffer, typeRef, serializer, 
generateNewMethod));
@@ -791,7 +791,7 @@ public abstract class BaseObjectCodecBuilder extends 
CodecBuilder {
     walkPath.add(elementType.toString());
     ListExpression builder = new ListExpression();
     Class<?> elemClass = TypeUtils.getRawType(elementType);
-    boolean trackingRef = needWriteRef(elemClass);
+    boolean trackingRef = needWriteRef(elementType);
     Tuple2<Expression, Invoke> writeElementsHeader =
         writeElementsHeader(elemClass, trackingRef, serializer, buffer, 
collection);
     Expression flags = writeElementsHeader.f0;
@@ -1084,10 +1084,8 @@ public abstract class BaseObjectCodecBuilder extends 
CodecBuilder {
     boolean inline = keyMonomorphic && valueMonomorphic;
     Class<?> keyTypeRawType = keyType.getRawType();
     Class<?> valueTypeRawType = valueType.getRawType();
-    boolean trackingKeyRef =
-        visitFury(fury -> 
fury.getClassResolver().needToWriteRef(keyTypeRawType));
-    boolean trackingValueRef =
-        visitFury(fury -> 
fury.getClassResolver().needToWriteRef(valueTypeRawType));
+    boolean trackingKeyRef = visitFury(fury -> 
fury.getClassResolver().needToWriteRef(keyType));
+    boolean trackingValueRef = visitFury(fury -> 
fury.getClassResolver().needToWriteRef(valueType));
     Tuple2<Expression, Expression> mapKVSerializer =
         getMapKVSerializer(keyTypeRawType, valueTypeRawType);
     Expression keySerializer = mapKVSerializer.f0;
@@ -1179,10 +1177,8 @@ public abstract class BaseObjectCodecBuilder extends 
CodecBuilder {
 
     Expression chunkHeader;
     Expression keySerializer, valueSerializer;
-    boolean trackingKeyRef =
-        visitFury(fury -> 
fury.getClassResolver().needToWriteRef(keyTypeRawType));
-    boolean trackingValueRef =
-        visitFury(fury -> 
fury.getClassResolver().needToWriteRef(valueTypeRawType));
+    boolean trackingKeyRef = visitFury(fury -> 
fury.getClassResolver().needToWriteRef(keyType));
+    boolean trackingValueRef = visitFury(fury -> 
fury.getClassResolver().needToWriteRef(valueType));
     Expression keyWriteRef = Literal.ofBoolean(trackingKeyRef);
     Expression valueWriteRef = Literal.ofBoolean(trackingValueRef);
     boolean inline = keyMonomorphic && valueMonomorphic;
@@ -1392,8 +1388,7 @@ public abstract class BaseObjectCodecBuilder extends 
CodecBuilder {
       TypeRef<?> typeRef,
       Function<Expression, Expression> callback,
       InvokeHint invokeHint) {
-    Class<?> rawType = getRawType(typeRef);
-    if (visitFury(f -> f.getClassResolver().needToWriteRef(rawType))) {
+    if (visitFury(f -> f.getClassResolver().needToWriteRef(typeRef))) {
       return readRef(buffer, callback, () -> deserializeForNotNull(buffer, 
typeRef, invokeHint));
     } else {
       if (typeRef.isPrimitive()) {
@@ -1566,7 +1561,7 @@ public abstract class BaseObjectCodecBuilder extends 
CodecBuilder {
     Class<?> elemClass = TypeUtils.getRawType(elementType);
     walkPath.add(elementType.toString());
     boolean finalType = isMonomorphic(elemClass);
-    boolean trackingRef = visitFury(fury -> 
fury.getClassResolver().needToWriteRef(elemClass));
+    boolean trackingRef = visitFury(fury -> 
fury.getClassResolver().needToWriteRef(elementType));
     if (finalType) {
       if (trackingRef) {
         builder.add(readContainerElements(elementType, true, null, null, 
buffer, collection, size));
@@ -1770,8 +1765,8 @@ public abstract class BaseObjectCodecBuilder extends 
CodecBuilder {
     Class<?> valueCls = valueType.getRawType();
     boolean keyMonomorphic = isMonomorphic(keyCls);
     boolean valueMonomorphic = isMonomorphic(valueCls);
-    boolean refKey = needWriteRef(keyCls);
-    boolean refValue = needWriteRef(valueCls);
+    boolean refKey = needWriteRef(keyType);
+    boolean refValue = needWriteRef(valueType);
     boolean inline = keyMonomorphic && valueMonomorphic && (!refKey || 
!refValue);
     Tuple2<Expression, Expression> mapKVSerializer = 
getMapKVSerializer(keyCls, valueCls);
     Expression keySerializer = mapKVSerializer.f0;
@@ -1845,8 +1840,8 @@ public abstract class BaseObjectCodecBuilder extends 
CodecBuilder {
     boolean valueMonomorphic = isMonomorphic(valueType);
     Class<?> keyTypeRawType = keyType.getRawType();
     Class<?> valueTypeRawType = valueType.getRawType();
-    boolean trackingKeyRef = needWriteRef(keyTypeRawType);
-    boolean trackingValueRef = needWriteRef(valueTypeRawType);
+    boolean trackingKeyRef = needWriteRef(keyType);
+    boolean trackingValueRef = needWriteRef(valueType);
     boolean inline = keyMonomorphic && valueMonomorphic && (!trackingKeyRef || 
!trackingValueRef);
     ListExpression expressions = new ListExpression(buffer);
     Expression trackKeyRef = neq(bitand(chunkHeader, ofInt(TRACKING_KEY_REF)), 
ofInt(0));
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 695d552b..6f213436 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
@@ -348,16 +348,22 @@ public class ClassDef implements Serializable {
   }
 
   public abstract static class FieldType implements Serializable {
-    public FieldType(boolean isMonomorphic) {
+    public FieldType(boolean isMonomorphic, boolean trackingRef) {
       this.isMonomorphic = isMonomorphic;
+      this.trackingRef = trackingRef;
     }
 
     protected final boolean isMonomorphic;
+    protected final boolean trackingRef;
 
     public boolean isMonomorphic() {
       return isMonomorphic;
     }
 
+    public boolean trackingRef() {
+      return trackingRef;
+    }
+
     /**
      * Convert a serializable field type to type token. If field type is a 
generic type with
      * generics, the generics will be built up recursively. The final leaf 
object type will be built
@@ -369,48 +375,46 @@ public class ClassDef implements Serializable {
 
     @Override
     public boolean equals(Object o) {
-      if (this == o) {
-        return true;
-      }
       if (o == null || getClass() != o.getClass()) {
         return false;
       }
       FieldType fieldType = (FieldType) o;
-      return isMonomorphic == fieldType.isMonomorphic;
+      return isMonomorphic == fieldType.isMonomorphic && trackingRef == 
fieldType.trackingRef;
     }
 
     @Override
     public int hashCode() {
-      return Objects.hash(isMonomorphic);
+      return Objects.hash(isMonomorphic, trackingRef);
     }
 
     /** Write field type info. */
-    public void write(MemoryBuffer buffer, boolean writeMonomorphicFlag) {
-      byte header = (byte) (isMonomorphic ? 1 : 0);
+    public void write(MemoryBuffer buffer, boolean writeHeader) {
+      byte header = (byte) ((isMonomorphic ? 1 : 0) << 1);
+      // header of nested generic fields in collection/map will be written 
independently
+      header |= (byte) (trackingRef ? 1 : 0);
       if (this instanceof ClassDef.RegisteredFieldType) {
         short classId = ((ClassDef.RegisteredFieldType) this).getClassId();
-        buffer.writeVarUint32Small7(
-            writeMonomorphicFlag ? ((5 + classId) << 1) | header : 5 + 
classId);
+        buffer.writeVarUint32Small7(writeHeader ? ((5 + classId) << 2) | 
header : 5 + classId);
       } else if (this instanceof ClassDef.EnumFieldType) {
-        buffer.writeVarUint32Small7(writeMonomorphicFlag ? ((4) << 1) | header 
: 4);
+        buffer.writeVarUint32Small7(writeHeader ? ((4) << 2) | header : 4);
       } else if (this instanceof ClassDef.ArrayFieldType) {
         ClassDef.ArrayFieldType arrayFieldType = (ClassDef.ArrayFieldType) 
this;
-        buffer.writeVarUint32Small7(writeMonomorphicFlag ? ((3) << 1) | header 
: 3);
+        buffer.writeVarUint32Small7(writeHeader ? ((3) << 2) | header : 3);
         buffer.writeVarUint32Small7(arrayFieldType.getDimensions());
         (arrayFieldType).getComponentType().write(buffer);
       } else if (this instanceof ClassDef.CollectionFieldType) {
-        buffer.writeVarUint32Small7(writeMonomorphicFlag ? ((2) << 1) | header 
: 2);
+        buffer.writeVarUint32Small7(writeHeader ? ((2) << 2) | 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);
+        buffer.writeVarUint32Small7(writeHeader ? ((1) << 2) | 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 ClassDef.ObjectFieldType);
-        buffer.writeVarUint32Small7(writeMonomorphicFlag ? header : 0);
+        buffer.writeVarUint32Small7(writeHeader ? header : 0);
       }
     }
 
@@ -420,25 +424,27 @@ public class ClassDef implements Serializable {
 
     public static FieldType read(MemoryBuffer buffer) {
       int header = buffer.readVarUint32Small7();
-      boolean isMonomorphic = (header & 0b1) != 0;
-      return read(buffer, isMonomorphic, header >>> 1);
+      boolean isMonomorphic = (header & 0b10) != 0;
+      boolean trackingRef = (header & 0b1) != 0;
+      return read(buffer, isMonomorphic, trackingRef, header >>> 2);
     }
 
     /** Read field type info. */
-    public static FieldType read(MemoryBuffer buffer, boolean isFinal, int 
typeId) {
+    public static FieldType read(
+        MemoryBuffer buffer, boolean isFinal, boolean trackingRef, int typeId) 
{
       if (typeId == 0) {
-        return new ObjectFieldType(isFinal);
+        return new ObjectFieldType(isFinal, trackingRef);
       } else if (typeId == 1) {
-        return new MapFieldType(isFinal, read(buffer), read(buffer));
+        return new MapFieldType(isFinal, trackingRef, read(buffer), 
read(buffer));
       } else if (typeId == 2) {
-        return new CollectionFieldType(isFinal, read(buffer));
+        return new CollectionFieldType(isFinal, trackingRef, read(buffer));
       } else if (typeId == 3) {
         int dims = buffer.readVarUint32Small7();
-        return new ArrayFieldType(isFinal, read(buffer), dims);
+        return new ArrayFieldType(isFinal, trackingRef, read(buffer), dims);
       } else if (typeId == 4) {
         return EnumFieldType.getInstance();
       } else {
-        return new RegisteredFieldType(isFinal, (short) (typeId - 5));
+        return new RegisteredFieldType(isFinal, trackingRef, (short) (typeId - 
5));
       }
     }
   }
@@ -447,8 +453,8 @@ public class ClassDef implements Serializable {
   public static class RegisteredFieldType extends FieldType {
     private final short classId;
 
-    public RegisteredFieldType(boolean isFinal, short classId) {
-      super(isFinal);
+    public RegisteredFieldType(boolean isFinal, boolean trackingRef, short 
classId) {
+      super(isFinal, trackingRef);
       this.classId = classId;
     }
 
@@ -458,7 +464,7 @@ public class ClassDef implements Serializable {
 
     @Override
     public TypeRef<?> toTypeToken(ClassResolver classResolver) {
-      return TypeRef.of(classResolver.getRegisteredClass(classId));
+      return TypeRef.of(classResolver.getRegisteredClass(classId), new 
TypeExtMeta(trackingRef));
     }
 
     @Override
@@ -486,6 +492,8 @@ public class ClassDef implements Serializable {
       return "RegisteredFieldType{"
           + "isMonomorphic="
           + isMonomorphic()
+          + ", trackingRef="
+          + trackingRef()
           + ", classId="
           + classId
           + '}';
@@ -503,8 +511,8 @@ public class ClassDef implements Serializable {
   public static class CollectionFieldType extends FieldType {
     private final FieldType elementType;
 
-    public CollectionFieldType(boolean isFinal, FieldType elementType) {
-      super(isFinal);
+    public CollectionFieldType(boolean isFinal, boolean trackingRef, FieldType 
elementType) {
+      super(isFinal, trackingRef);
       this.elementType = elementType;
     }
 
@@ -514,7 +522,8 @@ public class ClassDef implements Serializable {
 
     @Override
     public TypeRef<?> toTypeToken(ClassResolver classResolver) {
-      return collectionOf(elementType.toTypeToken(classResolver));
+      // TODO support preserve element TypeExtMeta
+      return collectionOf(elementType.toTypeToken(classResolver), new 
TypeExtMeta(trackingRef));
     }
 
     @Override
@@ -544,6 +553,8 @@ public class ClassDef implements Serializable {
           + elementType
           + ", isFinal="
           + isMonomorphic()
+          + ", trackingRef="
+          + trackingRef()
           + '}';
     }
   }
@@ -560,8 +571,9 @@ public class ClassDef implements Serializable {
     private final FieldType keyType;
     private final FieldType valueType;
 
-    public MapFieldType(boolean isFinal, FieldType keyType, FieldType 
valueType) {
-      super(isFinal);
+    public MapFieldType(
+        boolean isFinal, boolean trackingRef, FieldType keyType, FieldType 
valueType) {
+      super(isFinal, trackingRef);
       this.keyType = keyType;
       this.valueType = valueType;
     }
@@ -576,7 +588,11 @@ public class ClassDef implements Serializable {
 
     @Override
     public TypeRef<?> toTypeToken(ClassResolver classResolver) {
-      return mapOf(keyType.toTypeToken(classResolver), 
valueType.toTypeToken(classResolver));
+      // TODO support preserve element TypeExtMeta, it will be lost when 
building other TypeRef
+      return mapOf(
+          keyType.toTypeToken(classResolver),
+          valueType.toTypeToken(classResolver),
+          new TypeExtMeta(trackingRef));
     }
 
     @Override
@@ -608,6 +624,8 @@ public class ClassDef implements Serializable {
           + valueType
           + ", isFinal="
           + isMonomorphic()
+          + ", trackingRef="
+          + trackingRef()
           + '}';
     }
   }
@@ -616,7 +634,7 @@ public class ClassDef implements Serializable {
     private static final EnumFieldType INSTANCE = new EnumFieldType();
 
     private EnumFieldType() {
-      super(true);
+      super(true, false);
     }
 
     @Override
@@ -633,8 +651,9 @@ public class ClassDef implements Serializable {
     private final FieldType componentType;
     private final int dimensions;
 
-    public ArrayFieldType(boolean isMonomorphic, FieldType componentType, int 
dimensions) {
-      super(isMonomorphic);
+    public ArrayFieldType(
+        boolean isMonomorphic, boolean trackingRef, FieldType componentType, 
int dimensions) {
+      super(isMonomorphic, trackingRef);
       this.componentType = componentType;
       this.dimensions = dimensions;
     }
@@ -648,9 +667,12 @@ public class ClassDef implements Serializable {
             // We embed `isMonomorphic` flag in ObjectArraySerializer, so this 
flag can be ignored
             // here.
             NonexistentClass.getNonexistentClass(
-                componentType instanceof EnumFieldType, dimensions, true));
+                componentType instanceof EnumFieldType, dimensions, true),
+            new TypeExtMeta(trackingRef));
       } else {
-        return TypeRef.of(Array.newInstance(componentRawType, new 
int[dimensions]).getClass());
+        return TypeRef.of(
+            Array.newInstance(componentRawType, new 
int[dimensions]).getClass(),
+            new TypeExtMeta(trackingRef));
       }
     }
 
@@ -691,6 +713,8 @@ public class ClassDef implements Serializable {
           + dimensions
           + ", isMonomorphic="
           + isMonomorphic
+          + ", trackingRef="
+          + trackingRef
           + '}';
     }
   }
@@ -698,13 +722,15 @@ public class ClassDef implements Serializable {
   /** Class for field type which isn't registered and not collection/map type 
too. */
   public static class ObjectFieldType extends FieldType {
 
-    public ObjectFieldType(boolean isFinal) {
-      super(isFinal);
+    public ObjectFieldType(boolean isFinal, boolean trackingRef) {
+      super(isFinal, trackingRef);
     }
 
     @Override
     public TypeRef<?> toTypeToken(ClassResolver classResolver) {
-      return isMonomorphic() ? TypeRef.of(FinalObjectTypeStub.class) : 
TypeRef.of(Object.class);
+      return isMonomorphic()
+          ? TypeRef.of(FinalObjectTypeStub.class, new TypeExtMeta(trackingRef))
+          : TypeRef.of(Object.class, new TypeExtMeta(trackingRef));
     }
 
     @Override
@@ -730,9 +756,11 @@ public class ClassDef implements Serializable {
     Preconditions.checkNotNull(genericType);
     Class<?> rawType = genericType.getCls();
     boolean isMonomorphic = genericType.isMonomorphic();
+    boolean trackingRef = genericType.trackingRef(classResolver);
     if (COLLECTION_TYPE.isSupertypeOf(genericType.getTypeRef())) {
       return new CollectionFieldType(
           isMonomorphic,
+          trackingRef,
           buildFieldType(
               classResolver,
               genericType.getTypeParameter0() == null
@@ -741,6 +769,7 @@ public class ClassDef implements Serializable {
     } else if (MAP_TYPE.isSupertypeOf(genericType.getTypeRef())) {
       return new MapFieldType(
           isMonomorphic,
+          trackingRef,
           buildFieldType(
               classResolver,
               genericType.getTypeParameter0() == null
@@ -754,7 +783,7 @@ public class ClassDef implements Serializable {
     } else {
       Short classId = classResolver.getRegisteredClassId(rawType);
       if (classId != null && classId != ClassResolver.NO_CLASS_ID) {
-        return new RegisteredFieldType(isMonomorphic, classId);
+        return new RegisteredFieldType(isMonomorphic, trackingRef, classId);
       } else {
         if (rawType.isEnum()) {
           return EnumFieldType.getInstance();
@@ -762,9 +791,12 @@ public class ClassDef implements Serializable {
         if (rawType.isArray()) {
           Tuple2<Class<?>, Integer> info = 
TypeUtils.getArrayComponentInfo(rawType);
           return new ArrayFieldType(
-              isMonomorphic, buildFieldType(classResolver, 
GenericType.build(info.f0)), info.f1);
+              isMonomorphic,
+              trackingRef,
+              buildFieldType(classResolver, GenericType.build(info.f0)),
+              info.f1);
         }
-        return new ObjectFieldType(isMonomorphic);
+        return new ObjectFieldType(isMonomorphic, trackingRef);
       }
     }
   }
diff --git 
a/java/fury-core/src/main/java/org/apache/fury/meta/ClassDefDecoder.java 
b/java/fury-core/src/main/java/org/apache/fury/meta/ClassDefDecoder.java
index c5d90d94..5567e2a6 100644
--- a/java/fury-core/src/main/java/org/apache/fury/meta/ClassDefDecoder.java
+++ b/java/fury-core/src/main/java/org/apache/fury/meta/ClassDefDecoder.java
@@ -118,8 +118,9 @@ class ClassDefDecoder {
       Encoding encoding = fieldNameEncodings[encodingFlags];
       String fieldName = 
Encoders.FIELD_NAME_DECODER.decode(buffer.readBytes(size), encoding);
       boolean isMonomorphic = (header & 0b100) != 0;
+      boolean trackingRef = (header & 0b001) != 0;
       int typeId = buffer.readVarUint32Small14();
-      FieldType fieldType = FieldType.read(buffer, isMonomorphic, typeId);
+      FieldType fieldType = FieldType.read(buffer, isMonomorphic, trackingRef, 
typeId);
       fieldInfos.add(new ClassDef.FieldInfo(className, fieldName, fieldType));
     }
     return fieldInfos;
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 ce9d936a..3ca0383a 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
@@ -250,6 +250,7 @@ class ClassDefEncoder {
       // `3 bits size + 2 bits field name encoding + polymorphism flag + 
nullability flag + ref
       // tracking flag`
       int header = ((fieldType.isMonomorphic() ? 1 : 0) << 2);
+      header |= ((fieldType.trackingRef() ? 1 : 0));
       // Encoding `UTF8/ALL_TO_LOWER_SPECIAL/LOWER_UPPER_DIGIT_SPECIAL/TAG_ID`
       MetaString metaString = 
Encoders.encodeFieldName(fieldInfo.getFieldName());
       int encodingFlags = 
fieldNameEncodingsList.indexOf(metaString.getEncoding());
diff --git a/java/fury-core/src/main/java/org/apache/fury/meta/TypeExtMeta.java 
b/java/fury-core/src/main/java/org/apache/fury/meta/TypeExtMeta.java
new file mode 100644
index 00000000..37df8273
--- /dev/null
+++ b/java/fury-core/src/main/java/org/apache/fury/meta/TypeExtMeta.java
@@ -0,0 +1,37 @@
+/*
+ * 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.meta;
+
+public class TypeExtMeta {
+  private final boolean trackingRef;
+
+  TypeExtMeta(boolean trackingRef) {
+    this.trackingRef = trackingRef;
+  }
+
+  public boolean trackingRef() {
+    return trackingRef;
+  }
+
+  @Override
+  public String toString() {
+    return "TypeExtMeta{" + "trackingRef=" + trackingRef + '}';
+  }
+}
diff --git a/java/fury-core/src/main/java/org/apache/fury/reflect/TypeRef.java 
b/java/fury-core/src/main/java/org/apache/fury/reflect/TypeRef.java
index 16869879..c521096d 100644
--- a/java/fury-core/src/main/java/org/apache/fury/reflect/TypeRef.java
+++ b/java/fury-core/src/main/java/org/apache/fury/reflect/TypeRef.java
@@ -40,6 +40,7 @@ import org.apache.fury.type.TypeUtils;
 // 
https://github.com/google/guava/blob/9f6a3840/guava/src/com/google/common/reflect/TypeToken.java
 public class TypeRef<T> {
   private final Type type;
+  private final Object extInfo;
   private transient Class<? super T> rawType;
   private transient Map<TypeVariableKey, Type> typeMappings;
 
@@ -57,14 +58,27 @@ public class TypeRef<T> {
    */
   protected TypeRef() {
     this.type = capture();
+    this.extInfo = null;
+  }
+
+  protected TypeRef(Object extInfo) {
+    this.type = capture();
+    this.extInfo = extInfo;
   }
 
   private TypeRef(Class<T> declaringClass) {
     this.type = declaringClass;
+    this.extInfo = null;
+  }
+
+  private TypeRef(Class<T> declaringClass, Object extInfo) {
+    this.type = declaringClass;
+    this.extInfo = extInfo;
   }
 
   private TypeRef(Type type) {
     this.type = type;
+    this.extInfo = null;
   }
 
   /** Returns an instance of type token that wraps {@code type}. */
@@ -72,6 +86,10 @@ public class TypeRef<T> {
     return new TypeRef<>(clazz);
   }
 
+  public static <T> TypeRef<T> of(Class<T> clazz, Object extInfo) {
+    return new TypeRef<>(clazz, extInfo);
+  }
+
   /** Returns an instance of type token that wraps {@code type}. */
   public static <T> TypeRef<T> of(Type type) {
     return new TypeRef<>(type);
@@ -140,6 +158,10 @@ public class TypeRef<T> {
             });
   }
 
+  public Object getExtInfo() {
+    return extInfo;
+  }
+
   /** Returns true if this type is one of the primitive types (including 
{@code void}). */
   public boolean isPrimitive() {
     return type instanceof Class && ((Class<?>) type).isPrimitive();
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 995994f4..28ed27d6 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
@@ -109,6 +109,7 @@ import org.apache.fury.meta.ClassDef;
 import org.apache.fury.meta.ClassSpec;
 import org.apache.fury.meta.Encoders;
 import org.apache.fury.meta.MetaString;
+import org.apache.fury.meta.TypeExtMeta;
 import org.apache.fury.reflect.ReflectionUtils;
 import org.apache.fury.reflect.TypeRef;
 import org.apache.fury.serializer.ArraySerializers;
@@ -1120,7 +1121,13 @@ public class ClassResolver {
    * Whether to track reference for this type. If false, reference tracing of 
subclasses may be
    * ignored too.
    */
-  public boolean needToWriteRef(Class<?> cls) {
+  public boolean needToWriteRef(TypeRef<?> typeRef) {
+    Object extInfo = typeRef.getExtInfo();
+    if (extInfo instanceof TypeExtMeta) {
+      TypeExtMeta meta = (TypeExtMeta) extInfo;
+      return meta.trackingRef();
+    }
+    Class<?> cls = typeRef.getRawType();
     if (fury.trackingRef()) {
       ClassInfo classInfo = getClassInfo(cls, false);
       if (classInfo == null || classInfo.serializer == null) {
@@ -1900,7 +1907,7 @@ public class ClassResolver {
 
   public GenericType buildGenericType(TypeRef<?> typeRef) {
     return GenericType.build(
-        typeRef.getType(),
+        typeRef,
         t -> {
           if (t.getClass() == Class.class) {
             return isMonomorphic((Class<?>) t);
diff --git 
a/java/fury-core/src/main/java/org/apache/fury/serializer/AbstractObjectSerializer.java
 
b/java/fury-core/src/main/java/org/apache/fury/serializer/AbstractObjectSerializer.java
index a6e3af86..f7540407 100644
--- 
a/java/fury-core/src/main/java/org/apache/fury/serializer/AbstractObjectSerializer.java
+++ 
b/java/fury-core/src/main/java/org/apache/fury/serializer/AbstractObjectSerializer.java
@@ -371,7 +371,7 @@ public abstract class AbstractObjectSerializer<T> extends 
Serializer<T> {
     for (Descriptor descriptor : grouper.getOtherDescriptors()) {
       GenericTypeField genericTypeField =
           new GenericTypeField(
-              descriptor.getRawType(),
+              descriptor.getTypeRef(),
               descriptor.getDeclaringClass() + "." + descriptor.getName(),
               descriptor.getField() != null
                   ? FieldAccessor.createAccessor(descriptor.getField())
@@ -394,7 +394,7 @@ public abstract class AbstractObjectSerializer<T> extends 
Serializer<T> {
 
   private static FinalTypeField buildFinalTypeField(Fury fury, Descriptor d) {
     return new FinalTypeField(
-        d.getRawType(),
+        d.getTypeRef(),
         d.getDeclaringClass() + "." + d.getName(),
         // `d.getField()` will be null when peer class doesn't have this field.
         d.getField() != null ? FieldAccessor.createAccessor(d.getField()) : 
null,
@@ -410,12 +410,14 @@ public abstract class AbstractObjectSerializer<T> extends 
Serializer<T> {
   }
 
   public static class InternalFieldInfo {
+    private final TypeRef<?> typeRef;
     protected final short classId;
     protected final String qualifiedFieldName;
     protected final FieldAccessor fieldAccessor;
 
     private InternalFieldInfo(
-        short classId, String qualifiedFieldName, FieldAccessor fieldAccessor) 
{
+        TypeRef<?> typeRef, short classId, String qualifiedFieldName, 
FieldAccessor fieldAccessor) {
+      this.typeRef = typeRef;
       this.classId = classId;
       this.qualifiedFieldName = qualifiedFieldName;
       this.fieldAccessor = fieldAccessor;
@@ -436,17 +438,19 @@ public abstract class AbstractObjectSerializer<T> extends 
Serializer<T> {
 
   static final class FinalTypeField extends InternalFieldInfo {
     final ClassInfo classInfo;
+    final boolean trackingRef;
 
-    private FinalTypeField(Class<?> type, String fieldName, FieldAccessor 
accessor, Fury fury) {
-      super(getRegisteredClassId(fury, type), fieldName, accessor);
+    private FinalTypeField(TypeRef<?> type, String fieldName, FieldAccessor 
accessor, Fury fury) {
+      super(type, getRegisteredClassId(fury, type.getRawType()), fieldName, 
accessor);
       // invoke `copy` to avoid ObjectSerializer construct clear serializer by 
`clearSerializer`.
-      if (type == FinalObjectTypeStub.class) {
+      if (type.getRawType() == FinalObjectTypeStub.class) {
         // `FinalObjectTypeStub` has no fields, using its `classInfo`
         // will make deserialization failed.
         classInfo = null;
       } else {
-        classInfo = fury.getClassResolver().getClassInfo(type);
+        classInfo = fury.getClassResolver().getClassInfo(type.getRawType());
       }
+      trackingRef = fury.getClassResolver().needToWriteRef(type);
     }
   }
 
@@ -455,22 +459,13 @@ public abstract class AbstractObjectSerializer<T> extends 
Serializer<T> {
     final ClassInfoHolder classInfoHolder;
     final boolean trackingRef;
 
-    private GenericTypeField(
-        Class<?> cls, String qualifiedFieldName, FieldAccessor accessor, Fury 
fury) {
-      super(getRegisteredClassId(fury, cls), qualifiedFieldName, accessor);
-      // TODO support generics <T> in Pojo<T>, see 
ComplexObjectSerializer.getGenericTypes
-      genericType = fury.getClassResolver().buildGenericType(cls);
-      classInfoHolder = fury.getClassResolver().nilClassInfoHolder();
-      trackingRef = fury.getClassResolver().needToWriteRef(cls);
-    }
-
     private GenericTypeField(
         TypeRef<?> typeRef, String qualifiedFieldName, FieldAccessor accessor, 
Fury fury) {
-      super(getRegisteredClassId(fury, getRawType(typeRef)), 
qualifiedFieldName, accessor);
+      super(typeRef, getRegisteredClassId(fury, getRawType(typeRef)), 
qualifiedFieldName, accessor);
       // TODO support generics <T> in Pojo<T>, see 
ComplexObjectSerializer.getGenericTypes
       genericType = fury.getClassResolver().buildGenericType(typeRef);
       classInfoHolder = fury.getClassResolver().nilClassInfoHolder();
-      trackingRef = 
fury.getClassResolver().needToWriteRef(getRawType(typeRef));
+      trackingRef = fury.getClassResolver().needToWriteRef(typeRef);
     }
 
     @Override
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 c6faf3e7..320d8afc 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
@@ -163,11 +163,15 @@ public final class ObjectSerializer<T> extends 
AbstractObjectSerializer<T> {
         if (writeBasicObjectFieldValueFailed(fury, buffer, fieldValue, 
classId)) {
           Serializer<Object> serializer = fieldInfo.classInfo.getSerializer();
           if (!metaShareEnabled || isFinal[i]) {
-            // whether tracking ref is recorded in `fieldInfo.serializer`, so 
it's still
-            // consistent with jit serializer.
-            fury.writeRef(buffer, fieldValue, serializer);
+            if (!fieldInfo.trackingRef) {
+              fury.writeNullable(buffer, fieldValue, serializer);
+            } else {
+              // whether tracking ref is recorded in `fieldInfo.serializer`, 
so it's still
+              // consistent with jit serializer.
+              fury.writeRef(buffer, fieldValue, serializer);
+            }
           } else {
-            if (serializer.needToWriteRef()) {
+            if (fieldInfo.trackingRef && serializer.needToWriteRef()) {
               if (!refResolver.writeRefOrNull(buffer, fieldValue)) {
                 classResolver.writeClass(buffer, fieldInfo.classInfo);
                 // No generics for field, no need to update `depth`.
@@ -336,6 +340,9 @@ public final class ObjectSerializer<T> extends 
AbstractObjectSerializer<T> {
     Serializer<Object> serializer = fieldInfo.classInfo.getSerializer();
     Object fieldValue;
     if (isFinal) {
+      if (!fieldInfo.trackingRef) {
+        return fury.readNullable(buffer, serializer);
+      }
       // whether tracking ref is recorded in `fieldInfo.serializer`, so it's 
still
       // consistent with jit serializer.
       fieldValue = fury.readRef(buffer, serializer);
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 47f441f1..3e9cea6c 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
@@ -36,6 +36,7 @@ import org.apache.fury.serializer.Serializer;
 
 /** GenericType for building java generics as a tree and binding with fury 
serializers. */
 // TODO(chaokunyang) refine generics which can be inspired by spring 
ResolvableType.
+@SuppressWarnings("rawtypes")
 public class GenericType {
   private static final Predicate<Type> defaultFinalPredicate =
       type -> {
@@ -115,11 +116,15 @@ public class GenericType {
   }
 
   public static GenericType build(TypeRef<?> context, Type type, 
Predicate<Type> finalPredicate) {
-    return build(context.resolveType(type).getType(), finalPredicate);
+    return build(context.resolveType(type), finalPredicate);
   }
 
-  @SuppressWarnings("rawtypes")
   public static GenericType build(Type type, Predicate<Type> finalPredicate) {
+    return build(TypeRef.of(type), finalPredicate);
+  }
+
+  public static GenericType build(TypeRef<?> typeRef, Predicate<Type> 
finalPredicate) {
+    Type type = typeRef.getType();
     if (type instanceof ParameterizedType) {
       // List<String>, List<T>, Map<String, List<String>>, SomeClass<T>
       Type[] actualTypeArguments = ((ParameterizedType) 
type).getActualTypeArguments();
@@ -129,10 +134,10 @@ public class GenericType {
         list.add(build);
       }
       GenericType[] genericTypes = list.toArray(new GenericType[0]);
-      return new GenericType(TypeRef.of(type), finalPredicate.test(type), 
genericTypes);
+      return new GenericType(typeRef, finalPredicate.test(type), genericTypes);
     } else if (type instanceof GenericArrayType) { // List<String>[] or T[]
       Type componentType = ((GenericArrayType) type).getGenericComponentType();
-      return new GenericType(TypeRef.of(type), finalPredicate.test(type), 
build(componentType));
+      return new GenericType(typeRef, finalPredicate.test(type), 
build(componentType));
     } else if (type instanceof TypeVariable) { // T
       TypeVariable typeVariable = (TypeVariable) type;
       Type typeVariableBound =
@@ -149,7 +154,7 @@ public class GenericType {
       }
     } else {
       // Class type: String, Integer
-      return new GenericType(TypeRef.of(type), finalPredicate.test(type));
+      return new GenericType(typeRef, finalPredicate.test(type));
     }
   }
 
@@ -201,7 +206,7 @@ public class GenericType {
   public boolean trackingRef(ClassResolver classResolver) {
     Boolean trackingRef = this.trackingRef;
     if (trackingRef == null) {
-      trackingRef = this.trackingRef = classResolver.needToWriteRef(cls);
+      trackingRef = this.trackingRef = classResolver.needToWriteRef(typeRef);
     }
     return trackingRef;
   }
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 65c0515a..e0acf5f7 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
@@ -512,6 +512,10 @@ public class TypeUtils {
     return new TypeRef<Collection<E>>() {}.where(new TypeParameter<E>() {}, 
elemType);
   }
 
+  public static <E> TypeRef<Collection<E>> collectionOf(TypeRef<E> elemType, 
Object extMeta) {
+    return new TypeRef<Collection<E>>(extMeta) {}.where(new TypeParameter<E>() 
{}, elemType);
+  }
+
   public static <K, V> TypeRef<Map<K, V>> mapOf(Class<K> keyType, Class<V> 
valueType) {
     return mapOf(TypeRef.of(keyType), TypeRef.of(valueType));
   }
@@ -521,6 +525,12 @@ public class TypeUtils {
         .where(new TypeParameter<V>() {}, valueType);
   }
 
+  public static <K, V> TypeRef<Map<K, V>> mapOf(
+      TypeRef<K> keyType, TypeRef<V> valueType, Object extMeta) {
+    return new TypeRef<Map<K, V>>(extMeta) {}.where(new TypeParameter<K>() {}, 
keyType)
+        .where(new TypeParameter<V>() {}, valueType);
+  }
+
   public static <K, V> TypeRef<? extends Map<K, V>> mapOf(
       Class<?> mapType, TypeRef<K> keyType, TypeRef<V> valueType) {
     TypeRef<Map<K, V>> mapTypeRef = mapOf(keyType, valueType);
diff --git 
a/java/fury-core/src/main/resources/META-INF/native-image/org.apache.fury/fury-core/native-image.properties
 
b/java/fury-core/src/main/resources/META-INF/native-image/org.apache.fury/fury-core/native-image.properties
index d28b1af1..a54bfa97 100644
--- 
a/java/fury-core/src/main/resources/META-INF/native-image/org.apache.fury/fury-core/native-image.properties
+++ 
b/java/fury-core/src/main/resources/META-INF/native-image/org.apache.fury/fury-core/native-image.properties
@@ -238,6 +238,7 @@ 
Args=--initialize-at-build-time=org.apache.fury.memory.MemoryBuffer,\
     org.apache.fury.meta.ClassDefDecoder,\
     org.apache.fury.meta.ClassDefEncoder,\
     org.apache.fury.meta.ClassSpec,\
+    org.apache.fury.meta.TypeExtMeta,\
     org.apache.fury.meta.DeflaterMetaCompressor,\
     org.apache.fury.meta.Encoders,\
     org.apache.fury.meta.MetaCompressor,\
diff --git 
a/java/fury-core/src/test/java/org/apache/fury/meta/ClassDefTest.java 
b/java/fury-core/src/test/java/org/apache/fury/meta/ClassDefTest.java
index 89435b21..7ecf20b0 100644
--- a/java/fury-core/src/test/java/org/apache/fury/meta/ClassDefTest.java
+++ b/java/fury-core/src/test/java/org/apache/fury/meta/ClassDefTest.java
@@ -20,6 +20,7 @@
 package org.apache.fury.meta;
 
 import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
 import static org.testng.Assert.assertTrue;
 
 import com.google.common.collect.ImmutableList;
@@ -34,6 +35,9 @@ import org.apache.fury.Fury;
 import org.apache.fury.FuryTestBase;
 import org.apache.fury.memory.MemoryBuffer;
 import org.apache.fury.reflect.ReflectionUtils;
+import org.apache.fury.reflect.TypeRef;
+import org.apache.fury.resolver.ClassResolver;
+import org.apache.fury.test.bean.Foo;
 import org.testng.annotations.Test;
 
 public class ClassDefTest extends FuryTestBase {
@@ -176,4 +180,12 @@ public class ClassDefTest extends FuryTestBase {
     assertTrue(classDef.getFieldsInfo().isEmpty());
     assertTrue(classDef.isObjectType());
   }
+
+  @Test
+  public void testTypeExtInfo() {
+    Fury fury = Fury.builder().withMetaShare(true).build();
+    ClassResolver classResolver = fury.getClassResolver();
+    assertTrue(classResolver.needToWriteRef(TypeRef.of(Foo.class, new 
TypeExtMeta(true))));
+    assertFalse(classResolver.needToWriteRef(TypeRef.of(Foo.class, new 
TypeExtMeta(false))));
+  }
 }
diff --git 
a/java/fury-core/src/test/java/org/apache/fury/resolver/ClassResolverTest.java 
b/java/fury-core/src/test/java/org/apache/fury/resolver/ClassResolverTest.java
index 3a0e59cf..2656c203 100644
--- 
a/java/fury-core/src/test/java/org/apache/fury/resolver/ClassResolverTest.java
+++ 
b/java/fury-core/src/test/java/org/apache/fury/resolver/ClassResolverTest.java
@@ -53,6 +53,7 @@ import org.apache.fury.logging.Logger;
 import org.apache.fury.logging.LoggerFactory;
 import org.apache.fury.memory.MemoryBuffer;
 import org.apache.fury.memory.MemoryUtils;
+import org.apache.fury.reflect.TypeRef;
 import org.apache.fury.resolver.longlongpkg.C1;
 import org.apache.fury.resolver.longlongpkg.C2;
 import org.apache.fury.resolver.longlongpkg.C3;
@@ -304,7 +305,8 @@ public class ClassResolverTest extends FuryTestBase {
             .requireClassRegistration(false)
             .build();
     ClassResolver classResolver = fury.getClassResolver();
-    
Assert.assertFalse(classResolver.needToWriteRef(TestNeedToWriteReferenceClass.class));
+    Assert.assertFalse(
+        
classResolver.needToWriteRef(TypeRef.of(TestNeedToWriteReferenceClass.class)));
     assertNull(classResolver.getClassInfo(TestNeedToWriteReferenceClass.class, 
false));
   }
 


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


Reply via email to