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 9699707e8 fix(java): fix collection get element type bug (#3803)
9699707e8 is described below
commit 9699707e8145d0e51e18e86fe82cbcda5e5e76b7
Author: Pigsy-Monk <[email protected]>
AuthorDate: Wed Jul 1 00:52:00 2026 +0800
fix(java): fix collection get element type bug (#3803)
## Why?
## What does this PR do?
## Related issues
Closes #3798
## 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
---------
Co-authored-by: 慕白 <[email protected]>
---
.../main/java/org/apache/fory/meta/FieldTypes.java | 51 ++---
.../main/java/org/apache/fory/reflect/TypeRef.java | 224 ++++++++++++++++++---
.../org/apache/fory/serializer/FieldGroups.java | 8 -
.../serializer/collection/MapLikeSerializer.java | 68 +------
.../main/java/org/apache/fory/type/ScalaTypes.java | 7 +-
.../main/java/org/apache/fory/type/TypeUtils.java | 61 +++---
.../java/org/apache/fory/reflect/TypeRefTest.java | 143 +++++++++++++
.../collection/CollectionSerializersTest.java | 203 +++++++++++++++++++
.../serializer/collection/MapSerializersTest.java | 153 ++++++++++++++
9 files changed, 747 insertions(+), 171 deletions(-)
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 6dc1266a2..2f9da98bb 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
@@ -278,12 +278,9 @@ public class FieldTypes {
buildFieldType(
resolver,
null, // nested fields don't have Field reference
- genericType.getTypeParameter0() == null
- ? GenericType.build(Object.class)
- : genericType.getTypeParameter0()));
+ getTypeParameter(genericType, 0)));
} else if (MAP_TYPE.isSupertypeOf(genericType.getTypeRef())
|| (isXlang && resolver.isMap(rawType))) {
- Tuple2<TypeRef<?>, TypeRef<?>> mapKeyValueType =
getMapKeyValueType(genericType);
return new MapFieldType(
typeId,
nullable,
@@ -291,15 +288,11 @@ public class FieldTypes {
buildFieldType(
resolver,
null, // nested fields don't have Field reference
- mapKeyValueType.f0 == null
- ? GenericType.build(Object.class)
- : resolver.buildGenericType(mapKeyValueType.f0)),
+ getTypeParameter(genericType, 0)),
buildFieldType(
resolver,
null, // nested fields don't have Field reference
- mapKeyValueType.f1 == null
- ? GenericType.build(Object.class)
- : resolver.buildGenericType(mapKeyValueType.f1)));
+ getTypeParameter(genericType, 1)));
} else if (isUnionType || Union.class.isAssignableFrom(rawType)) {
return new UnionFieldType(nullable, trackingRef);
} else if (Types.isEnumType(typeId)) {
@@ -346,16 +339,11 @@ public class FieldTypes {
}
}
- private static Tuple2<TypeRef<?>, TypeRef<?>> getMapKeyValueType(GenericType
genericType) {
- if (genericType.getTypeParametersCount() >= 2) {
- return Tuple2.of(
- genericType.getTypeParameter0().getTypeRef(),
- genericType.getTypeParameter1().getTypeRef());
+ private static GenericType getTypeParameter(GenericType genericType, int
index) {
+ if (genericType.getTypeParametersCount() <= index) {
+ return GenericType.build(Object.class);
}
- if (!MAP_TYPE.isSupertypeOf(genericType.getTypeRef())) {
- return Tuple2.of(TypeRef.of(Object.class), TypeRef.of(Object.class));
- }
- return TypeUtils.getMapKeyValueType(genericType.getTypeRef());
+ return genericType.getTypeParameters()[index];
}
private static TypeExtMeta primitiveListInlineMeta(TypeRef<?> typeRef) {
@@ -918,16 +906,13 @@ public class FieldTypes {
return collectionOf(elementType, TypeExtMeta.of(typeId, nullable,
trackingRef));
}
if (!declaredClass.isArray()) {
- if (declElementType.equals(elementType)) {
- return declared;
- }
TypeExtMeta extMeta = typeExtMeta(typeId, nullable, trackingRef,
declared);
- if (!java.util.Collection.class.isAssignableFrom(declaredClass)
- && resolver.isCollection(declaredClass)) {
- return TypeRef.of(
- declaredClass, extMeta,
java.util.Collections.singletonList(elementType), null);
+ if (declElementType.equals(elementType)
+ && Objects.equals(declared.getTypeExtMeta(), extMeta)) {
+ return declared;
}
- return collectionOf(declaredClass, elementType, extMeta);
+ return TypeRef.of(
+ declared.getType(), extMeta,
java.util.Collections.singletonList(elementType), null);
}
// Build array type from element type
// elementType could be base type (int) or intermediate array (int[])
@@ -1028,13 +1013,13 @@ public class FieldTypes {
TypeExtMeta extMeta = typeExtMeta(typeId, nullable, trackingRef,
declared);
TypeRef<?> keyTypeRef = keyType.toTypeToken(classResolver, keyDecl);
TypeRef<?> valueTypeRef = valueType.toTypeToken(classResolver,
valueDecl);
- Class<?> declaredClass = declared.getRawType();
- if (!java.util.Map.class.isAssignableFrom(declaredClass)
- && classResolver.isMap(declaredClass)) {
- return TypeRef.of(
- declaredClass, extMeta, java.util.Arrays.asList(keyTypeRef,
valueTypeRef), null);
+ if (keyDecl.equals(keyTypeRef)
+ && valueDecl.equals(valueTypeRef)
+ && Objects.equals(declared.getTypeExtMeta(), extMeta)) {
+ return declared;
}
- return mapOf(declaredClass, keyTypeRef, valueTypeRef, extMeta);
+ return TypeRef.of(
+ declared.getType(), extMeta, java.util.Arrays.asList(keyTypeRef,
valueTypeRef), null);
}
return mapOf(
keyType.toTypeToken(classResolver, keyDecl),
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 cc28b2d5a..407a6ddfa 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
@@ -14,7 +14,6 @@
package org.apache.fory.reflect;
-import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.Modifier;
@@ -26,14 +25,18 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.annotation.CheckForNull;
import org.apache.fory.annotation.Internal;
+import org.apache.fory.collection.Tuple2;
import org.apache.fory.meta.TypeExtMeta;
+import org.apache.fory.type.ScalaTypes;
import org.apache.fory.type.TypeUtils;
// Mostly derived from Guava 32.1.2 com.google.common.reflect.TypeToken
@@ -94,8 +97,16 @@ public class TypeRef<T> {
TypeRef<?> componentType) {
this.type = type;
this.typeExtMeta = typeExtMeta;
- this.typeArguments = typeArguments;
+ this.typeArguments =
+ typeArguments == null
+ ? null
+ : immutableTypeArguments(normalizeContainerTypeArguments(type,
typeArguments));
this.componentType = componentType;
+ this.hasTypeExtMeta = hasNestedTypeExtMeta(typeExtMeta,
this.typeArguments, componentType);
+ }
+
+ private static boolean hasNestedTypeExtMeta(
+ TypeExtMeta typeExtMeta, List<TypeRef<?>> typeArguments, TypeRef<?>
componentType) {
boolean hasMeta = typeExtMeta != null;
if (!hasMeta && typeArguments != null) {
for (TypeRef<?> typeArg : typeArguments) {
@@ -108,7 +119,7 @@ public class TypeRef<T> {
if (!hasMeta && componentType != null) {
hasMeta = componentType.hasTypeExtMeta();
}
- this.hasTypeExtMeta = hasMeta;
+ return hasMeta;
}
/** Returns an instance of type token that wraps {@code type}. */
@@ -130,9 +141,7 @@ public class TypeRef<T> {
TypeExtMeta typeExtMeta,
List<TypeRef<?>> typeArguments,
TypeRef<?> componentType) {
- List<TypeRef<?>> explicitTypeArguments =
- typeArguments == null ? null : Collections.unmodifiableList(new
ArrayList<>(typeArguments));
- return new TypeRef<>(type, typeExtMeta, explicitTypeArguments,
componentType);
+ return new TypeRef<>(type, typeExtMeta, typeArguments, componentType);
}
@Internal
@@ -140,6 +149,151 @@ public class TypeRef<T> {
return TypeUseMetadata.typeRef(typeUse);
}
+ private static List<TypeRef<?>> immutableTypeArguments(List<TypeRef<?>>
typeArguments) {
+ return typeArguments == null
+ ? null
+ : Collections.unmodifiableList(new ArrayList<>(typeArguments));
+ }
+
+ private static List<TypeRef<?>> normalizeContainerTypeArguments(
+ Type type, List<TypeRef<?>> typeArguments) {
+ if (typeArguments.isEmpty()) {
+ return typeArguments;
+ }
+ Class<?> rawType = TypeUtils.getRawType(type);
+ if (isMapLike(rawType)) {
+ return normalizeMapTypeArguments(type, rawType, typeArguments);
+ }
+ if (isIterableLike(rawType)) {
+ return normalizeIterableTypeArguments(type, rawType, typeArguments);
+ }
+ return typeArguments;
+ }
+
+ private static List<TypeRef<?>> normalizeIterableTypeArguments(
+ Type type, Class<?> rawType, List<TypeRef<?>> typeArguments) {
+ if (!hasFullExplicitRawArgs(type, rawType, typeArguments)) {
+ return typeArguments;
+ }
+ return Collections.singletonList(
+ resolveTypeVariables(
+ rawIterableElementType(rawType).getType(),
+ explicitTypeVarRefs(rawType, typeArguments)));
+ }
+
+ private static List<TypeRef<?>> normalizeMapTypeArguments(
+ Type type, Class<?> rawType, List<TypeRef<?>> typeArguments) {
+ if (!hasFullExplicitRawArgs(type, rawType, typeArguments)) {
+ return typeArguments;
+ }
+ Tuple2<TypeRef<?>, TypeRef<?>> keyValueType = rawMapKeyValueTypes(rawType);
+ Map<TypeVariableKey, TypeRef<?>> typeVarRefs =
explicitTypeVarRefs(rawType, typeArguments);
+ return Arrays.asList(
+ resolveTypeVariables(keyValueType.f0.getType(), typeVarRefs),
+ resolveTypeVariables(keyValueType.f1.getType(), typeVarRefs));
+ }
+
+ private static boolean hasFullExplicitRawArgs(
+ Type type, Class<?> rawType, List<TypeRef<?>> typeArguments) {
+ return type instanceof ParameterizedType
+ && ((ParameterizedType) type).getActualTypeArguments().length ==
typeArguments.size()
+ && rawType.getTypeParameters().length == typeArguments.size();
+ }
+
+ private static Map<TypeVariableKey, TypeRef<?>> explicitTypeVarRefs(
+ Class<?> rawType, List<TypeRef<?>> typeArguments) {
+ TypeVariable<?>[] variables = rawType.getTypeParameters();
+ Map<TypeVariableKey, TypeRef<?>> typeVarRefs = new HashMap<>();
+ for (int i = 0; i < variables.length; i++) {
+ typeVarRefs.put(new TypeVariableKey(variables[i]), typeArguments.get(i));
+ }
+ return typeVarRefs;
+ }
+
+ private static TypeRef<?> rawIterableElementType(Class<?> rawType) {
+ if (isScalaIterable(rawType)) {
+ @SuppressWarnings({"rawtypes", "unchecked"})
+ TypeRef<?> iterableType =
+ ((TypeRef) TypeRef.of(rawType)).getSupertype((Class)
ScalaTypes.getScalaIterableType());
+ return iterableType
+ .resolveType(ScalaTypes.getScalaIteratorReturnType())
+ .resolveType(ScalaTypes.getScalaNextReturnType());
+ }
+ @SuppressWarnings("unchecked")
+ TypeRef<?> iterableType =
+ ((TypeRef<? extends Iterable<?>>)
TypeRef.of(rawType)).getSupertype(Iterable.class);
+ return iterableType.resolveType(Iterable.class.getTypeParameters()[0]);
+ }
+
+ private static Tuple2<TypeRef<?>, TypeRef<?>> rawMapKeyValueTypes(Class<?>
rawType) {
+ if (isScalaMap(rawType)) {
+ TypeRef<?> kvTupleType = rawIterableElementType(rawType);
+ ParameterizedType type = (ParameterizedType) kvTupleType.getType();
+ Type[] types = type.getActualTypeArguments();
+ return Tuple2.of(TypeRef.of(types[0]), TypeRef.of(types[1]));
+ }
+ @SuppressWarnings("unchecked")
+ TypeRef<?> mapType =
+ ((TypeRef<? extends Map<?, ?>>)
TypeRef.of(rawType)).getSupertype(Map.class);
+ TypeVariable<?>[] typeParameters = Map.class.getTypeParameters();
+ return Tuple2.of(
+ mapType.resolveType(typeParameters[0]),
mapType.resolveType(typeParameters[1]));
+ }
+
+ private static TypeRef<?> resolveTypeVariables(
+ Type type, Map<TypeVariableKey, TypeRef<?>> typeVarRefs) {
+ if (type instanceof TypeVariable) {
+ TypeRef<?> typeRef = typeVarRefs.get(new
TypeVariableKey((TypeVariable<?>) type));
+ return typeRef == null ? TypeRef.of(type) : typeRef;
+ }
+ if (type instanceof ParameterizedType) {
+ ParameterizedType parameterizedType = (ParameterizedType) type;
+ Type ownerType = parameterizedType.getOwnerType();
+ Type resolvedOwnerType =
+ ownerType == null ? null : resolveTypeVariables(ownerType,
typeVarRefs).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);
+ resolvedArguments.add(resolvedType);
+ resolvedTypes[i] = resolvedType.getType();
+ }
+ return TypeRef.of(
+ new ParameterizedTypeImpl(
+ resolvedOwnerType, parameterizedType.getRawType(),
resolvedTypes),
+ null,
+ resolvedArguments,
+ null);
+ }
+ if (type instanceof GenericArrayType) {
+ TypeRef<?> componentType =
+ resolveTypeVariables(((GenericArrayType)
type).getGenericComponentType(), typeVarRefs);
+ return TypeRef.of(newArrayType(componentType.getType()), null, null,
componentType);
+ }
+ return TypeRef.of(type);
+ }
+
+ private static boolean isMapLike(Class<?> rawType) {
+ return Map.class.isAssignableFrom(rawType) || isScalaMap(rawType);
+ }
+
+ private static boolean isIterableLike(Class<?> rawType) {
+ return Iterable.class.isAssignableFrom(rawType) ||
isScalaIterable(rawType);
+ }
+
+ private static boolean isScalaMap(Class<?> rawType) {
+ return ScalaTypes.SCALA_AVAILABLE
+ && rawType.getName().startsWith("scala.collection")
+ && ScalaTypes.getScalaMapType().isAssignableFrom(rawType);
+ }
+
+ private static boolean isScalaIterable(Class<?> rawType) {
+ return ScalaTypes.SCALA_AVAILABLE
+ && rawType.getName().startsWith("scala.collection")
+ && ScalaTypes.getScalaIterableType().isAssignableFrom(rawType);
+ }
+
/** Returns the captured type. */
private Type capture() {
final Type superclass = getClass().getGenericSuperclass();
@@ -212,7 +366,7 @@ public class TypeRef<T> {
}
public boolean hasExplicitTypeArguments() {
- return typeArguments != null;
+ return typeArguments != null || type instanceof ParameterizedType;
}
public List<TypeRef<?>> getTypeArguments() {
@@ -220,10 +374,12 @@ public class TypeRef<T> {
return typeArguments;
}
if (type instanceof ParameterizedType) {
- ParameterizedType parameterizedType = (ParameterizedType) type;
- return Arrays.stream(parameterizedType.getActualTypeArguments())
- .map(TypeRef::of)
- .collect(Collectors.toList());
+ Type[] actualTypeArguments = ((ParameterizedType)
type).getActualTypeArguments();
+ List<TypeRef<?>> args = new ArrayList<>(actualTypeArguments.length);
+ for (Type actualTypeArgument : actualTypeArguments) {
+ args.add(TypeRef.of(actualTypeArgument));
+ }
+ return immutableTypeArguments(normalizeContainerTypeArguments(type,
args));
}
return new ArrayList<>();
}
@@ -483,7 +639,6 @@ public class TypeRef<T> {
Type resolvedRawType = resolveType0(parameterizedType.getRawType(),
mappings).type;
Type[] args = parameterizedType.getActualTypeArguments();
-
Type[] resolvedArgs = new Type[args.length];
for (int i = 0; i < args.length; i++) {
resolvedArgs[i] = resolveType0(args[i], mappings).type;
@@ -515,15 +670,20 @@ public class TypeRef<T> {
}
private static void populateTypeMapping(Map<TypeVariableKey, Type> storage,
Type... types) {
+ populateTypeMapping(storage, new HashSet<>(), types);
+ }
+
+ private static void populateTypeMapping(
+ Map<TypeVariableKey, Type> storage, Set<Class<?>> visitedClasses,
Type... types) {
for (Type type : types) {
if (type == null) {
continue;
}
if (type instanceof TypeVariable) {
- populateTypeMapping(storage, ((TypeVariable<?>) type).getBounds());
+ populateTypeMapping(storage, visitedClasses, ((TypeVariable<?>)
type).getBounds());
} else if (type instanceof WildcardType) {
- populateTypeMapping(storage, ((WildcardType) type).getUpperBounds());
+ populateTypeMapping(storage, visitedClasses, ((WildcardType)
type).getUpperBounds());
} else if (type instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) type;
@@ -554,12 +714,21 @@ public class TypeRef<T> {
}
storage.put(key, arg);
}
- populateTypeMapping(storage, rawClass);
- populateTypeMapping(storage, parameterizedType.getOwnerType());
+ // Scala collection traits can form recursive generic supertype
graphs. The parameterized
+ // occurrence above still contributes its mappings; the raw class
hierarchy only needs one
+ // walk per resolution.
+ if (visitedClasses.add(rawClass)) {
+ populateTypeMapping(storage, visitedClasses,
rawClass.getGenericSuperclass());
+ populateTypeMapping(storage, visitedClasses,
rawClass.getGenericInterfaces());
+ }
+ populateTypeMapping(storage, visitedClasses,
parameterizedType.getOwnerType());
} else if (type instanceof Class) {
Class<?> clazz = (Class<?>) type;
- populateTypeMapping(storage, clazz.getGenericSuperclass());
- populateTypeMapping(storage, clazz.getGenericInterfaces());
+ if (!visitedClasses.add(clazz)) {
+ continue;
+ }
+ populateTypeMapping(storage, visitedClasses,
clazz.getGenericSuperclass());
+ populateTypeMapping(storage, visitedClasses,
clazz.getGenericInterfaces());
} else {
throw new AssertionError("Unknown type: " + type);
}
@@ -1215,7 +1384,13 @@ public class TypeRef<T> {
LocalClass<String> localClassInstance = new LocalClass<String>() {};
Class<?> subclass = localClassInstance.getClass();
- ParameterizedType parameterizedType = (ParameterizedType)
subclass.getGenericSuperclass();
+ Type genericSuperclass = subclass.getGenericSuperclass();
+ if (!(genericSuperclass instanceof ParameterizedType)) {
+ // Android release minification can strip this probe's Signature
attribute while leaving
+ // user generic metadata intact. The probe must not make TypeRef
unusable in that case.
+ return LOCAL_CLASS_HAS_NO_OWNER;
+ }
+ ParameterizedType parameterizedType = (ParameterizedType)
genericSuperclass;
for (ClassOwnership behavior : ClassOwnership.values()) {
if (behavior.getOwnerType(LocalClass.class) ==
parameterizedType.getOwnerType()) {
return behavior;
@@ -1445,13 +1620,10 @@ public class TypeRef<T> {
@Override
public int hashCode() {
- Annotation[] declaredAnnotations = typeVariable.getDeclaredAnnotations();
- String name = typeVariable.getName();
-
- int result = 1;
- result =
- 31 * result + (declaredAnnotations != null ?
Arrays.hashCode(declaredAnnotations) : 0);
- result = 31 * result + (name != null ? name.hashCode() : 0);
+ // Match typeVariablesEquals and avoid TypeVariable annotation APIs,
which are absent on
+ // Android runtimes used by the instrumented tests.
+ int result = typeVariable.getGenericDeclaration().hashCode();
+ result = 31 * result + typeVariable.getName().hashCode();
return result;
}
}
diff --git
a/java/fory-core/src/main/java/org/apache/fory/serializer/FieldGroups.java
b/java/fory-core/src/main/java/org/apache/fory/serializer/FieldGroups.java
index 1337285e9..06e763707 100644
--- a/java/fory-core/src/main/java/org/apache/fory/serializer/FieldGroups.java
+++ b/java/fory-core/src/main/java/org/apache/fory/serializer/FieldGroups.java
@@ -22,7 +22,6 @@ package org.apache.fory.serializer;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collection;
import java.util.IdentityHashMap;
import java.util.List;
@@ -263,13 +262,6 @@ public class FieldGroups {
t = resolver.buildGenericType(typeRef);
}
Class<?> cls = t.getCls();
- if (t.getTypeParametersCount() > 0) {
- boolean skip =
- Arrays.stream(t.getTypeParameters()).allMatch(p -> p.getCls() ==
Object.class);
- if (skip) {
- t = new GenericType(t.getTypeRef(), t.isMonomorphic());
- }
- }
genericType = t;
Field field = descriptor.getField();
if (field != null) {
diff --git
a/java/fory-core/src/main/java/org/apache/fory/serializer/collection/MapLikeSerializer.java
b/java/fory-core/src/main/java/org/apache/fory/serializer/collection/MapLikeSerializer.java
index 334bd8a35..4f6f828d1 100644
---
a/java/fory-core/src/main/java/org/apache/fory/serializer/collection/MapLikeSerializer.java
+++
b/java/fory-core/src/main/java/org/apache/fory/serializer/collection/MapLikeSerializer.java
@@ -30,7 +30,6 @@ import static
org.apache.fory.serializer.collection.MapFlags.TRACKING_KEY_REF;
import static
org.apache.fory.serializer.collection.MapFlags.TRACKING_VALUE_REF;
import static org.apache.fory.serializer.collection.MapFlags.VALUE_DECL_TYPE;
import static org.apache.fory.serializer.collection.MapFlags.VALUE_HAS_NULL;
-import static org.apache.fory.type.TypeUtils.MAP_TYPE;
import java.lang.invoke.MethodHandle;
import java.lang.reflect.Constructor;
@@ -38,7 +37,6 @@ import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.fory.annotation.CodegenInvoke;
-import org.apache.fory.collection.Tuple2;
import org.apache.fory.config.Config;
import org.apache.fory.context.CopyContext;
import org.apache.fory.context.ReadContext;
@@ -47,7 +45,6 @@ import org.apache.fory.exception.DeserializationException;
import org.apache.fory.memory.MemoryBuffer;
import org.apache.fory.platform.AndroidSupport;
import org.apache.fory.reflect.ReflectionUtils;
-import org.apache.fory.reflect.TypeRef;
import org.apache.fory.resolver.RefMode;
import org.apache.fory.resolver.TypeInfo;
import org.apache.fory.resolver.TypeInfoHolder;
@@ -55,7 +52,6 @@ import org.apache.fory.resolver.TypeResolver;
import org.apache.fory.serializer.Serializer;
import org.apache.fory.type.GenericType;
import org.apache.fory.type.Generics;
-import org.apache.fory.type.TypeUtils;
import org.apache.fory.util.Preconditions;
/** Serializer for all map-like objects. */
@@ -68,10 +64,6 @@ public abstract class MapLikeSerializer<T> extends
Serializer<T> {
final TypeInfoHolder keyTypeInfoReadCache;
final TypeInfoHolder valueTypeInfoWriteCache;
final TypeInfoHolder valueTypeInfoReadCache;
- GenericType partialGenericKVTypeKey0;
- GenericType partialGenericKVTypeValue0;
- GenericType partialGenericKVTypeKey1;
- GenericType partialGenericKVTypeValue1;
private MapTypeCache(TypeResolver typeResolver) {
keyTypeInfoWriteCache = typeResolver.nilTypeInfoHolder();
@@ -138,15 +130,12 @@ public abstract class MapLikeSerializer<T> extends
Serializer<T> {
Generics generics = writeContext.getGenerics();
while (entry != null) {
GenericType genericType =
generics.nextGenericType(writeContext.getDepth());
- if (genericType == null) {
+ if (genericType == null || genericType.getTypeParametersCount() < 2) {
entry = writeJavaNullChunk(writeContext, entry, iterator, null, null);
if (entry != null) {
entry = writeJavaChunk(writeContext, classResolver, entry, iterator,
null, null);
}
} else {
- if (genericType.getTypeParametersCount() < 2) {
- genericType = getKVGenericType(genericType);
- }
GenericType keyGenericType = genericType.getTypeParameter0();
GenericType valueGenericType = genericType.getTypeParameter1();
entry =
@@ -445,12 +434,6 @@ public abstract class MapLikeSerializer<T> extends
Serializer<T> {
Entry<Object, Object> entry,
Iterator<Entry<Object, Object>> iterator) {
MemoryBuffer buffer = writeContext.getBuffer();
- // type parameters count for `Map field` will be 0;
- // type parameters count for `SubMap<V> field` which SubMap is
- // `SubMap<V> implements Map<String, V>` will be 1;
- if (genericType.getTypeParametersCount() < 2) {
- genericType = getKVGenericType(genericType);
- }
GenericType keyGenericType = genericType.getTypeParameter0();
GenericType valueGenericType = genericType.getTypeParameter1();
if (keyGenericType == objType && valueGenericType == objType) {
@@ -538,43 +521,6 @@ public abstract class MapLikeSerializer<T> extends
Serializer<T> {
return entry;
}
- private GenericType getKVGenericType(GenericType genericType) {
- GenericType mapGenericType = getCachedMapGenericType(genericType);
- if (mapGenericType == null) {
- TypeRef<?> typeRef = genericType.getTypeRef();
- if (!MAP_TYPE.isSupertypeOf(typeRef)) {
- mapGenericType = GenericType.build(TypeUtils.mapOf(Object.class,
Object.class));
- } else {
- Tuple2<TypeRef<?>, TypeRef<?>> mapKeyValueType =
TypeUtils.getMapKeyValueType(typeRef);
- mapGenericType = GenericType.build(TypeUtils.mapOf(mapKeyValueType.f0,
mapKeyValueType.f1));
- }
- cacheMapGenericType(genericType, mapGenericType);
- }
- return mapGenericType;
- }
-
- private GenericType getCachedMapGenericType(GenericType genericType) {
- MapTypeCache state = mapTypeCache;
- if (state == null) {
- return null;
- }
- if (genericType == state.partialGenericKVTypeKey0) {
- return state.partialGenericKVTypeValue0;
- }
- if (genericType == state.partialGenericKVTypeKey1) {
- return state.partialGenericKVTypeValue1;
- }
- return null;
- }
-
- private void cacheMapGenericType(GenericType genericType, GenericType
mapGenericType) {
- MapTypeCache state = mapTypeCache();
- state.partialGenericKVTypeKey1 = state.partialGenericKVTypeKey0;
- state.partialGenericKVTypeValue1 = state.partialGenericKVTypeValue0;
- state.partialGenericKVTypeKey0 = genericType;
- state.partialGenericKVTypeValue0 = mapGenericType;
- }
-
protected <K, V> void copyEntry(CopyContext copyContext, Map<K, V>
originMap, Map<K, V> newMap) {
TypeResolver classResolver = typeResolver;
MapTypeCache state = mapTypeCache();
@@ -650,7 +596,7 @@ public abstract class MapLikeSerializer<T> extends
Serializer<T> {
break;
}
GenericType genericType =
generics.nextGenericType(readContext.getDepth());
- if (genericType == null) {
+ if (genericType == null || genericType.getTypeParametersCount() < 2) {
sizeAndHeader = readJavaChunk(readContext, map, size, chunkHeader,
null, null);
} else {
sizeAndHeader =
@@ -755,9 +701,7 @@ public abstract class MapLikeSerializer<T> extends
Serializer<T> {
ReadContext readContext, boolean trackRef, boolean isKey) {
Generics generics = readContext.getGenerics();
GenericType genericType = generics.nextGenericType(readContext.getDepth());
- if (genericType.getTypeParametersCount() < 2) {
- genericType = getKVGenericType(genericType);
- }
+ Preconditions.checkState(genericType != null &&
genericType.getTypeParametersCount() >= 2);
GenericType type = isKey ? genericType.getTypeParameter0() :
genericType.getTypeParameter1();
generics.pushGenericType(type, readContext.getDepth());
Serializer<?> serializer = type.getSerializer(typeResolver);
@@ -858,12 +802,6 @@ public abstract class MapLikeSerializer<T> extends
Serializer<T> {
int chunkHeader) {
MemoryBuffer buffer = readContext.getBuffer();
MapTypeCache state = mapTypeCache();
- // type parameters count for `Map field` will be 0;
- // type parameters count for `SubMap<V> field` which SubMap is
- // `SubMap<V> implements Map<String, V>` will be 1;
- if (genericType.getTypeParametersCount() < 2) {
- genericType = getKVGenericType(genericType);
- }
GenericType keyGenericType = genericType.getTypeParameter0();
GenericType valueGenericType = genericType.getTypeParameter1();
// noinspection Duplicates
diff --git a/java/fory-core/src/main/java/org/apache/fory/type/ScalaTypes.java
b/java/fory-core/src/main/java/org/apache/fory/type/ScalaTypes.java
index 7c814d6ec..ea6efd366 100644
--- a/java/fory-core/src/main/java/org/apache/fory/type/ScalaTypes.java
+++ b/java/fory-core/src/main/java/org/apache/fory/type/ScalaTypes.java
@@ -21,6 +21,7 @@ package org.apache.fory.type;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
+import org.apache.fory.annotation.Internal;
import org.apache.fory.collection.Tuple2;
import org.apache.fory.reflect.ReflectionUtils;
import org.apache.fory.reflect.TypeRef;
@@ -92,7 +93,8 @@ public class ScalaTypes {
.resolveType(getScalaNextReturnType());
}
- private static Type getScalaIteratorReturnType() {
+ @Internal
+ public static Type getScalaIteratorReturnType() {
if (SCALA_ITERATOR_RETURN_TYPE == null) {
try {
SCALA_ITERATOR_RETURN_TYPE =
@@ -104,7 +106,8 @@ public class ScalaTypes {
return SCALA_ITERATOR_RETURN_TYPE;
}
- private static Type getScalaNextReturnType() {
+ @Internal
+ public static Type getScalaNextReturnType() {
if (SCALA_NEXT_RETURN_TYPE == null) {
Class<?> scalaIteratorType =
ReflectionUtils.loadClass("scala.collection.Iterator");
try {
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 ab6adc373..91405ce19 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
@@ -65,7 +65,6 @@ import java.util.OptionalLong;
import java.util.Set;
import java.util.TimeZone;
import java.util.WeakHashMap;
-import java.util.stream.Collectors;
import org.apache.fory.annotation.Ref;
import org.apache.fory.collection.BFloat16List;
import org.apache.fory.collection.BoolList;
@@ -574,11 +573,10 @@ public class TypeUtils {
public static TypeRef<?> getElementType(TypeRef<?> typeRef) {
if (typeRef.hasExplicitTypeArguments()) {
List<TypeRef<?>> typeArguments = typeRef.getTypeArguments();
- if (typeArguments.size() == 1) {
- Class<?> rawType = getRawType(typeRef);
- if (Iterable.class.isAssignableFrom(rawType) ||
isScalaCollectionClass(rawType)) {
- return typeArguments.get(0);
- }
+ Class<?> rawType = getRawType(typeRef);
+ if (typeArguments.size() == 1
+ && (Iterable.class.isAssignableFrom(rawType) ||
isScalaIterableClass(rawType))) {
+ return typeArguments.get(0);
}
}
Type type = typeRef.getType();
@@ -593,8 +591,7 @@ public class TypeUtils {
}
}
}
- if (ScalaTypes.SCALA_AVAILABLE
- && typeRef.getType().getTypeName().startsWith("scala.collection")) {
+ if (isScalaIterableClass(typeRef.getRawType())) {
return ScalaTypes.getElementType(typeRef);
}
TypeRef<?> supertype = ((TypeRef<? extends Iterable<?>>)
typeRef).getSupertype(Iterable.class);
@@ -611,12 +608,10 @@ public class TypeUtils {
public static Tuple2<TypeRef<?>, TypeRef<?>> getMapKeyValueType(TypeRef<?>
typeRef) {
if (typeRef.hasExplicitTypeArguments()) {
List<TypeRef<?>> typeArguments = typeRef.getTypeArguments();
- if (typeArguments.size() == 2) {
- Class<?> rawType = getRawType(typeRef);
- if ((Map.class.isAssignableFrom(rawType) ||
isScalaCollectionClass(rawType))
- && rawType.getTypeParameters().length == 2) {
- return Tuple2.of(typeArguments.get(0), typeArguments.get(1));
- }
+ Class<?> rawType = getRawType(typeRef);
+ if (typeArguments.size() == 2
+ && (Map.class.isAssignableFrom(rawType) ||
isScalaMapClass(rawType))) {
+ return Tuple2.of(typeArguments.get(0), typeArguments.get(1));
}
}
Type type = typeRef.getType();
@@ -632,8 +627,7 @@ public class TypeUtils {
}
}
}
- if (ScalaTypes.SCALA_AVAILABLE
- && typeRef.getType().getTypeName().startsWith("scala.collection")) {
+ if (isScalaMapClass(typeRef.getRawType())) {
return ScalaTypes.getMapKeyValueType(typeRef);
}
@SuppressWarnings("unchecked")
@@ -643,8 +637,16 @@ public class TypeUtils {
return Tuple2.of(keyType, valueType);
}
- private static boolean isScalaCollectionClass(Class<?> rawType) {
- return ScalaTypes.SCALA_AVAILABLE &&
rawType.getName().startsWith("scala.collection");
+ private static boolean isScalaMapClass(Class<?> rawType) {
+ return ScalaTypes.SCALA_AVAILABLE
+ && rawType.getName().startsWith("scala.collection")
+ && ScalaTypes.getScalaMapType().isAssignableFrom(rawType);
+ }
+
+ private static boolean isScalaIterableClass(Class<?> rawType) {
+ return ScalaTypes.SCALA_AVAILABLE
+ && rawType.getName().startsWith("scala.collection")
+ && ScalaTypes.getScalaIterableType().isAssignableFrom(rawType);
}
public static void applyRefTrackingOverride(
@@ -656,14 +658,9 @@ public class TypeUtils {
if (ref != null) {
genericType.setTrackingRefOverride(ref.enable() && globalTrackingRef);
}
- Object[] typeUseArgs = TypeUseMetadata.typeUseArguments(typeUse);
- if (typeUseArgs != null) {
- GenericType[] typeParameters = genericType.getTypeParameters();
- int len = Math.min(typeUseArgs.length, typeParameters.length);
- for (int i = 0; i < len; i++) {
- applyRefTrackingOverride(typeParameters[i], typeUseArgs[i],
globalTrackingRef);
- }
- }
+ // Child type-use metadata is already folded into TypeRef explicit
arguments. Replaying JVM
+ // type-use children here is unsafe because collection/map GenericType
parameters are normalized
+ // to element or key/value, not necessarily the raw declared type-argument
order.
}
public static TypeRef<?> getFieldTypeRef(Field field) {
@@ -1050,17 +1047,7 @@ public class TypeUtils {
/** Returns generic type arguments of <code>typeToken</code>. */
public static List<TypeRef<?>> getTypeArguments(TypeRef<?> typeRef) {
- if (typeRef.hasExplicitTypeArguments()) {
- return typeRef.getTypeArguments();
- }
- if (typeRef.getType() instanceof ParameterizedType) {
- ParameterizedType parameterizedType = (ParameterizedType)
typeRef.getType();
- return Arrays.stream(parameterizedType.getActualTypeArguments())
- .map(TypeRef::of)
- .collect(Collectors.toList());
- } else {
- return new ArrayList<>();
- }
+ return typeRef.getTypeArguments();
}
/**
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 a9e17babc..ef85f4eef 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
@@ -25,6 +25,9 @@ import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.WildcardType;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -39,9 +42,12 @@ import org.apache.fory.annotation.Ref;
import org.apache.fory.collection.Tuple2;
import org.apache.fory.config.Int32Encoding;
import org.apache.fory.meta.TypeExtMeta;
+import org.apache.fory.type.GenericType;
+import org.apache.fory.type.ScalaTypes;
import org.apache.fory.type.TypeUtils;
import org.apache.fory.type.Types;
import org.testng.Assert;
+import org.testng.SkipException;
import org.testng.annotations.Test;
public class TypeRefTest extends ForyTestBase {
@@ -96,6 +102,143 @@ public class TypeRefTest extends ForyTestBase {
serDeCheck(fory, new MyClass());
}
+ static class MultiParamList<A, E> extends ArrayList<E> {}
+
+ static class MultiParamMap<A, K, V> extends HashMap<K, V> {}
+
+ static class StringKeyMap<V> extends HashMap<String, V> {}
+
+ static class ArrayElementList<A, E> extends ArrayList<E[]> {}
+
+ static class ArrayValueMap<A, K, V> extends HashMap<K, V[]> {}
+
+ static class TypeUseItem {}
+
+ static class FixedElementList<A> extends ArrayList<TypeUseItem> {}
+
+ static class ContainerTypeUseStruct {
+ MultiParamList<String, List<TypeUseItem>> nestedItems;
+ FixedElementList<@Ref(enable = false) TypeUseItem> fixedItems;
+ }
+
+ @Test
+ public void testCustomContainerTypeRefNormalization() {
+ TypeRef<?> listType = new TypeRef<MultiParamList<String, Integer>>() {};
+ Assert.assertEquals(listType.getTypeArguments().size(), 1);
+ Assert.assertEquals(listType.getTypeArguments().get(0),
TypeRef.of(Integer.class));
+ Assert.assertEquals(TypeUtils.getElementType(listType),
TypeRef.of(Integer.class));
+
+ GenericType listGenericType = GenericType.build(listType);
+ Assert.assertEquals(listGenericType.getTypeParametersCount(), 1);
+ Assert.assertEquals(listGenericType.getTypeParameter0().getCls(),
Integer.class);
+
+ TypeRef<?> mapType = new TypeRef<MultiParamMap<String, Long, Integer>>()
{};
+ Assert.assertEquals(mapType.getTypeArguments().size(), 2);
+ Assert.assertEquals(mapType.getTypeArguments().get(0),
TypeRef.of(Long.class));
+ Assert.assertEquals(mapType.getTypeArguments().get(1),
TypeRef.of(Integer.class));
+
+ Tuple2<TypeRef<?>, TypeRef<?>> keyValueType =
TypeUtils.getMapKeyValueType(mapType);
+ Assert.assertEquals(keyValueType.f0, TypeRef.of(Long.class));
+ Assert.assertEquals(keyValueType.f1, TypeRef.of(Integer.class));
+
+ GenericType mapGenericType = GenericType.build(mapType);
+ Assert.assertEquals(mapGenericType.getTypeParametersCount(), 2);
+ Assert.assertEquals(mapGenericType.getTypeParameter0().getCls(),
Long.class);
+ Assert.assertEquals(mapGenericType.getTypeParameter1().getCls(),
Integer.class);
+
+ TypeRef<?> fixedKeyMapType = new TypeRef<StringKeyMap<List<Integer>>>() {};
+ Assert.assertEquals(fixedKeyMapType.getTypeArguments().size(), 2);
+ Assert.assertEquals(fixedKeyMapType.getTypeArguments().get(0),
TypeRef.of(String.class));
+ Assert.assertEquals(fixedKeyMapType.getTypeArguments().get(1), new
TypeRef<List<Integer>>() {});
+
+ GenericType fixedKeyMapGenericType = GenericType.build(fixedKeyMapType);
+ Assert.assertEquals(fixedKeyMapGenericType.getTypeParametersCount(), 2);
+ Assert.assertEquals(fixedKeyMapGenericType.getTypeParameter0().getCls(),
String.class);
+ Assert.assertEquals(fixedKeyMapGenericType.getTypeParameter1().getCls(),
List.class);
+ }
+
+ @Test
+ public void testCustomContainerArrayNormalization() {
+ TypeRef<?> elementType =
+ TypeUtils.getElementType(new TypeRef<ArrayElementList<String,
Integer>>() {});
+ Assert.assertEquals(elementType.getRawType(), Integer[].class);
+ Assert.assertEquals(elementType.getComponentType(),
TypeRef.of(Integer.class));
+
+ Tuple2<TypeRef<?>, TypeRef<?>> keyValueType =
+ TypeUtils.getMapKeyValueType(new TypeRef<ArrayValueMap<String, Long,
Integer>>() {});
+ Assert.assertEquals(keyValueType.f0, TypeRef.of(Long.class));
+ Assert.assertEquals(keyValueType.f1.getRawType(), Integer[].class);
+ Assert.assertEquals(keyValueType.f1.getComponentType(),
TypeRef.of(Integer.class));
+ }
+
+ @Test
+ public void testCustomContainerTypeUseMetadata() throws Exception {
+ Field nestedItemsField =
ContainerTypeUseStruct.class.getDeclaredField("nestedItems");
+ TypeRef<?> nestedItemsType =
TypeRef.ofTypeUse(nestedItemsField.getAnnotatedType());
+ Assert.assertEquals(nestedItemsType.getTypeArguments().size(), 1);
+ TypeRef<?> nestedListType = nestedItemsType.getTypeArguments().get(0);
+ Assert.assertEquals(nestedListType.getRawType(), List.class);
+ Assert.assertEquals(nestedListType.getTypeArguments().get(0),
TypeRef.of(TypeUseItem.class));
+
+ TypeExtMeta elementMeta = TypeExtMeta.of(Types.UNKNOWN, true, false);
+ TypeRef<?> refItemsType =
+ TypeRef.of(
+ new TypeRef.ParameterizedTypeImpl(
+ null, MultiParamList.class, new Type[] {String.class,
TypeUseItem.class}),
+ null,
+ Arrays.asList(TypeRef.of(String.class),
TypeRef.of(TypeUseItem.class, elementMeta)),
+ null);
+ Assert.assertEquals(refItemsType.getTypeArguments().size(), 1);
+
Assert.assertEquals(refItemsType.getTypeArguments().get(0).getTypeExtMeta(),
elementMeta);
+
+ TypeExtMeta keyMeta = TypeExtMeta.of(Types.UNKNOWN, true, false);
+ TypeExtMeta valueMeta = TypeExtMeta.of(Types.UNKNOWN, true, true);
+ TypeRef<?> refMapType =
+ TypeRef.of(
+ new TypeRef.ParameterizedTypeImpl(
+ null,
+ MultiParamMap.class,
+ new Type[] {String.class, TypeUseItem.class,
TypeUseItem.class}),
+ null,
+ Arrays.asList(
+ TypeRef.of(String.class),
+ TypeRef.of(TypeUseItem.class, keyMeta),
+ TypeRef.of(TypeUseItem.class, valueMeta)),
+ null);
+ Assert.assertEquals(refMapType.getTypeArguments().size(), 2);
+ Assert.assertEquals(refMapType.getTypeArguments().get(0).getTypeExtMeta(),
keyMeta);
+ Assert.assertEquals(refMapType.getTypeArguments().get(1).getTypeExtMeta(),
valueMeta);
+
+ Field fixedItemsField =
ContainerTypeUseStruct.class.getDeclaredField("fixedItems");
+ TypeRef<?> fixedItemsType =
TypeRef.ofTypeUse(fixedItemsField.getAnnotatedType());
+ TypeRef<?> fixedElementType = TypeUtils.getElementType(fixedItemsType);
+ Assert.assertEquals(fixedElementType, TypeRef.of(TypeUseItem.class));
+ Assert.assertFalse(fixedElementType.hasTypeExtMeta());
+ }
+
+ @Test
+ public void testScalaContainerTypeRefNormalization() throws Exception {
+ if (!ScalaTypes.SCALA_AVAILABLE) {
+ throw new SkipException("Scala is not available on the Java test
classpath");
+ }
+ Class<?> listClass = Class.forName("scala.collection.immutable.List");
+ TypeRef<?> listType =
+ TypeRef.of(new TypeRef.ParameterizedTypeImpl(null, listClass, new
Type[] {String.class}));
+ Assert.assertEquals(listType.getTypeArguments().size(), 1);
+ Assert.assertEquals(listType.getTypeArguments().get(0),
TypeRef.of(String.class));
+ Assert.assertEquals(TypeUtils.getElementType(listType),
TypeRef.of(String.class));
+
+ Class<?> mapClass = Class.forName("scala.collection.immutable.Map");
+ TypeRef<?> mapType =
+ TypeRef.of(
+ new TypeRef.ParameterizedTypeImpl(
+ null, mapClass, new Type[] {String.class, Integer.class}));
+ Tuple2<TypeRef<?>, TypeRef<?>> keyValueType =
TypeUtils.getMapKeyValueType(mapType);
+ Assert.assertEquals(mapType.getTypeArguments().size(), 2);
+ Assert.assertEquals(keyValueType.f0, TypeRef.of(String.class));
+ Assert.assertEquals(keyValueType.f1, TypeRef.of(Integer.class));
+ }
+
static class TypeUseMetadataStruct {
@Nullable String nickname;
diff --git
a/java/fory-core/src/test/java/org/apache/fory/serializer/collection/CollectionSerializersTest.java
b/java/fory-core/src/test/java/org/apache/fory/serializer/collection/CollectionSerializersTest.java
index ebff4537e..ff4261652 100644
---
a/java/fory-core/src/test/java/org/apache/fory/serializer/collection/CollectionSerializersTest.java
+++
b/java/fory-core/src/test/java/org/apache/fory/serializer/collection/CollectionSerializersTest.java
@@ -34,6 +34,7 @@ import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.io.Serializable;
import java.lang.reflect.Field;
+import java.lang.reflect.ParameterizedType;
import java.util.AbstractCollection;
import java.util.ArrayDeque;
import java.util.ArrayList;
@@ -70,18 +71,26 @@ import lombok.Data;
import lombok.EqualsAndHashCode;
import org.apache.fory.Fory;
import org.apache.fory.ForyTestBase;
+import org.apache.fory.annotation.Ref;
+import org.apache.fory.config.CompatibleMode;
+import org.apache.fory.config.Int64Encoding;
+import org.apache.fory.config.Language;
import org.apache.fory.context.ReadContext;
import org.apache.fory.exception.DeserializationException;
import org.apache.fory.exception.SerializationException;
import org.apache.fory.memory.MemoryBuffer;
import org.apache.fory.memory.MemoryUtils;
+import org.apache.fory.meta.FieldTypes;
+import org.apache.fory.platform.JdkVersion;
import org.apache.fory.reflect.FieldAccessor;
import org.apache.fory.reflect.TypeRef;
import org.apache.fory.resolver.TypeResolver;
import
org.apache.fory.serializer.collection.CollectionSerializers.JDKCompatibleCollectionSerializer;
import org.apache.fory.test.bean.Cyclic;
import org.apache.fory.type.GenericType;
+import org.apache.fory.type.Types;
import org.testng.Assert;
+import org.testng.SkipException;
import org.testng.annotations.Test;
import org.testng.collections.Maps;
@@ -254,6 +263,200 @@ public class CollectionSerializersTest extends
ForyTestBase {
assertThrowsCause(RuntimeException.class, () -> fory.deserialize(bytes2));
}
+ public static class MultiParamList<A, E> extends ArrayList<E> {
+ private A metadata;
+
+ public MultiParamList() {}
+
+ public MultiParamList(A metadata) {
+ this.metadata = metadata;
+ }
+
+ public A getMetadata() {
+ return metadata;
+ }
+
+ public void setMetadata(A metadata) {
+ this.metadata = metadata;
+ }
+ }
+
+ public static class MultiParamListHolder {
+ private String name;
+ private MultiParamList<String, Integer> numbers;
+
+ public MultiParamListHolder() {}
+
+ public MultiParamListHolder(String name, MultiParamList<String, Integer>
numbers) {
+ this.name = name;
+ this.numbers = numbers;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public MultiParamList<String, Integer> getNumbers() {
+ return numbers;
+ }
+ }
+
+ public static class CollectionRefItem {
+ public int id;
+ }
+
+ public static class MultiParamListRefHolder {
+ private MultiParamList<String, @Ref(enable = false) CollectionRefItem>
noRefItems;
+ private MultiParamList<String, @Ref(enable = true) CollectionRefItem>
refItems;
+
+ public MultiParamListRefHolder() {}
+ }
+
+ public static class NestedMultiParamListHolder {
+ private MultiParamList<String, List<@Ref(enable = false)
CollectionRefItem>> nestedItems;
+
+ public NestedMultiParamListHolder() {}
+ }
+
+ public static class FixedElementList<A> extends ArrayList<CollectionRefItem>
{
+ private A metadata;
+
+ public FixedElementList() {}
+
+ public FixedElementList(A metadata) {
+ this.metadata = metadata;
+ }
+ }
+
+ public static class FixedElementListHolder {
+ private FixedElementList<@Ref(enable = false) CollectionRefItem> items;
+
+ public FixedElementListHolder() {}
+ }
+
+ @Test(dataProvider = "enableCodegen")
+ public void testMultiParamCollectionRoundTrip(boolean enableCodegen) {
+ MultiParamList<String, Integer> list = new MultiParamList<>("my-metadata");
+ list.add(1);
+ list.add(2);
+ list.add(3);
+
+ MultiParamListHolder holder = new MultiParamListHolder("test-container",
list);
+ Fory fory = collectionGenericFory(enableCodegen);
+ MultiParamListHolder cloned = (MultiParamListHolder)
fory.deserialize(fory.serialize(holder));
+
+ Assert.assertEquals(cloned.getName(), "test-container");
+ Assert.assertNotNull(cloned.getNumbers());
+ Assert.assertEquals(cloned.getNumbers().getMetadata(), "my-metadata");
+ Assert.assertEquals(cloned.getNumbers().size(), 3);
+ Assert.assertEquals(cloned.getNumbers().get(0), Integer.valueOf(1));
+ Assert.assertEquals(cloned.getNumbers().get(1), Integer.valueOf(2));
+ Assert.assertEquals(cloned.getNumbers().get(2), Integer.valueOf(3));
+ }
+
+ @Test(dataProvider = "enableCodegen")
+ public void testMultiParamCollectionRefOverride(boolean enableCodegen) {
+ skipMemberGenericTypeUseOnJdk11();
+ CollectionRefItem noRefItem = new CollectionRefItem();
+ noRefItem.id = 1;
+ CollectionRefItem refItem = new CollectionRefItem();
+ refItem.id = 2;
+
+ MultiParamListRefHolder holder = new MultiParamListRefHolder();
+ holder.noRefItems = new MultiParamList<>("no-ref");
+ holder.noRefItems.add(noRefItem);
+ holder.noRefItems.add(noRefItem);
+ holder.refItems = new MultiParamList<>("ref");
+ holder.refItems.add(refItem);
+ holder.refItems.add(refItem);
+
+ Fory fory = collectionGenericFory(enableCodegen);
+ MultiParamListRefHolder cloned =
+ (MultiParamListRefHolder) fory.deserialize(fory.serialize(holder));
+ Assert.assertNotSame(cloned.noRefItems.get(0), cloned.noRefItems.get(1));
+ Assert.assertSame(cloned.refItems.get(0), cloned.refItems.get(1));
+ }
+
+ @Test(dataProvider = "enableCodegen")
+ public void testNestedMultiParamCollectionRef(boolean enableCodegen) {
+ skipMemberGenericTypeUseOnJdk11();
+ CollectionRefItem item = new CollectionRefItem();
+ item.id = 1;
+
+ NestedMultiParamListHolder holder = new NestedMultiParamListHolder();
+ holder.nestedItems = new MultiParamList<>("nested");
+ List<CollectionRefItem> inner = new ArrayList<>();
+ inner.add(item);
+ inner.add(item);
+ holder.nestedItems.add(inner);
+
+ Fory fory = collectionGenericFory(enableCodegen);
+ NestedMultiParamListHolder cloned =
+ (NestedMultiParamListHolder) fory.deserialize(fory.serialize(holder));
+ Assert.assertNotSame(cloned.nestedItems.get(0).get(0),
cloned.nestedItems.get(0).get(1));
+ }
+
+ private static void skipMemberGenericTypeUseOnJdk11() {
+ if (JdkVersion.MAJOR_VERSION <= 11) {
+ throw new SkipException(
+ "JDK 11 and earlier do not expose member-class generic field
type-use metadata");
+ }
+ }
+
+ @Test(dataProvider = "enableCodegen")
+ public void testFixedElementCollectionRefMeta(boolean enableCodegen) {
+ CollectionRefItem element = new CollectionRefItem();
+ element.id = 1;
+ CollectionRefItem metadata = new CollectionRefItem();
+ metadata.id = 2;
+
+ FixedElementListHolder holder = new FixedElementListHolder();
+ holder.items = new FixedElementList<>(metadata);
+ holder.items.add(element);
+ holder.items.add(element);
+
+ Fory fory = collectionGenericFory(enableCodegen);
+ FixedElementListHolder cloned =
+ (FixedElementListHolder) fory.deserialize(fory.serialize(holder));
+ Assert.assertSame(cloned.items.get(0), cloned.items.get(1));
+ }
+
+ @Test
+ public void testCollectionFieldTypeKeepsDeclaredType() {
+ Fory fory =
builder().withXlang(false).requireClassRegistration(false).build();
+ TypeRef<?> declared = new TypeRef<List<Integer>>() {};
+ FieldTypes.CollectionFieldType fieldType =
+ new FieldTypes.CollectionFieldType(
+ Types.LIST,
+ true,
+ true,
+ new FieldTypes.RegisteredFieldType(true, false, Types.INT32, -1));
+
+ TypeRef<?> rebuilt = fieldType.toTypeToken(fory.getTypeResolver(),
declared);
+
+ Assert.assertTrue(rebuilt.getType() instanceof ParameterizedType);
+ Assert.assertEquals(rebuilt.getRawType(), List.class);
+ Assert.assertEquals(rebuilt.getTypeArguments().size(), 1);
+ Assert.assertEquals(rebuilt.getTypeExtMeta().typeId(), Types.LIST);
+ Assert.assertTrue(rebuilt.getTypeExtMeta().nullable());
+ Assert.assertTrue(rebuilt.getTypeExtMeta().trackingRef());
+ }
+
+ private static Fory collectionGenericFory(boolean enableCodegen) {
+ return Fory.builder()
+ .withLanguage(Language.JAVA)
+ .requireClassRegistration(false)
+ .withRefTracking(true)
+ .withCompatibleMode(CompatibleMode.COMPATIBLE)
+ .withAsyncCompilation(false)
+ .withIntCompressed(true)
+ .withCodegen(enableCodegen)
+ .withLongCompressed(Int64Encoding.VARINT)
+ .withIntArrayCompressed(true)
+ .withLongArrayCompressed(true)
+ .build();
+ }
+
@Test(dataProvider = "referenceTrackingConfig")
public void testSortedSet(boolean referenceTrackingConfig) {
Fory fory =
diff --git
a/java/fory-core/src/test/java/org/apache/fory/serializer/collection/MapSerializersTest.java
b/java/fory-core/src/test/java/org/apache/fory/serializer/collection/MapSerializersTest.java
index bb5f721c5..ba2303b27 100644
---
a/java/fory-core/src/test/java/org/apache/fory/serializer/collection/MapSerializersTest.java
+++
b/java/fory-core/src/test/java/org/apache/fory/serializer/collection/MapSerializersTest.java
@@ -31,6 +31,7 @@ import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.io.Serializable;
+import java.lang.reflect.ParameterizedType;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Arrays;
@@ -58,9 +59,12 @@ import org.apache.fory.ForyTestBase;
import org.apache.fory.annotation.Ref;
import org.apache.fory.collection.LazyMap;
import org.apache.fory.collection.MapEntry;
+import org.apache.fory.config.CompatibleMode;
import org.apache.fory.exception.SerializationException;
import org.apache.fory.memory.MemoryBuffer;
import org.apache.fory.memory.MemoryUtils;
+import org.apache.fory.meta.FieldTypes;
+import org.apache.fory.platform.JdkVersion;
import org.apache.fory.reflect.TypeRef;
import org.apache.fory.serializer.Serializer;
import
org.apache.fory.serializer.collection.CollectionSerializersTest.TestEnum;
@@ -68,7 +72,9 @@ import org.apache.fory.test.bean.BeanB;
import org.apache.fory.test.bean.Cyclic;
import org.apache.fory.test.bean.MapFields;
import org.apache.fory.type.GenericType;
+import org.apache.fory.type.Types;
import org.testng.Assert;
+import org.testng.SkipException;
import org.testng.annotations.Test;
public class MapSerializersTest extends ForyTestBase {
@@ -1160,6 +1166,153 @@ public class MapSerializersTest extends ForyTestBase {
new HashMap<>(mapOf(new HashMap<>(mapOf(1, list)), list))));
}
+ public static class MultiParamMap<A, K, V> extends HashMap<K, V> {}
+
+ @Data
+ public static class MultiParamMapHolder {
+ public MultiParamMap<String, Integer, Long> map;
+ }
+
+ @Data
+ public static class MultiParamMapRefHolder {
+ public MultiParamMap<String, Integer, @Ref(enable = false) MapRefItem>
noRefMap;
+ public MultiParamMap<String, Integer, @Ref(enable = true) MapRefItem>
refMap;
+ }
+
+ @Data
+ public static class NestedMultiParamMapRefHolder {
+ public MultiParamMap<String, Integer, List<@Ref(enable = false)
MapRefItem>> nestedMap;
+ }
+
+ public static class FixedValueMap<K, A> extends HashMap<K, MapRefItem> {}
+
+ @Data
+ public static class FixedValueMapRefHolder {
+ public FixedValueMap<Integer, @Ref(enable = false) MapRefItem> map;
+ }
+
+ @Test(dataProvider = "enableCodegen")
+ public void testMultiParamMapGeneric(boolean enableCodegen) {
+ Fory fory =
+ builder()
+ .withXlang(false)
+ .withCodegen(enableCodegen)
+ .requireClassRegistration(false)
+ .build();
+ MultiParamMapHolder holder = new MultiParamMapHolder();
+ holder.map = new MultiParamMap<>();
+ holder.map.put(1, 2L);
+ holder.map.put(3, 4L);
+ serDeCheck(fory, holder);
+ }
+
+ @Test
+ public void testMapFieldTypeKeepsDeclaredType() {
+ Fory fory =
builder().withXlang(false).requireClassRegistration(false).build();
+ TypeRef<?> declared = new TypeRef<Map<String, Integer>>() {};
+ FieldTypes.MapFieldType fieldType =
+ new FieldTypes.MapFieldType(
+ Types.MAP,
+ true,
+ true,
+ new FieldTypes.RegisteredFieldType(true, false, Types.STRING, -1),
+ new FieldTypes.RegisteredFieldType(true, false, Types.INT32, -1));
+
+ TypeRef<?> rebuilt = fieldType.toTypeToken(fory.getTypeResolver(),
declared);
+
+ Assert.assertTrue(rebuilt.getType() instanceof ParameterizedType);
+ Assert.assertEquals(rebuilt.getRawType(), Map.class);
+ Assert.assertEquals(rebuilt.getTypeArguments().size(), 2);
+ Assert.assertEquals(rebuilt.getTypeExtMeta().typeId(), Types.MAP);
+ Assert.assertTrue(rebuilt.getTypeExtMeta().nullable());
+ Assert.assertTrue(rebuilt.getTypeExtMeta().trackingRef());
+ }
+
+ @Test(dataProvider = "enableCodegen")
+ public void testMultiParamMapRefOverride(boolean enableCodegen) {
+ skipMemberGenericTypeUseOnJdk11();
+ Fory fory =
+ builder()
+ .withXlang(false)
+ .withRefTracking(true)
+ .withCodegen(enableCodegen)
+ .withCompatibleMode(CompatibleMode.COMPATIBLE)
+ .requireClassRegistration(false)
+ .build();
+ MapRefItem noRefItem = new MapRefItem();
+ noRefItem.id = 1;
+ noRefItem.name = "no-ref";
+ MapRefItem refItem = new MapRefItem();
+ refItem.id = 2;
+ refItem.name = "ref";
+ MultiParamMapRefHolder holder = new MultiParamMapRefHolder();
+ holder.noRefMap = new MultiParamMap<>();
+ holder.noRefMap.put(1, noRefItem);
+ holder.noRefMap.put(2, noRefItem);
+ holder.refMap = new MultiParamMap<>();
+ holder.refMap.put(1, refItem);
+ holder.refMap.put(2, refItem);
+
+ MultiParamMapRefHolder cloned = serDe(fory, holder);
+ Assert.assertNotSame(cloned.noRefMap.get(1), cloned.noRefMap.get(2));
+ Assert.assertSame(cloned.refMap.get(1), cloned.refMap.get(2));
+ }
+
+ @Test(dataProvider = "enableCodegen")
+ public void testNestedMultiParamMapRefOverride(boolean enableCodegen) {
+ skipMemberGenericTypeUseOnJdk11();
+ Fory fory =
+ builder()
+ .withXlang(false)
+ .withRefTracking(true)
+ .withCodegen(enableCodegen)
+ .withCompatibleMode(CompatibleMode.COMPATIBLE)
+ .requireClassRegistration(false)
+ .build();
+ MapRefItem item = new MapRefItem();
+ item.id = 1;
+ item.name = "nested";
+ List<MapRefItem> values = new ArrayList<>();
+ values.add(item);
+ values.add(item);
+
+ NestedMultiParamMapRefHolder holder = new NestedMultiParamMapRefHolder();
+ holder.nestedMap = new MultiParamMap<>();
+ holder.nestedMap.put(1, values);
+
+ NestedMultiParamMapRefHolder cloned = serDe(fory, holder);
+ Assert.assertNotSame(cloned.nestedMap.get(1).get(0),
cloned.nestedMap.get(1).get(1));
+ }
+
+ private static void skipMemberGenericTypeUseOnJdk11() {
+ if (JdkVersion.MAJOR_VERSION <= 11) {
+ throw new SkipException(
+ "JDK 11 and earlier do not expose member-class generic field
type-use metadata");
+ }
+ }
+
+ @Test(dataProvider = "enableCodegen")
+ public void testFixedValueMapIgnoresMetadataRef(boolean enableCodegen) {
+ Fory fory =
+ builder()
+ .withXlang(false)
+ .withRefTracking(true)
+ .withCodegen(enableCodegen)
+ .withCompatibleMode(CompatibleMode.COMPATIBLE)
+ .requireClassRegistration(false)
+ .build();
+ MapRefItem value = new MapRefItem();
+ value.id = 1;
+ value.name = "value";
+ FixedValueMapRefHolder holder = new FixedValueMapRefHolder();
+ holder.map = new FixedValueMap<>();
+ holder.map.put(1, value);
+ holder.map.put(2, value);
+
+ FixedValueMapRefHolder cloned = serDe(fory, holder);
+ Assert.assertSame(cloned.map.get(1), cloned.map.get(2));
+ }
+
public static class StringKeyMap<T> extends HashMap<String, T> {}
@Test
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]