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]