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

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


The following commit(s) were added to refs/heads/main by this push:
     new 46831b083 fix(java): restore inherited container field types (#3864)
46831b083 is described below

commit 46831b08310a58f535b236d32c29d9c3607817de
Author: Shawn Yang <[email protected]>
AuthorDate: Thu Jul 16 14:19:01 2026 +0530

    fix(java): restore inherited container field types (#3864)
    
    ## Why?
    
    
    
    ## What does this PR do?
    
    
    
    ## Related issues
    
    Closes #3843
    
    ## AI Contribution Checklist
    
    
    
    - [ ] Substantial AI assistance was used in this PR: `yes` / `no`
    - [ ] If `yes`, I included a completed [AI Contribution
    
Checklist](https://github.com/apache/fory/blob/main/AI_POLICY.md#9-contributor-checklist-for-ai-assisted-prs)
    in this PR description and the required `AI Usage Disclosure`.
    - [ ] If `yes`, my PR description includes the required `ai_review`
    summary and screenshot evidence or equivalent persisted links of the
    final clean AI review results from both fresh reviewers described in
    `AI_POLICY.md`, the Fory-guided reviewer and the independent general
    reviewer, on the current PR diff or current HEAD after the latest code
    changes.
    
    
    
    ## Does this PR introduce any user-facing change?
    
    
    
    - [ ] Does this PR introduce any public API change?
    - [ ] Does this PR introduce any binary protocol compatibility change?
    
    ## Benchmark
---
 .../annotation/processing/ForyStructProcessor.java |  20 ++-
 .../fory/annotation/processing/SourceTypeNode.java |  13 +-
 .../processing/ForyStructProcessorTest.java        | 111 ++++++++++++++
 .../main/java/org/apache/fory/meta/FieldTypes.java |  88 ++++++++++--
 .../main/java/org/apache/fory/reflect/TypeRef.java | 160 ++++++++++++++++++---
 .../main/java/org/apache/fory/type/Descriptor.java |  34 ++++-
 .../main/java/org/apache/fory/type/TypeUtils.java  |  18 ++-
 .../java/org/apache/fory/meta/TypeDefTest.java     | 156 ++++++++++++++++++++
 .../java/org/apache/fory/reflect/TypeRefTest.java  | 128 +++++++++++++++++
 9 files changed, 682 insertions(+), 46 deletions(-)

diff --git 
a/java/fory-annotation-processor/src/main/java/org/apache/fory/annotation/processing/ForyStructProcessor.java
 
b/java/fory-annotation-processor/src/main/java/org/apache/fory/annotation/processing/ForyStructProcessor.java
index bb14b00db..d0257dcba 100644
--- 
a/java/fory-annotation-processor/src/main/java/org/apache/fory/annotation/processing/ForyStructProcessor.java
+++ 
b/java/fory-annotation-processor/src/main/java/org/apache/fory/annotation/processing/ForyStructProcessor.java
@@ -761,6 +761,7 @@ public final class ForyStructProcessor extends 
AbstractProcessor {
     }
     List<SourceTypeNode> arguments = new ArrayList<>();
     SourceTypeNode componentType = null;
+    SourceTypeNode ownerType = null;
     if (kind == TypeKind.ARRAY) {
       TypeMirror componentMirror = ((ArrayType) type).getComponentType();
       componentType =
@@ -771,14 +772,22 @@ public final class ForyStructProcessor extends 
AbstractProcessor {
               errorElement,
               true);
     } else if (type instanceof DeclaredType) {
+      DeclaredType declaredType = (DeclaredType) type;
       List<?> argumentTrees = treeInfo.typeArgumentTrees();
       int index = 0;
-      for (TypeMirror argument : ((DeclaredType) type).getTypeArguments()) {
+      for (TypeMirror argument : declaredType.getTypeArguments()) {
         Object argumentTree = index < argumentTrees.size() ? 
argumentTrees.get(index) : null;
         arguments.add(
             buildTypeNode(argument, argumentTree, nestedNullable(argument), 
errorElement, false));
         index++;
       }
+      TypeMirror enclosingType = declaredType.getEnclosingType();
+      // A non-static member container can use enclosing type variables in its 
inherited element
+      // type, so generated descriptors must retain the declared owner 
alongside local arguments.
+      if (enclosingType instanceof DeclaredType
+          && 
!declaredType.asElement().getModifiers().contains(Modifier.STATIC)) {
+        ownerType = buildTypeNode(enclosingType);
+      }
     }
     String rawType = canonicalName(types.erasure(type));
     String extMeta =
@@ -787,7 +796,14 @@ public final class ForyStructProcessor extends 
AbstractProcessor {
     boolean primitive = kind.isPrimitive();
     boolean nestedStruct = isCompatibleForyStructType(type);
     return new SourceTypeNode(
-        rawType, typeName(type), extMeta, arguments, componentType, primitive, 
nestedStruct);
+        rawType,
+        typeName(type),
+        extMeta,
+        arguments,
+        componentType,
+        ownerType,
+        primitive,
+        nestedStruct);
   }
 
   private boolean isCompatibleForyStructType(TypeMirror type) {
diff --git 
a/java/fory-annotation-processor/src/main/java/org/apache/fory/annotation/processing/SourceTypeNode.java
 
b/java/fory-annotation-processor/src/main/java/org/apache/fory/annotation/processing/SourceTypeNode.java
index 7b5dd9245..e53745a1a 100644
--- 
a/java/fory-annotation-processor/src/main/java/org/apache/fory/annotation/processing/SourceTypeNode.java
+++ 
b/java/fory-annotation-processor/src/main/java/org/apache/fory/annotation/processing/SourceTypeNode.java
@@ -29,6 +29,7 @@ final class SourceTypeNode {
   final String typeExtMeta;
   final List<SourceTypeNode> typeArguments;
   final SourceTypeNode componentType;
+  final SourceTypeNode ownerType;
   final boolean primitive;
   final boolean nestedCompatibleStruct;
 
@@ -38,6 +39,7 @@ final class SourceTypeNode {
       String typeExtMeta,
       List<SourceTypeNode> typeArguments,
       SourceTypeNode componentType,
+      SourceTypeNode ownerType,
       boolean primitive,
       boolean nestedCompatibleStruct) {
     this.rawType = rawType;
@@ -48,6 +50,7 @@ final class SourceTypeNode {
             ? Collections.emptyList()
             : Collections.unmodifiableList(new ArrayList<>(typeArguments));
     this.componentType = componentType;
+    this.ownerType = ownerType;
     this.primitive = primitive;
     this.nestedCompatibleStruct = nestedCompatibleStruct;
   }
@@ -55,7 +58,10 @@ final class SourceTypeNode {
   String generatedTypeExpression() {
     // Generated serializers must not reference TypeRef directly: Android 
javac resolves TypeRef's
     // AnnotatedType overloads while compiling generated source.
-    if (typeExtMeta == null && typeArguments.isEmpty() && componentType == 
null) {
+    if (typeExtMeta == null
+        && typeArguments.isEmpty()
+        && componentType == null
+        && ownerType == null) {
       return "Descriptor.generatedType(" + rawType + ".class)";
     }
     return "Descriptor.generatedType("
@@ -66,6 +72,8 @@ final class SourceTypeNode {
         + typeArgumentsExpression()
         + ", "
         + (componentType == null ? "null" : 
componentType.generatedTypeExpression())
+        + ", "
+        + (ownerType == null ? "null" : ownerType.generatedTypeExpression())
         + ")";
   }
 
@@ -91,6 +99,9 @@ final class SourceTypeNode {
     if (componentType != null && componentType.hasNestedCompatibleStruct()) {
       return true;
     }
+    if (ownerType != null && ownerType.hasNestedCompatibleStruct()) {
+      return true;
+    }
     for (SourceTypeNode typeArgument : typeArguments) {
       if (typeArgument.hasNestedCompatibleStruct()) {
         return true;
diff --git 
a/java/fory-annotation-processor/src/test/java/org/apache/fory/annotation/processing/ForyStructProcessorTest.java
 
b/java/fory-annotation-processor/src/test/java/org/apache/fory/annotation/processing/ForyStructProcessorTest.java
index 380297f69..cf6866d2b 100644
--- 
a/java/fory-annotation-processor/src/test/java/org/apache/fory/annotation/processing/ForyStructProcessorTest.java
+++ 
b/java/fory-annotation-processor/src/test/java/org/apache/fory/annotation/processing/ForyStructProcessorTest.java
@@ -30,6 +30,7 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 import javax.tools.Diagnostic;
 import javax.tools.DiagnosticCollector;
 import javax.tools.JavaCompiler;
@@ -43,7 +44,9 @@ import org.apache.fory.context.MetaWriteContext;
 import org.apache.fory.exception.ForyException;
 import org.apache.fory.exception.SerializationException;
 import org.apache.fory.meta.FieldInfo;
+import org.apache.fory.meta.FieldTypes;
 import org.apache.fory.meta.TypeDef;
+import org.apache.fory.reflect.TypeRef;
 import org.apache.fory.serializer.StaticGeneratedStructSerializer;
 import org.apache.fory.type.Descriptor;
 import org.apache.fory.type.Types;
@@ -386,7 +389,10 @@ public class ForyStructProcessorTest {
         compile(
             "test.MetadataStruct",
             "package test;\n"
+                + "import java.util.ArrayList;\n"
+                + "import java.util.HashMap;\n"
                 + "import java.util.List;\n"
+                + "import java.util.Map;\n"
                 + "import org.apache.fory.annotation.ForyStruct;\n"
                 + "import org.apache.fory.annotation.Ref;\n"
                 + "import org.apache.fory.annotation.Int32Type;\n"
@@ -395,6 +401,22 @@ public class ForyStructProcessorTest {
                 + "@ForyStruct public class MetadataStruct {\n"
                 + "  public List<@Ref String> names;\n"
                 + "  public List<@Int32Type(encoding = Int32Encoding.FIXED) 
Integer> codes;\n"
+                + "  public static class MultiParamMap<A, K, V> extends 
HashMap<K, V> {}\n"
+                + "  public static class MultiParamList<A, B, E> extends 
ArrayList<E> {}\n"
+                + "  public static class SwappedMap<K, V> extends HashMap<V, 
K> {}\n"
+                + "  public static class NestedElementList<E> extends 
ArrayList<List<E>> {}\n"
+                + "  public static class Owner<T> {\n"
+                + "    public class Values<E> extends ArrayList<Map<T, E>> 
{}\n"
+                + "  }\n"
+                + "  public static class WildcardList<E> extends 
ArrayList<List<? extends E>> {}\n"
+                + "  @ForyStruct public static class OwnedStruct { public int 
id; }\n"
+                + "  public MultiParamMap<Object, String, List<String>> 
mapped;\n"
+                + "  public MultiParamList<Object, Long, String> listed;\n"
+                + "  public SwappedMap<String, Integer> swapped;\n"
+                + "  public NestedElementList<String> nested;\n"
+                + "  public Owner<String>.Values<Integer> owned;\n"
+                + "  public Owner<OwnedStruct>.Values<Integer> ownedStruct;\n"
+                + "  public WildcardList<String> wildcard;\n"
                 + "  public @UInt16Type int code;\n"
                 + "  public MetadataStruct() {}\n"
                 + "}\n");
@@ -402,6 +424,8 @@ public class ForyStructProcessorTest {
     String generatedSource = 
result.generatedSource("test/MetadataStruct_ForySerializer.java");
     Assert.assertFalse(generatedSource.contains("TypeRef"), generatedSource);
     Assert.assertTrue(generatedSource.contains("Descriptor.generatedType("), 
generatedSource);
+    Assert.assertTrue(
+        generatedSource.contains("HAS_NESTED_COMPATIBLE_STRUCT_FIELDS = 
true;"), generatedSource);
     try (URLClassLoader loader = result.classLoader()) {
       Class<?> type = loader.loadClass("test.MetadataStruct");
       Class<?> serializerType = 
loader.loadClass("test.MetadataStruct_ForySerializer");
@@ -428,6 +452,93 @@ public class ForyStructProcessorTest {
       Assert.assertEquals(
           
codes.getTypeRef().getTypeArguments().get(0).getTypeExtMeta().typeId(), 
Types.INT32);
       
Assert.assertTrue(codes.getTypeRef().getTypeArguments().get(0).getTypeExtMeta().nullable());
+      Descriptor mapped = descriptor(serializer.getDescriptors(), "mapped");
+      Assert.assertEquals(mapped.getTypeRef().getTypeArguments().size(), 2);
+      
Assert.assertEquals(mapped.getTypeRef().getTypeArguments().get(0).getRawType(), 
String.class);
+      TypeRef<?> mappedValue = mapped.getTypeRef().getTypeArguments().get(1);
+      Assert.assertEquals(mappedValue.getRawType(), List.class);
+      Assert.assertEquals(mappedValue.getTypeArguments().get(0).getRawType(), 
String.class);
+      FieldTypes.MapFieldType mappedType =
+          (FieldTypes.MapFieldType) 
FieldTypes.buildFieldType(fory.getTypeResolver(), mapped);
+      Assert.assertEquals(mappedType.getKeyType().getTypeId(), Types.STRING);
+      Assert.assertEquals(
+          ((FieldTypes.CollectionFieldType) 
mappedType.getValueType()).getElementType().getTypeId(),
+          Types.STRING);
+      Descriptor owned = descriptor(serializer.getDescriptors(), "owned");
+      TypeRef<?> ownedElement = owned.getTypeRef().getTypeArguments().get(0);
+      Assert.assertEquals(ownedElement.getRawType(), Map.class);
+      Assert.assertEquals(ownedElement.getTypeArguments().get(0).getRawType(), 
String.class);
+      Assert.assertEquals(ownedElement.getTypeArguments().get(1).getRawType(), 
Integer.class);
+      FieldTypes.CollectionFieldType ownedType =
+          (FieldTypes.CollectionFieldType) 
FieldTypes.buildFieldType(fory.getTypeResolver(), owned);
+      FieldTypes.MapFieldType ownedElementType =
+          (FieldTypes.MapFieldType) ownedType.getElementType();
+      Assert.assertEquals(ownedElementType.getKeyType().getTypeId(), 
Types.STRING);
+      Assert.assertEquals(ownedElementType.getValueType().getTypeId(), 
Types.VARINT32);
+      Descriptor wildcard = descriptor(serializer.getDescriptors(), 
"wildcard");
+      TypeRef<?> wildcardList = 
wildcard.getTypeRef().getTypeArguments().get(0);
+      Assert.assertEquals(wildcardList.getRawType(), List.class);
+      TypeRef<?> wildcardElement = wildcardList.getTypeArguments().get(0);
+      Assert.assertTrue(wildcardElement.isWildcard());
+      Assert.assertEquals(wildcardElement.resolveWildcard().getRawType(), 
String.class);
+      FieldTypes.CollectionFieldType wildcardType =
+          (FieldTypes.CollectionFieldType)
+              FieldTypes.buildFieldType(fory.getTypeResolver(), wildcard);
+      FieldTypes.CollectionFieldType wildcardListType =
+          (FieldTypes.CollectionFieldType) wildcardType.getElementType();
+      Assert.assertEquals(wildcardListType.getElementType().getTypeId(), 
Types.STRING);
+      Descriptor listed = descriptor(serializer.getDescriptors(), "listed");
+      Assert.assertEquals(listed.getTypeRef().getTypeArguments().size(), 1);
+      
Assert.assertEquals(listed.getTypeRef().getTypeArguments().get(0).getRawType(), 
String.class);
+      FieldTypes.CollectionFieldType listedType =
+          (FieldTypes.CollectionFieldType)
+              FieldTypes.buildFieldType(fory.getTypeResolver(), listed);
+      Assert.assertEquals(listedType.getElementType().getTypeId(), 
Types.STRING);
+      Descriptor swapped = descriptor(serializer.getDescriptors(), "swapped");
+      Assert.assertEquals(swapped.getTypeRef().getTypeArguments().size(), 2);
+      Assert.assertEquals(
+          swapped.getTypeRef().getTypeArguments().get(0).getRawType(), 
Integer.class);
+      Assert.assertEquals(
+          swapped.getTypeRef().getTypeArguments().get(1).getRawType(), 
String.class);
+      FieldTypes.MapFieldType swappedType =
+          (FieldTypes.MapFieldType) 
FieldTypes.buildFieldType(fory.getTypeResolver(), swapped);
+      FieldTypes.MapFieldType remoteSwappedType =
+          new FieldTypes.MapFieldType(
+              swappedType.getTypeId(),
+              !swappedType.nullable(),
+              swappedType.trackingRef(),
+              swappedType.getKeyType(),
+              swappedType.getValueType());
+      Descriptor rebuiltSwapped =
+          new FieldInfo(type.getName(), "swapped", remoteSwappedType)
+              .toDescriptor(fory.getTypeResolver(), swapped);
+      
Assert.assertEquals(rebuiltSwapped.getTypeRef().getTypeArguments().size(), 2);
+      Assert.assertEquals(
+          rebuiltSwapped.getTypeRef().getTypeArguments().get(0).getRawType(), 
Integer.class);
+      Assert.assertEquals(
+          rebuiltSwapped.getTypeRef().getTypeArguments().get(1).getRawType(), 
String.class);
+      Descriptor nested = descriptor(serializer.getDescriptors(), "nested");
+      TypeRef<?> nestedElement = nested.getTypeRef().getTypeArguments().get(0);
+      Assert.assertEquals(nestedElement.getRawType(), List.class);
+      
Assert.assertEquals(nestedElement.getTypeArguments().get(0).getRawType(), 
String.class);
+      FieldTypes.CollectionFieldType nestedType =
+          (FieldTypes.CollectionFieldType)
+              FieldTypes.buildFieldType(fory.getTypeResolver(), nested);
+      FieldTypes.CollectionFieldType remoteNestedType =
+          new FieldTypes.CollectionFieldType(
+              nestedType.getTypeId(),
+              !nestedType.nullable(),
+              nestedType.trackingRef(),
+              nestedType.getElementType());
+      Descriptor rebuiltNested =
+          new FieldInfo(type.getName(), "nested", remoteNestedType)
+              .toDescriptor(fory.getTypeResolver(), nested);
+      
Assert.assertEquals(rebuiltNested.getTypeRef().getTypeArguments().size(), 1);
+      TypeRef<?> rebuiltNestedElement = 
rebuiltNested.getTypeRef().getTypeArguments().get(0);
+      Assert.assertEquals(rebuiltNestedElement.getRawType(), List.class);
+      Assert.assertEquals(rebuiltNestedElement.getTypeArguments().size(), 1);
+      Assert.assertEquals(
+          rebuiltNestedElement.getTypeArguments().get(0).getRawType(), 
String.class);
       Descriptor code = descriptor(serializer.getDescriptors(), "code");
       Assert.assertTrue(code.getTypeRef().hasTypeExtMeta());
       Assert.assertEquals(code.getTypeRef().getTypeExtMeta().typeId(), 
Types.UINT16);
diff --git a/java/fory-core/src/main/java/org/apache/fory/meta/FieldTypes.java 
b/java/fory-core/src/main/java/org/apache/fory/meta/FieldTypes.java
index 751a31477..284414af9 100644
--- a/java/fory-core/src/main/java/org/apache/fory/meta/FieldTypes.java
+++ b/java/fory-core/src/main/java/org/apache/fory/meta/FieldTypes.java
@@ -32,7 +32,9 @@ import java.io.Serializable;
 import java.lang.annotation.Annotation;
 import java.lang.reflect.Array;
 import java.lang.reflect.Field;
+import java.util.HashSet;
 import java.util.Objects;
+import java.util.Set;
 import org.apache.fory.collection.BFloat16List;
 import org.apache.fory.collection.BoolList;
 import org.apache.fory.collection.Float16List;
@@ -106,6 +108,36 @@ public class FieldTypes {
   /** Build field type from generics, nested generics will be extracted too. */
   private static FieldType buildFieldType(
       TypeResolver resolver, Descriptor descriptor, GenericType genericType) {
+    return buildFieldType(resolver, descriptor, genericType, new HashSet<>());
+  }
+
+  private static FieldType buildFieldType(
+      TypeResolver resolver,
+      Descriptor descriptor,
+      GenericType genericType,
+      Set<String> activeTypes) {
+    TypeRef<?> typeRef = genericType.getTypeRef();
+    // TypeRef equality intentionally ignores explicit arguments without 
type-use metadata. Use its
+    // semantic key so generated List<List<T>> is not mistaken for a recursive 
List<T> branch.
+    String typeKey = typeRef.getTypeKey();
+    if (!activeTypes.add(typeKey)) {
+      Preconditions.checkState(descriptor == null);
+      // Raw, F-bounded, and mutually recursive container bindings do not 
always carry TypeRef's
+      // explicit-empty recursion marker. End the repeated schema branch as 
Object.
+      return buildFieldTypeNode(resolver, null, 
GenericType.build(Object.class), activeTypes);
+    }
+    try {
+      return buildFieldTypeNode(resolver, descriptor, genericType, 
activeTypes);
+    } finally {
+      activeTypes.remove(typeKey);
+    }
+  }
+
+  private static FieldType buildFieldTypeNode(
+      TypeResolver resolver,
+      Descriptor descriptor,
+      GenericType genericType,
+      Set<String> activeTypes) {
     Preconditions.checkNotNull(genericType);
     Field field = descriptor == null ? null : descriptor.getField();
     Class<?> rawType = genericType.getCls();
@@ -273,6 +305,7 @@ public class FieldTypes {
 
     if (COLLECTION_TYPE.isSupertypeOf(genericType.getTypeRef())
         || (isXlang && (resolver.isCollection(rawType) || 
resolver.isSet(rawType)))) {
+      TypeRef<?> elementType = getCollectionElementType(genericType);
       return new CollectionFieldType(
           typeId,
           nullable,
@@ -280,9 +313,11 @@ public class FieldTypes {
           buildFieldType(
               resolver,
               null, // nested fields don't have Field reference
-              getTypeParameter(genericType, 0)));
+              resolver.buildGenericType(elementType),
+              activeTypes));
     } else if (MAP_TYPE.isSupertypeOf(genericType.getTypeRef())
         || (isXlang && resolver.isMap(rawType))) {
+      Tuple2<TypeRef<?>, TypeRef<?>> mapKeyValueType = 
getMapKeyValueType(genericType);
       return new MapFieldType(
           typeId,
           nullable,
@@ -290,11 +325,13 @@ public class FieldTypes {
           buildFieldType(
               resolver,
               null, // nested fields don't have Field reference
-              getTypeParameter(genericType, 0)),
+              resolver.buildGenericType(mapKeyValueType.f0),
+              activeTypes),
           buildFieldType(
               resolver,
               null, // nested fields don't have Field reference
-              getTypeParameter(genericType, 1)));
+              resolver.buildGenericType(mapKeyValueType.f1),
+              activeTypes));
     } else if (isUnionType || Union.class.isAssignableFrom(rawType)) {
       return new UnionFieldType(nullable, trackingRef);
     } else if (Types.isEnumType(typeId)) {
@@ -318,7 +355,7 @@ public class FieldTypes {
               typeId,
               nullable,
               trackingRef,
-              buildFieldType(resolver, null, GenericType.build(elemType)));
+              buildFieldType(resolver, null, GenericType.build(elemType), 
activeTypes));
         } else {
           // For native mode, use Java class IDs for arrays
           if (resolver.isRegisteredById(rawType)) {
@@ -329,7 +366,7 @@ public class FieldTypes {
               typeId,
               nullable,
               trackingRef,
-              buildFieldType(resolver, null, 
GenericType.build(arrayComponentInfo.f0)),
+              buildFieldType(resolver, null, 
GenericType.build(arrayComponentInfo.f0), activeTypes),
               arrayComponentInfo.f1);
         }
       }
@@ -341,11 +378,40 @@ public class FieldTypes {
     }
   }
 
-  private static GenericType getTypeParameter(GenericType genericType, int 
index) {
-    if (genericType.getTypeParametersCount() <= index) {
-      return GenericType.build(Object.class);
+  private static TypeRef<?> getCollectionElementType(GenericType genericType) {
+    TypeRef<?> typeRef = genericType.getTypeRef();
+    // An explicit empty argument list terminates self-referential container 
expansion.
+    if (typeRef.hasExplicitTypeArguments() && 
typeRef.getTypeArguments().isEmpty()) {
+      return TypeRef.of(Object.class);
+    }
+    if (COLLECTION_TYPE.isSupertypeOf(typeRef)) {
+      // TypeRef normalizes CollectionXXX<A, B, C> extends Collection<C> to 
semantic element C.
+      return TypeUtils.getElementType(typeRef);
+    }
+    if (genericType.getTypeParametersCount() >= 1) {
+      // Non-Java xlang collection types cannot use Java hierarchy resolution.
+      return genericType.getTypeParameters()[0].getTypeRef();
     }
-    return genericType.getTypeParameters()[index];
+    return TypeRef.of(Object.class);
+  }
+
+  private static Tuple2<TypeRef<?>, TypeRef<?>> getMapKeyValueType(GenericType 
genericType) {
+    TypeRef<?> typeRef = genericType.getTypeRef();
+    // An explicit empty argument list terminates self-referential container 
expansion.
+    if (typeRef.hasExplicitTypeArguments() && 
typeRef.getTypeArguments().isEmpty()) {
+      return Tuple2.of(TypeRef.of(Object.class), TypeRef.of(Object.class));
+    }
+    if (MAP_TYPE.isSupertypeOf(typeRef)) {
+      // TypeRef normalizes MapXXX<A, B, C> extends Map<B, C> to semantic 
key/value B/C.
+      return TypeUtils.getMapKeyValueType(typeRef);
+    }
+    if (genericType.getTypeParametersCount() >= 2) {
+      // Non-Java xlang map types cannot use Java hierarchy resolution.
+      return Tuple2.of(
+          genericType.getTypeParameters()[0].getTypeRef(),
+          genericType.getTypeParameters()[1].getTypeRef());
+    }
+    return Tuple2.of(TypeRef.of(Object.class), TypeRef.of(Object.class));
   }
 
   private static TypeExtMeta primitiveListInlineMeta(TypeRef<?> typeRef) {
@@ -913,7 +979,7 @@ public class FieldTypes {
             && Objects.equals(declared.getTypeExtMeta(), extMeta)) {
           return declared;
         }
-        return TypeRef.of(
+        return TypeRef.ofSemanticTypeArguments(
             declared.getType(), extMeta, 
java.util.Collections.singletonList(elementType), null);
       }
       // Build array type from element type
@@ -1020,7 +1086,7 @@ public class FieldTypes {
             && Objects.equals(declared.getTypeExtMeta(), extMeta)) {
           return declared;
         }
-        return TypeRef.of(
+        return TypeRef.ofSemanticTypeArguments(
             declared.getType(), extMeta, java.util.Arrays.asList(keyTypeRef, 
valueTypeRef), null);
       }
       return mapOf(
diff --git a/java/fory-core/src/main/java/org/apache/fory/reflect/TypeRef.java 
b/java/fory-core/src/main/java/org/apache/fory/reflect/TypeRef.java
index c7d5ece3b..a12f49675 100644
--- a/java/fory-core/src/main/java/org/apache/fory/reflect/TypeRef.java
+++ b/java/fory-core/src/main/java/org/apache/fory/reflect/TypeRef.java
@@ -104,6 +104,16 @@ public class TypeRef<T> {
       List<TypeRef<?>> typeArguments,
       TypeRef<?> componentType,
       boolean normalizeArgs) {
+    this(type, typeExtMeta, typeArguments, componentType, normalizeArgs, null);
+  }
+
+  private TypeRef(
+      Type type,
+      TypeExtMeta typeExtMeta,
+      List<TypeRef<?>> typeArguments,
+      TypeRef<?> componentType,
+      boolean normalizeArgs,
+      Set<Class<?>> activeContainerTypes) {
     this.type = type;
     this.typeExtMeta = typeExtMeta;
     this.typeArguments =
@@ -111,7 +121,10 @@ public class TypeRef<T> {
             ? null
             : immutableTypeArguments(
                 normalizeArgs
-                    ? normalizeContainerTypeArguments(type, typeArguments)
+                    ? normalizeContainerTypeArguments(
+                        type,
+                        typeArguments,
+                        activeContainerTypes == null ? new HashSet<>() : 
activeContainerTypes)
                     : typeArguments);
     this.componentType = componentType;
     this.hasTypeExtMeta = hasNestedTypeExtMeta(typeExtMeta, 
this.typeArguments, componentType);
@@ -156,6 +169,53 @@ public class TypeRef<T> {
     return new TypeRef<>(type, typeExtMeta, typeArguments, componentType);
   }
 
+  /** Builds a container type whose arguments are normalized semantic 
element/key/value types. */
+  @Internal
+  public static <T> TypeRef<T> ofSemanticTypeArguments(
+      Type type,
+      TypeExtMeta typeExtMeta,
+      List<TypeRef<?>> typeArguments,
+      TypeRef<?> componentType) {
+    // Hierarchy normalization interprets arguments as raw declaration 
parameters. Callers of this
+    // factory already carry semantic element/key/value types and must not 
normalize them again.
+    return new TypeRef<>(type, typeExtMeta, typeArguments, componentType, 
false);
+  }
+
+  /** Builds a generated type whose arguments still follow the raw class 
declaration. */
+  @Internal
+  public static <T> TypeRef<T> ofDeclaredTypeArguments(
+      Class<T> rawType,
+      TypeExtMeta typeExtMeta,
+      List<TypeRef<?>> typeArguments,
+      TypeRef<?> componentType) {
+    return ofDeclaredTypeArguments(rawType, typeExtMeta, typeArguments, 
componentType, null);
+  }
+
+  /** Builds a generated member type whose arguments still follow the source 
declaration. */
+  @Internal
+  public static <T> TypeRef<T> ofDeclaredTypeArguments(
+      Class<T> rawType,
+      TypeExtMeta typeExtMeta,
+      List<TypeRef<?>> typeArguments,
+      TypeRef<?> componentType,
+      TypeRef<?> ownerType) {
+    List<TypeRef<?>> normalizedArguments = typeArguments;
+    Type declaredType = rawType;
+    if (typeArguments != null) {
+      Type[] argumentTypes = new Type[typeArguments.size()];
+      for (int i = 0; i < typeArguments.size(); i++) {
+        argumentTypes[i] = typeArguments.get(i).getType();
+      }
+      declaredType =
+          new ParameterizedTypeImpl(
+              ownerType == null ? null : ownerType.getType(), rawType, 
argumentTypes);
+      normalizedArguments = normalizeContainerTypeArguments(declaredType, 
typeArguments);
+    }
+    // Keep declaration arguments in the Java Type for owner-variable 
resolution, while explicit
+    // arguments carry normalized element/key/value semantics and type-use 
metadata.
+    return new TypeRef<>(declaredType, typeExtMeta, normalizedArguments, 
componentType, false);
+  }
+
   @Internal
   public static <T> TypeRef<T> ofTypeUse(Object typeUse) {
     return TypeUseMetadata.typeRef(typeUse);
@@ -173,40 +233,63 @@ public class TypeRef<T> {
 
   private static List<TypeRef<?>> normalizeContainerTypeArguments(
       Type type, List<TypeRef<?>> typeArguments) {
-    if (typeArguments.isEmpty()) {
+    return normalizeContainerTypeArguments(type, typeArguments, new 
HashSet<>());
+  }
+
+  private static List<TypeRef<?>> normalizeContainerTypeArguments(
+      Type type, List<TypeRef<?>> typeArguments, Set<Class<?>> 
activeContainerTypes) {
+    Class<?> rawType = TypeUtils.getRawType(type);
+    boolean mapLike = isMapLike(rawType);
+    if (!mapLike && !isIterableLike(rawType)) {
       return typeArguments;
     }
-    Class<?> rawType = TypeUtils.getRawType(type);
-    if (isMapLike(rawType)) {
-      return normalizeMapTypeArguments(type, rawType, typeArguments);
+    // Inherited element/key/value types can form mutual cycles across 
container classes. A repeated
+    // raw owner has no finite semantic argument tree, so terminate that 
branch explicitly.
+    if (!activeContainerTypes.add(rawType)) {
+      return Collections.emptyList();
     }
-    if (isIterableLike(rawType)) {
-      return normalizeIterableTypeArguments(type, rawType, typeArguments);
+    try {
+      if (mapLike) {
+        return normalizeMapTypeArguments(type, rawType, typeArguments, 
activeContainerTypes);
+      }
+      return normalizeIterableTypeArguments(type, rawType, typeArguments, 
activeContainerTypes);
+    } finally {
+      activeContainerTypes.remove(rawType);
     }
-    return typeArguments;
   }
 
   private static List<TypeRef<?>> normalizeIterableTypeArguments(
-      Type type, Class<?> rawType, List<TypeRef<?>> typeArguments) {
+      Type type,
+      Class<?> rawType,
+      List<TypeRef<?>> typeArguments,
+      Set<Class<?>> activeContainerTypes) {
     if (!hasFullExplicitRawArgs(type, rawType, typeArguments)) {
       return typeArguments;
     }
     TypeRef<?> elementType = rawIterableElementType(rawType);
     return Collections.singletonList(
         resolveTypeVariables(
-            elementType.getType(), explicitTypeVarRefs(rawType, 
typeArguments), rawType));
+            elementType.getType(),
+            explicitTypeVarRefs(type, rawType, typeArguments),
+            rawType,
+            activeContainerTypes));
   }
 
   private static List<TypeRef<?>> normalizeMapTypeArguments(
-      Type type, Class<?> rawType, List<TypeRef<?>> typeArguments) {
+      Type type,
+      Class<?> rawType,
+      List<TypeRef<?>> typeArguments,
+      Set<Class<?>> activeContainerTypes) {
     if (!hasFullExplicitRawArgs(type, rawType, typeArguments)) {
       return typeArguments;
     }
     Tuple2<TypeRef<?>, TypeRef<?>> keyValueType = rawMapKeyValueTypes(rawType);
-    Map<TypeVariableKey, TypeRef<?>> typeVarRefs = 
explicitTypeVarRefs(rawType, typeArguments);
+    Map<TypeVariableKey, TypeRef<?>> typeVarRefs =
+        explicitTypeVarRefs(type, rawType, typeArguments);
     return Arrays.asList(
-        resolveTypeVariables(keyValueType.f0.getType(), typeVarRefs, rawType),
-        resolveTypeVariables(keyValueType.f1.getType(), typeVarRefs, rawType));
+        resolveTypeVariables(keyValueType.f0.getType(), typeVarRefs, rawType, 
activeContainerTypes),
+        resolveTypeVariables(
+            keyValueType.f1.getType(), typeVarRefs, rawType, 
activeContainerTypes));
   }
 
   private static boolean hasFullExplicitRawArgs(
@@ -217,9 +300,12 @@ public class TypeRef<T> {
   }
 
   private static Map<TypeVariableKey, TypeRef<?>> explicitTypeVarRefs(
-      Class<?> rawType, List<TypeRef<?>> typeArguments) {
-    TypeVariable<?>[] variables = rawType.getTypeParameters();
+      Type type, Class<?> rawType, List<TypeRef<?>> typeArguments) {
     Map<TypeVariableKey, TypeRef<?>> typeVarRefs = new HashMap<>();
+    for (Map.Entry<TypeVariableKey, Type> entry : 
resolveTypeMappings(type).entrySet()) {
+      typeVarRefs.put(entry.getKey(), TypeRef.of(entry.getValue()));
+    }
+    TypeVariable<?>[] variables = rawType.getTypeParameters();
     for (int i = 0; i < variables.length; i++) {
       typeVarRefs.put(new TypeVariableKey(variables[i]), typeArguments.get(i));
     }
@@ -257,24 +343,39 @@ public class TypeRef<T> {
   }
 
   private static TypeRef<?> resolveTypeVariables(
-      Type type, Map<TypeVariableKey, TypeRef<?>> typeVarRefs, Class<?> 
containerRawType) {
+      Type type,
+      Map<TypeVariableKey, TypeRef<?>> typeVarRefs,
+      Class<?> containerRawType,
+      Set<Class<?>> activeContainerTypes) {
     if (type instanceof TypeVariable) {
       TypeRef<?> typeRef = typeVarRefs.get(new 
TypeVariableKey((TypeVariable<?>) type));
       return typeRef == null ? TypeRef.of(type) : typeRef;
     }
+    if (type instanceof WildcardType) {
+      WildcardType wildcardType = (WildcardType) type;
+      Type[] upperBounds =
+          resolveTypeVariables(
+              wildcardType.getUpperBounds(), typeVarRefs, containerRawType, 
activeContainerTypes);
+      Type[] lowerBounds =
+          resolveTypeVariables(
+              wildcardType.getLowerBounds(), typeVarRefs, containerRawType, 
activeContainerTypes);
+      return TypeRef.of(new WildcardTypeImpl(upperBounds, lowerBounds));
+    }
     if (type instanceof ParameterizedType) {
       ParameterizedType parameterizedType = (ParameterizedType) type;
       Type ownerType = parameterizedType.getOwnerType();
       Type resolvedOwnerType =
           ownerType == null
               ? null
-              : resolveTypeVariables(ownerType, typeVarRefs, 
containerRawType).getType();
+              : resolveTypeVariables(ownerType, typeVarRefs, containerRawType, 
activeContainerTypes)
+                  .getType();
       Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
       List<TypeRef<?>> resolvedArguments = new 
ArrayList<>(actualTypeArguments.length);
       Type[] resolvedTypes = new Type[actualTypeArguments.length];
       for (int i = 0; i < actualTypeArguments.length; i++) {
         TypeRef<?> resolvedType =
-            resolveTypeVariables(actualTypeArguments[i], typeVarRefs, 
containerRawType);
+            resolveTypeVariables(
+                actualTypeArguments[i], typeVarRefs, containerRawType, 
activeContainerTypes);
         resolvedArguments.add(resolvedType);
         resolvedTypes[i] = resolvedType.getType();
       }
@@ -287,17 +388,34 @@ public class TypeRef<T> {
         // arguments again.
         return ofResolvedTypeArgs(resolvedType, Collections.emptyList());
       }
-      return TypeRef.of(resolvedType, null, resolvedArguments, null);
+      return new TypeRef<>(resolvedType, null, resolvedArguments, null, true, 
activeContainerTypes);
     }
     if (type instanceof GenericArrayType) {
       TypeRef<?> componentType =
           resolveTypeVariables(
-              ((GenericArrayType) type).getGenericComponentType(), 
typeVarRefs, containerRawType);
+              ((GenericArrayType) type).getGenericComponentType(),
+              typeVarRefs,
+              containerRawType,
+              activeContainerTypes);
       return TypeRef.of(newArrayType(componentType.getType()), null, null, 
componentType);
     }
     return TypeRef.of(type);
   }
 
+  private static Type[] resolveTypeVariables(
+      Type[] types,
+      Map<TypeVariableKey, TypeRef<?>> typeVarRefs,
+      Class<?> containerRawType,
+      Set<Class<?>> activeContainerTypes) {
+    Type[] resolvedTypes = new Type[types.length];
+    for (int i = 0; i < types.length; i++) {
+      resolvedTypes[i] =
+          resolveTypeVariables(types[i], typeVarRefs, containerRawType, 
activeContainerTypes)
+              .getType();
+    }
+    return resolvedTypes;
+  }
+
   private static TypeRef<?> ofResolvedTypeArgs(Type type, List<TypeRef<?>> 
typeArguments) {
     return new TypeRef<>(type, null, typeArguments, null, false);
   }
diff --git a/java/fory-core/src/main/java/org/apache/fory/type/Descriptor.java 
b/java/fory-core/src/main/java/org/apache/fory/type/Descriptor.java
index f3ef0616f..18e5e4719 100644
--- a/java/fory-core/src/main/java/org/apache/fory/type/Descriptor.java
+++ b/java/fory-core/src/main/java/org/apache/fory/type/Descriptor.java
@@ -123,12 +123,14 @@ public class Descriptor {
     private final TypeExtMeta typeExtMeta;
     private final List<GeneratedType> typeArguments;
     private final GeneratedType componentType;
+    private final GeneratedType ownerType;
 
     private GeneratedType(
         Class<?> rawType,
         TypeExtMeta typeExtMeta,
         List<GeneratedType> typeArguments,
-        GeneratedType componentType) {
+        GeneratedType componentType,
+        GeneratedType ownerType) {
       this.rawType = Objects.requireNonNull(rawType);
       this.typeExtMeta = typeExtMeta;
       this.typeArguments =
@@ -136,12 +138,13 @@ public class Descriptor {
               ? java.util.Collections.emptyList()
               : java.util.Collections.unmodifiableList(new 
ArrayList<>(typeArguments));
       this.componentType = componentType;
+      this.ownerType = ownerType;
     }
   }
 
   @Internal
   public static GeneratedType generatedType(Class<?> rawType) {
-    return new GeneratedType(rawType, null, null, null);
+    return new GeneratedType(rawType, null, null, null, null);
   }
 
   @Internal
@@ -150,7 +153,17 @@ public class Descriptor {
       TypeExtMeta typeExtMeta,
       List<GeneratedType> typeArguments,
       GeneratedType componentType) {
-    return new GeneratedType(rawType, typeExtMeta, typeArguments, 
componentType);
+    return new GeneratedType(rawType, typeExtMeta, typeArguments, 
componentType, null);
+  }
+
+  @Internal
+  public static GeneratedType generatedType(
+      Class<?> rawType,
+      TypeExtMeta typeExtMeta,
+      List<GeneratedType> typeArguments,
+      GeneratedType componentType,
+      GeneratedType ownerType) {
+    return new GeneratedType(rawType, typeExtMeta, typeArguments, 
componentType, ownerType);
   }
 
   private static TypeRef<?> toTypeRef(GeneratedType generatedType) {
@@ -164,9 +177,22 @@ public class Descriptor {
     }
     TypeRef<?> componentType =
         generatedType.componentType == null ? null : 
toTypeRef(generatedType.componentType);
-    if (generatedType.typeExtMeta == null && typeArguments == null && 
componentType == null) {
+    TypeRef<?> ownerType =
+        generatedType.ownerType == null ? null : 
toTypeRef(generatedType.ownerType);
+    if (generatedType.typeExtMeta == null
+        && typeArguments == null
+        && componentType == null
+        && ownerType == null) {
       return TypeRef.of(generatedType.rawType);
     }
+    if (typeArguments != null || ownerType != null) {
+      return TypeRef.ofDeclaredTypeArguments(
+          generatedType.rawType,
+          generatedType.typeExtMeta,
+          typeArguments == null ? java.util.Collections.emptyList() : 
typeArguments,
+          componentType,
+          ownerType);
+    }
     return TypeRef.of(
         generatedType.rawType, generatedType.typeExtMeta, typeArguments, 
componentType);
   }
diff --git a/java/fory-core/src/main/java/org/apache/fory/type/TypeUtils.java 
b/java/fory-core/src/main/java/org/apache/fory/type/TypeUtils.java
index dac44db53..d87b86e0a 100644
--- a/java/fory-core/src/main/java/org/apache/fory/type/TypeUtils.java
+++ b/java/fory-core/src/main/java/org/apache/fory/type/TypeUtils.java
@@ -773,13 +773,13 @@ public class TypeUtils {
   public static <E> TypeRef<Collection<E>> collectionOf(TypeRef<E> elemType) {
     TypeRef<Collection<E>> raw =
         new TypeRef<Collection<E>>() {}.where(new TypeParameter<E>() {}, 
elemType);
-    return TypeRef.of(raw.getType(), null, Arrays.asList(elemType), null);
+    return TypeRef.ofSemanticTypeArguments(raw.getType(), null, 
Arrays.asList(elemType), null);
   }
 
   public static <E> TypeRef<Collection<E>> collectionOf(TypeRef<E> elemType, 
TypeExtMeta extMeta) {
     TypeRef<Collection<E>> raw =
         new TypeRef<Collection<E>>(extMeta) {}.where(new TypeParameter<E>() 
{}, elemType);
-    return TypeRef.of(raw.getType(), extMeta, Arrays.asList(elemType), null);
+    return TypeRef.ofSemanticTypeArguments(raw.getType(), extMeta, 
Arrays.asList(elemType), null);
   }
 
   public static <E> TypeRef<? extends Collection<E>> collectionOf(
@@ -787,7 +787,7 @@ public class TypeUtils {
     TypeRef<? extends Collection<E>> raw =
         new TypeRef<Collection<E>>(extMeta) {}.where(new TypeParameter<E>() 
{}, elemType)
             .getSubtype(collectionType);
-    return TypeRef.of(raw.getType(), extMeta, Arrays.asList(elemType), null);
+    return TypeRef.ofSemanticTypeArguments(raw.getType(), extMeta, 
Arrays.asList(elemType), null);
   }
 
   public static <K, V> TypeRef<Map<K, V>> mapOf(Class<K> keyType, Class<V> 
valueType) {
@@ -798,7 +798,8 @@ public class TypeUtils {
     TypeRef<Map<K, V>> raw =
         new TypeRef<Map<K, V>>() {}.where(new TypeParameter<K>() {}, keyType)
             .where(new TypeParameter<V>() {}, valueType);
-    return TypeRef.of(raw.getType(), null, Arrays.asList(keyType, valueType), 
null);
+    return TypeRef.ofSemanticTypeArguments(
+        raw.getType(), null, Arrays.asList(keyType, valueType), null);
   }
 
   public static <K, V> TypeRef<Map<K, V>> mapOf(
@@ -806,7 +807,8 @@ public class TypeUtils {
     TypeRef<Map<K, V>> raw =
         new TypeRef<Map<K, V>>(extMeta) {}.where(new TypeParameter<K>() {}, 
keyType)
             .where(new TypeParameter<V>() {}, valueType);
-    return TypeRef.of(raw.getType(), extMeta, Arrays.asList(keyType, 
valueType), null);
+    return TypeRef.ofSemanticTypeArguments(
+        raw.getType(), extMeta, Arrays.asList(keyType, valueType), null);
   }
 
   public static <K, V> TypeRef<? extends Map<K, V>> mapOf(
@@ -815,14 +817,16 @@ public class TypeUtils {
         new TypeRef<Map<K, V>>(extMeta) {}.where(new TypeParameter<K>() {}, 
keyType)
             .where(new TypeParameter<V>() {}, valueType);
     TypeRef<? extends Map<K, V>> raw = mapTypeRef.getSubtype(mapType);
-    return TypeRef.of(raw.getType(), extMeta, Arrays.asList(keyType, 
valueType), null);
+    return TypeRef.ofSemanticTypeArguments(
+        raw.getType(), extMeta, Arrays.asList(keyType, valueType), null);
   }
 
   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);
     TypeRef<? extends Map<K, V>> raw = mapTypeRef.getSubtype(mapType);
-    return TypeRef.of(raw.getType(), null, Arrays.asList(keyType, valueType), 
null);
+    return TypeRef.ofSemanticTypeArguments(
+        raw.getType(), null, Arrays.asList(keyType, valueType), null);
   }
 
   public static <K, V> TypeRef<? extends Map<K, V>> mapOf(
diff --git a/java/fory-core/src/test/java/org/apache/fory/meta/TypeDefTest.java 
b/java/fory-core/src/test/java/org/apache/fory/meta/TypeDefTest.java
index d0b30f73b..133361d89 100644
--- a/java/fory-core/src/test/java/org/apache/fory/meta/TypeDefTest.java
+++ b/java/fory-core/src/test/java/org/apache/fory/meta/TypeDefTest.java
@@ -25,6 +25,7 @@ import static org.testng.Assert.assertTrue;
 
 import com.google.common.collect.ImmutableList;
 import java.lang.reflect.Field;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
@@ -82,6 +83,55 @@ public class TypeDefTest extends ForyTestBase {
     private Map map3;
   }
 
+  static class StringMap extends HashMap<String, String> {}
+
+  static class StringMapHolder {
+    private StringMap values;
+  }
+
+  static class StringList extends ArrayList<String> {}
+
+  static class StringListHolder {
+    private StringList values;
+  }
+
+  static class SelfList extends ArrayList<SelfList> {}
+
+  static class SelfListHolder {
+    private SelfList values;
+  }
+
+  static class SelfMap extends HashMap<SelfMap, SelfMap> {}
+
+  static class SelfMapHolder {
+    private SelfMap values;
+  }
+
+  static class BoundedCollectionHolder<T extends Collection<T>> {
+    private T values;
+  }
+
+  static class MultiParamMap<A, K, V> extends HashMap<K, V> {}
+
+  static class MultiParamMapHolder {
+    private MultiParamMap<Object, String, List<String>> values;
+  }
+
+  static class MultiParamList<A, B, E> extends ArrayList<E> {}
+
+  static class MultiParamListHolder {
+    private MultiParamList<Object, Long, String> values;
+  }
+
+  static class SwappedMap<K, V> extends HashMap<V, K> {}
+
+  static class NestedElementList<E> extends ArrayList<List<E>> {}
+
+  static class RemappedContainerHolder {
+    private SwappedMap<String, Integer> map;
+    private NestedElementList<String> list;
+  }
+
   @Test
   public void testTypeDefSerialization() throws NoSuchFieldException {
     Fory fory =
@@ -185,6 +235,112 @@ public class TypeDefTest extends ForyTestBase {
     assertEquals(typeDef1, typeDef);
   }
 
+  @Test
+  public void testInheritedMapFieldTypes() {
+    Fory fory = 
Fory.builder().withXlang(false).requireClassRegistration(false).build();
+    TypeDef typeDef = TypeDef.buildTypeDef(fory.getTypeResolver(), 
StringMapHolder.class);
+
+    FieldTypes.MapFieldType mapType =
+        (FieldTypes.MapFieldType) 
typeDef.getFieldsInfo().get(0).getFieldType();
+    assertEquals(mapType.getKeyType().getTypeId(), Types.STRING);
+    assertEquals(mapType.getValueType().getTypeId(), Types.STRING);
+  }
+
+  @Test
+  public void testInheritedCollectionFieldType() {
+    Fory fory = 
Fory.builder().withXlang(false).requireClassRegistration(false).build();
+    TypeDef typeDef = TypeDef.buildTypeDef(fory.getTypeResolver(), 
StringListHolder.class);
+
+    FieldTypes.CollectionFieldType collectionType =
+        (FieldTypes.CollectionFieldType) 
typeDef.getFieldsInfo().get(0).getFieldType();
+    assertEquals(collectionType.getElementType().getTypeId(), Types.STRING);
+  }
+
+  @Test
+  public void testRecursiveContainerFieldTypes() {
+    Fory fory = 
Fory.builder().withXlang(false).requireClassRegistration(false).build();
+
+    TypeDef listTypeDef = TypeDef.buildTypeDef(fory.getTypeResolver(), 
SelfListHolder.class);
+    FieldTypes.CollectionFieldType listType =
+        (FieldTypes.CollectionFieldType) 
listTypeDef.getFieldsInfo().get(0).getFieldType();
+    assertEquals(listType.getElementType().getTypeId(), 
ClassResolver.EMPTY_OBJECT_ID);
+
+    TypeDef mapTypeDef = TypeDef.buildTypeDef(fory.getTypeResolver(), 
SelfMapHolder.class);
+    FieldTypes.MapFieldType mapType =
+        (FieldTypes.MapFieldType) 
mapTypeDef.getFieldsInfo().get(0).getFieldType();
+    assertEquals(mapType.getKeyType().getTypeId(), 
ClassResolver.EMPTY_OBJECT_ID);
+    assertEquals(mapType.getValueType().getTypeId(), 
ClassResolver.EMPTY_OBJECT_ID);
+
+    TypeDef boundedTypeDef =
+        TypeDef.buildTypeDef(fory.getTypeResolver(), 
BoundedCollectionHolder.class);
+    FieldTypes.CollectionFieldType boundedType =
+        (FieldTypes.CollectionFieldType) 
boundedTypeDef.getFieldsInfo().get(0).getFieldType();
+    assertEquals(boundedType.getElementType().getTypeId(), 
ClassResolver.EMPTY_OBJECT_ID);
+  }
+
+  @Test
+  public void testMultiParamContainerFieldTypes() {
+    Fory fory = 
Fory.builder().withXlang(false).requireClassRegistration(false).build();
+
+    TypeDef mapTypeDef = TypeDef.buildTypeDef(fory.getTypeResolver(), 
MultiParamMapHolder.class);
+    FieldTypes.MapFieldType mapType =
+        (FieldTypes.MapFieldType) 
mapTypeDef.getFieldsInfo().get(0).getFieldType();
+    assertEquals(mapType.getKeyType().getTypeId(), Types.STRING);
+    FieldTypes.CollectionFieldType valueType =
+        (FieldTypes.CollectionFieldType) mapType.getValueType();
+    assertEquals(valueType.getElementType().getTypeId(), Types.STRING);
+
+    TypeDef listTypeDef = TypeDef.buildTypeDef(fory.getTypeResolver(), 
MultiParamListHolder.class);
+    FieldTypes.CollectionFieldType listType =
+        (FieldTypes.CollectionFieldType) 
listTypeDef.getFieldsInfo().get(0).getFieldType();
+    assertEquals(listType.getElementType().getTypeId(), Types.STRING);
+  }
+
+  @Test
+  public void testContainerTypeRebuild() {
+    Fory fory = 
Fory.builder().withXlang(false).requireClassRegistration(false).build();
+    Map<String, Descriptor> descriptors =
+        Descriptor.getDescriptorsMap(RemappedContainerHolder.class);
+
+    Descriptor mapDescriptor = descriptors.get("map");
+    FieldTypes.MapFieldType mapType =
+        (FieldTypes.MapFieldType) 
FieldTypes.buildFieldType(fory.getTypeResolver(), mapDescriptor);
+    FieldTypes.MapFieldType remoteMapType =
+        new FieldTypes.MapFieldType(
+            mapType.getTypeId(),
+            !mapType.nullable(),
+            mapType.trackingRef(),
+            mapType.getKeyType(),
+            mapType.getValueType());
+    Descriptor rebuiltMap =
+        new FieldInfo(RemappedContainerHolder.class.getName(), "map", 
remoteMapType)
+            .toDescriptor(fory.getTypeResolver(), mapDescriptor);
+    List<TypeRef<?>> rebuiltMapTypes = 
rebuiltMap.getTypeRef().getTypeArguments();
+    assertEquals(rebuiltMapTypes.size(), 2);
+    assertEquals(rebuiltMapTypes.get(0).getRawType(), Integer.class);
+    assertEquals(rebuiltMapTypes.get(1).getRawType(), String.class);
+
+    Descriptor listDescriptor = descriptors.get("list");
+    FieldTypes.CollectionFieldType listType =
+        (FieldTypes.CollectionFieldType)
+            FieldTypes.buildFieldType(fory.getTypeResolver(), listDescriptor);
+    FieldTypes.CollectionFieldType remoteListType =
+        new FieldTypes.CollectionFieldType(
+            listType.getTypeId(),
+            !listType.nullable(),
+            listType.trackingRef(),
+            listType.getElementType());
+    Descriptor rebuiltList =
+        new FieldInfo(RemappedContainerHolder.class.getName(), "list", 
remoteListType)
+            .toDescriptor(fory.getTypeResolver(), listDescriptor);
+    List<TypeRef<?>> rebuiltListTypes = 
rebuiltList.getTypeRef().getTypeArguments();
+    assertEquals(rebuiltListTypes.size(), 1);
+    TypeRef<?> rebuiltElementType = rebuiltListTypes.get(0);
+    assertEquals(rebuiltElementType.getRawType(), List.class);
+    assertEquals(rebuiltElementType.getTypeArguments().size(), 1);
+    assertEquals(rebuiltElementType.getTypeArguments().get(0).getRawType(), 
String.class);
+  }
+
   @Test
   public void testInterface() {
     Fory fory = 
Fory.builder().withXlang(false).withMetaShare(true).withCompatible(false).build();
diff --git 
a/java/fory-core/src/test/java/org/apache/fory/reflect/TypeRefTest.java 
b/java/fory-core/src/test/java/org/apache/fory/reflect/TypeRefTest.java
index 0c64b0a07..46d70f46f 100644
--- a/java/fory-core/src/test/java/org/apache/fory/reflect/TypeRefTest.java
+++ b/java/fory-core/src/test/java/org/apache/fory/reflect/TypeRefTest.java
@@ -131,6 +131,20 @@ public class TypeRefTest extends ForyTestBase {
 
   static class MultiParamMap<A, K, V> extends HashMap<K, V> {}
 
+  static class SwappedMap<K, V> extends HashMap<V, K> {}
+
+  static class NestedElementList<E> extends ArrayList<List<E>> {}
+
+  static class ParamA<T> extends ArrayList<ParamB<T>> {}
+
+  static class ParamB<T> extends ArrayList<ParamA<T>> {}
+
+  static class Owner<T> {
+    class Values<E> extends ArrayList<Map<T, E>> {}
+  }
+
+  static class WildcardList<T> extends ArrayList<List<? extends T>> {}
+
   static class StringKeyMap<V> extends HashMap<String, V> {}
 
   static class ArrayElementList<A, E> extends ArrayList<E[]> {}
@@ -180,6 +194,120 @@ public class TypeRefTest extends ForyTestBase {
     Assert.assertEquals(fixedKeyMapGenericType.getTypeParametersCount(), 2);
     Assert.assertEquals(fixedKeyMapGenericType.getTypeParameter0().getCls(), 
String.class);
     Assert.assertEquals(fixedKeyMapGenericType.getTypeParameter1().getCls(), 
List.class);
+
+    TypeRef<?> generatedMapType =
+        TypeRef.ofDeclaredTypeArguments(
+            MultiParamMap.class,
+            null,
+            Arrays.asList(
+                TypeRef.of(String.class), TypeRef.of(Long.class), 
TypeRef.of(Integer.class)),
+            null);
+    Assert.assertEquals(generatedMapType.getTypeArguments().size(), 2);
+    Assert.assertEquals(generatedMapType.getTypeArguments().get(0), 
TypeRef.of(Long.class));
+    Assert.assertEquals(generatedMapType.getTypeArguments().get(1), 
TypeRef.of(Integer.class));
+
+    TypeRef<?> generatedListType =
+        TypeRef.ofDeclaredTypeArguments(
+            MultiParamList.class,
+            null,
+            Arrays.asList(TypeRef.of(String.class), TypeRef.of(Integer.class)),
+            null);
+    Assert.assertEquals(generatedListType.getTypeArguments().size(), 1);
+    Assert.assertEquals(generatedListType.getTypeArguments().get(0), 
TypeRef.of(Integer.class));
+
+    TypeRef<?> generatedSwappedMap =
+        TypeRef.ofDeclaredTypeArguments(
+            SwappedMap.class,
+            null,
+            Arrays.asList(TypeRef.of(String.class), TypeRef.of(Integer.class)),
+            null);
+    Assert.assertEquals(
+        generatedSwappedMap.getTypeArguments(),
+        Arrays.asList(TypeRef.of(Integer.class), TypeRef.of(String.class)));
+    TypeRef<?> rebuiltSwappedMap =
+        TypeRef.of(SwappedMap.class, null, 
generatedSwappedMap.getTypeArguments(), null);
+    Assert.assertEquals(
+        rebuiltSwappedMap.getTypeArguments(), 
generatedSwappedMap.getTypeArguments());
+
+    TypeRef<?> generatedNestedList =
+        TypeRef.ofDeclaredTypeArguments(
+            NestedElementList.class, null, 
Arrays.asList(TypeRef.of(String.class)), null);
+    Assert.assertEquals(
+        generatedNestedList.getTypeArguments(), Arrays.asList(new 
TypeRef<List<String>>() {}));
+    TypeRef<?> rebuiltNestedList =
+        TypeRef.of(NestedElementList.class, null, 
generatedNestedList.getTypeArguments(), null);
+    Assert.assertEquals(
+        rebuiltNestedList.getTypeArguments(), 
generatedNestedList.getTypeArguments());
+
+    TypeRef<?> semanticSwappedMap =
+        TypeUtils.mapOf(
+            SwappedMap.class, TypeRef.of(Integer.class), 
TypeRef.of(String.class), null);
+    Assert.assertEquals(
+        semanticSwappedMap.getTypeArguments(),
+        Arrays.asList(TypeRef.of(Integer.class), TypeRef.of(String.class)));
+    TypeRef<?> semanticNestedList =
+        TypeUtils.collectionOf(NestedElementList.class, new 
TypeRef<List<String>>() {}, null);
+    Assert.assertEquals(
+        semanticNestedList.getTypeArguments(), Arrays.asList(new 
TypeRef<List<String>>() {}));
+  }
+
+  @Test
+  public void testMutualContainerType() {
+    TypeRef<?> type = new TypeRef<ParamA<String>>() {};
+    assertMutualContainerType(type);
+    Assert.assertNotNull(GenericType.build(type));
+
+    TypeRef<?> generatedType =
+        TypeRef.ofDeclaredTypeArguments(
+            ParamA.class, null, Arrays.asList(TypeRef.of(String.class)), null);
+    assertMutualContainerType(generatedType);
+    Assert.assertNotNull(GenericType.build(generatedType));
+  }
+
+  private static void assertMutualContainerType(TypeRef<?> type) {
+    TypeRef<?> paramB = type.getTypeArguments().get(0);
+    Assert.assertEquals(paramB.getRawType(), ParamB.class);
+    TypeRef<?> paramA = paramB.getTypeArguments().get(0);
+    Assert.assertEquals(paramA.getRawType(), ParamA.class);
+    Assert.assertTrue(paramA.getTypeArguments().isEmpty());
+  }
+
+  @Test
+  public void testOwnerContainerType() {
+    assertOwnerContainerType(new TypeRef<Owner<String>.Values<Integer>>() {});
+
+    TypeRef<?> ownerType =
+        TypeRef.ofDeclaredTypeArguments(
+            Owner.class, null, Arrays.asList(TypeRef.of(String.class)), null);
+    TypeRef<?> generatedType =
+        TypeRef.ofDeclaredTypeArguments(
+            Owner.Values.class, null, 
Arrays.asList(TypeRef.of(Integer.class)), null, ownerType);
+    assertOwnerContainerType(generatedType);
+  }
+
+  private static void assertOwnerContainerType(TypeRef<?> type) {
+    TypeRef<?> elementType = TypeUtils.getElementType(type);
+    Assert.assertEquals(elementType.getRawType(), Map.class);
+    Tuple2<TypeRef<?>, TypeRef<?>> keyValueType = 
TypeUtils.getMapKeyValueType(elementType);
+    Assert.assertEquals(keyValueType.f0.getRawType(), String.class);
+    Assert.assertEquals(keyValueType.f1.getRawType(), Integer.class);
+  }
+
+  @Test
+  public void testWildcardContainerType() {
+    assertWildcardContainerType(new TypeRef<WildcardList<String>>() {});
+    TypeRef<?> generatedType =
+        TypeRef.ofDeclaredTypeArguments(
+            WildcardList.class, null, Arrays.asList(TypeRef.of(String.class)), 
null);
+    assertWildcardContainerType(generatedType);
+  }
+
+  private static void assertWildcardContainerType(TypeRef<?> type) {
+    TypeRef<?> listType = TypeUtils.getElementType(type);
+    Assert.assertEquals(listType.getRawType(), List.class);
+    TypeRef<?> wildcardType = listType.getTypeArguments().get(0);
+    Assert.assertTrue(wildcardType.isWildcard());
+    Assert.assertEquals(wildcardType.resolveWildcard().getRawType(), 
String.class);
   }
 
   @Test


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

Reply via email to