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/fury.git
The following commit(s) were added to refs/heads/main by this push:
new 3dd49c91 fix(java): fix generics when write/read null map kv chunk
(#2261)
3dd49c91 is described below
commit 3dd49c91e743d2b7bf99fd16fa8ef9b70733d17c
Author: Shawn Yang <[email protected]>
AuthorDate: Thu May 29 11:38:14 2025 +0800
fix(java): fix generics when write/read null map kv chunk (#2261)
## What does this PR do?
1. Fix generics when write/read null map kv chunk
2. Fix some race condition in async jit mode
## Related issues
Closes #2247
#2257
#2253
## Does this PR introduce any user-facing change?
<!--
If any user-facing interface changes, please [open an
issue](https://github.com/apache/fury/issues/new/choose) describing the
need to do so and update the document if necessary.
-->
- [ ] Does this PR introduce any public API change?
- [ ] Does this PR introduce any binary protocol compatibility change?
## Benchmark
<!--
When the PR has an impact on performance (if you don't know whether the
PR will have an impact on performance, you can submit the PR first, and
if it will have impact on performance, the code reviewer will explain
it), be sure to attach a benchmark data here.
-->
---
.../fury/builder/BaseObjectCodecBuilder.java | 131 ++++++++++++------
.../java/org/apache/fury/builder/CodecBuilder.java | 2 +-
.../fury/builder/CompatibleCodecBuilder.java | 8 +-
.../fury/builder/MetaSharedCodecBuilder.java | 2 +-
.../apache/fury/builder/ObjectCodecBuilder.java | 9 +-
.../org/apache/fury/resolver/ClassResolver.java | 17 +++
.../collection/AbstractMapSerializer.java | 148 ++++++++++++++++++---
.../java/org/apache/fury/type/GenericType.java | 6 +-
.../serializer/collection/MapSerializersTest.java | 20 +++
.../DifferentPOJOCompatibleSerializerTest.java | 3 +-
...JOCompatibleSerializerWithRegistrationTest.java | 3 +-
11 files changed, 283 insertions(+), 66 deletions(-)
diff --git
a/java/fury-core/src/main/java/org/apache/fury/builder/BaseObjectCodecBuilder.java
b/java/fury-core/src/main/java/org/apache/fury/builder/BaseObjectCodecBuilder.java
index 7a011cf8..1f75a033 100644
---
a/java/fury-core/src/main/java/org/apache/fury/builder/BaseObjectCodecBuilder.java
+++
b/java/fury-core/src/main/java/org/apache/fury/builder/BaseObjectCodecBuilder.java
@@ -22,6 +22,7 @@ package org.apache.fury.builder;
import static org.apache.fury.codegen.CodeGenerator.getPackage;
import static org.apache.fury.codegen.Expression.Invoke.inlineInvoke;
import static org.apache.fury.codegen.Expression.Literal.ofInt;
+import static org.apache.fury.codegen.Expression.Literal.ofString;
import static org.apache.fury.codegen.Expression.Reference.fieldRef;
import static org.apache.fury.codegen.ExpressionOptimizer.invokeGenerated;
import static org.apache.fury.codegen.ExpressionUtils.add;
@@ -118,6 +119,7 @@ import org.apache.fury.serializer.StringSerializer;
import org.apache.fury.serializer.collection.AbstractCollectionSerializer;
import org.apache.fury.serializer.collection.AbstractMapSerializer;
import org.apache.fury.serializer.collection.CollectionFlags;
+import org.apache.fury.type.GenericType;
import org.apache.fury.type.TypeUtils;
import org.apache.fury.util.GraalvmSupport;
import org.apache.fury.util.Preconditions;
@@ -141,12 +143,12 @@ public abstract class BaseObjectCodecBuilder extends
CodecBuilder {
private static final TypeRef<?> COLLECTION_SERIALIZER_TYPE =
TypeRef.of(AbstractCollectionSerializer.class);
private static final TypeRef<?> MAP_SERIALIZER_TYPE =
TypeRef.of(AbstractMapSerializer.class);
+ private static final TypeRef<?> GENERIC_TYPE = TypeRef.of(GenericType.class);
protected final Reference refResolverRef;
protected final Reference classResolverRef =
fieldRef(CLASS_RESOLVER_NAME, CLASS_RESOLVER_TYPE_TOKEN);
protected final Fury fury;
- protected final ClassResolver classResolver;
protected final Reference stringSerializerRef;
private final Map<Class<?>, Reference> serializerMap = new HashMap<>();
private final Map<String, Object> sharedFieldMap = new HashMap<>();
@@ -157,7 +159,6 @@ public abstract class BaseObjectCodecBuilder extends
CodecBuilder {
public BaseObjectCodecBuilder(TypeRef<?> beanType, Fury fury, Class<?>
parentSerializerClass) {
super(new CodegenContext(), beanType);
this.fury = fury;
- this.classResolver = fury.getClassResolver();
this.parentSerializerClass = parentSerializerClass;
addCommonImports();
ctx.reserveName(REF_RESOLVER_NAME);
@@ -220,12 +221,12 @@ public abstract class BaseObjectCodecBuilder extends
CodecBuilder {
protected abstract String codecSuffix();
- protected <T> T visitFury(Function<Fury, T> function) {
+ protected <T> T fury(Function<Fury, T> function) {
return fury.getJITContext().asyncVisitFury(function);
}
private boolean needWriteRef(TypeRef<?> type) {
- return visitFury(fury -> fury.getClassResolver().needToWriteRef(type));
+ return fury(fury -> fury.getClassResolver().needToWriteRef(type));
}
@Override
@@ -483,19 +484,19 @@ public abstract class BaseObjectCodecBuilder extends
CodecBuilder {
}
protected boolean useCollectionSerialization(TypeRef<?> typeRef) {
- return visitFury(f ->
f.getClassResolver().isCollection(TypeUtils.getRawType(typeRef)));
+ return fury(f ->
f.getClassResolver().isCollection(TypeUtils.getRawType(typeRef)));
}
protected boolean useCollectionSerialization(Class<?> type) {
- return visitFury(f ->
f.getClassResolver().isCollection(TypeUtils.getRawType(type)));
+ return fury(f ->
f.getClassResolver().isCollection(TypeUtils.getRawType(type)));
}
protected boolean useMapSerialization(TypeRef<?> typeRef) {
- return visitFury(f ->
f.getClassResolver().isMap(TypeUtils.getRawType(typeRef)));
+ return fury(f ->
f.getClassResolver().isMap(TypeUtils.getRawType(typeRef)));
}
protected boolean useMapSerialization(Class<?> type) {
- return visitFury(f ->
f.getClassResolver().isMap(TypeUtils.getRawType(type)));
+ return fury(f -> f.getClassResolver().isMap(TypeUtils.getRawType(type)));
}
/**
@@ -539,7 +540,8 @@ public abstract class BaseObjectCodecBuilder extends
CodecBuilder {
classInfo,
inlineInvoke(classResolverRef, "getClassInfo",
classInfoTypeRef, clsExpr))));
}
- writeClassAndObject.add(classResolver.writeClassExpr(classResolverRef,
buffer, classInfo));
+ writeClassAndObject.add(
+ fury(f -> f.getClassResolver().writeClassExpr(classResolverRef,
buffer, classInfo)));
writeClassAndObject.add(
new Invoke(
invokeInline(classInfo, "getSerializer", getSerializerType(clz)),
@@ -562,7 +564,8 @@ public abstract class BaseObjectCodecBuilder extends
CodecBuilder {
new Assign(
classInfo,
inlineInvoke(classResolverRef, "getClassInfo",
classInfoTypeRef, clsExpr))));
- writeClassAction.add(classResolver.writeClassExpr(classResolverRef,
buffer, classInfo));
+ writeClassAction.add(
+ fury(f -> f.getClassResolver().writeClassExpr(classResolverRef,
buffer, classInfo)));
if (returnSerializer) {
writeClassAction.add(
invoke(classInfo, "getSerializer", "serializer",
getSerializerType(declaredClass)));
@@ -582,10 +585,10 @@ public abstract class BaseObjectCodecBuilder extends
CodecBuilder {
Class<? extends Serializer> serializerClass;
if (fury.isCrossLanguage()) {
// xlang will take all map/collection interface as monomorphic
- serializerClass = visitFury(f ->
f.getXtypeResolver().getSerializer(cls)).getClass();
+ serializerClass = fury(f ->
f.getXtypeResolver().getSerializer(cls)).getClass();
} else {
// potential recursive call for seq codec generation is handled in
`getSerializerClass`.
- serializerClass = visitFury(f ->
f.getClassResolver().getSerializerClass(cls));
+ serializerClass = fury(f ->
f.getClassResolver().getSerializerClass(cls));
}
Preconditions.checkNotNull(serializerClass, "Unsupported for class " +
cls);
if (!ReflectionUtils.isPublic(serializerClass)) {
@@ -655,6 +658,31 @@ public abstract class BaseObjectCodecBuilder extends
CodecBuilder {
}
}
+ private final Map<TypeRef<?>, String> namesForSharedGenericTypeFields = new
HashMap<>();
+
+ protected Expression getGenericTypeField(TypeRef<?> typeRef) {
+ // create a field name from generic type, so multiple call of same generic
type will reuse the
+ // same field.
+ String name =
+ namesForSharedGenericTypeFields.computeIfAbsent(
+ typeRef,
+ k ->
+ StringUtils.uncapitalize(typeRef.getRawType().getSimpleName())
+ + namesForSharedGenericTypeFields.size());
+
+ return getOrCreateField(
+ false,
+ GenericType.class,
+ name,
+ () ->
+ new Invoke(
+ classResolverRef,
+ "getGenericTypeInStruct",
+ GENERIC_TYPE,
+ beanClassExpr(),
+ ofString(typeRef.getType().getTypeName())));
+ }
+
/**
* The boolean value in tuple indicates whether the classinfo needs update.
*
@@ -743,9 +771,9 @@ public abstract class BaseObjectCodecBuilder extends
CodecBuilder {
}
protected TypeRef<?> getSerializerType(Class<?> objType) {
- if (classResolver.isCollection(objType)) {
+ if (fury(f -> f.getClassResolver().isCollection(objType))) {
return COLLECTION_SERIALIZER_TYPE;
- } else if (classResolver.isMap(objType)) {
+ } else if (fury(f -> f.getClassResolver().isMap(objType))) {
return MAP_SERIALIZER_TYPE;
}
return SERIALIZER_TYPE;
@@ -781,7 +809,8 @@ public abstract class BaseObjectCodecBuilder extends
CodecBuilder {
new Assign(
classInfo,
inlineInvoke(classResolverRef, "getClassInfo",
classInfoTypeRef, clsExpr))));
- writeClassAction.add(classResolver.writeClassExpr(classResolverRef,
buffer, classInfo));
+ writeClassAction.add(
+ fury(f -> f.getClassResolver().writeClassExpr(classResolverRef,
buffer, classInfo)));
writeClassAction.add(
new Return(invokeInline(classInfo, "getSerializer",
getSerializerType(typeRef))));
// Spit this into a separate method to avoid method too big to inline.
@@ -853,7 +882,7 @@ public abstract class BaseObjectCodecBuilder extends
CodecBuilder {
Literal notDeclTypeFlag = ofInt(CollectionFlags.NOT_DECL_ELEMENT_TYPE);
Expression isDeclType = neq(new BitAnd(flags, notDeclTypeFlag),
notDeclTypeFlag);
Expression elemSerializer; // make it in scope of `if(sameElementClass)`
- boolean maybeDecl = visitFury(f ->
f.getClassResolver().isSerializable(elemClass));
+ boolean maybeDecl = fury(f ->
f.getClassResolver().isSerializable(elemClass));
TypeRef<?> serializerType = getSerializerType(elementType);
if (maybeDecl) {
elemSerializer =
@@ -1085,7 +1114,8 @@ public abstract class BaseObjectCodecBuilder extends
CodecBuilder {
classInfo,
inlineInvoke(classResolverRef, "getClassInfo",
classInfoTypeRef, clsExpr))));
// Note: writeClassExpr is thread safe.
- writeClassAction.add(classResolver.writeClassExpr(classResolverRef,
buffer, classInfo));
+ writeClassAction.add(
+ fury(f -> f.getClassResolver().writeClassExpr(classResolverRef,
buffer, classInfo)));
writeClassAction.add(
new Return(invokeInline(classInfo, "getSerializer",
MAP_SERIALIZER_TYPE)));
// Spit this into a separate method to avoid method too big to inline.
@@ -1121,8 +1151,8 @@ public abstract class BaseObjectCodecBuilder extends
CodecBuilder {
boolean inline = keyMonomorphic && valueMonomorphic;
Class<?> keyTypeRawType = keyType.getRawType();
Class<?> valueTypeRawType = valueType.getRawType();
- boolean trackingKeyRef = visitFury(fury ->
fury.getClassResolver().needToWriteRef(keyType));
- boolean trackingValueRef = visitFury(fury ->
fury.getClassResolver().needToWriteRef(valueType));
+ boolean trackingKeyRef = fury(fury ->
fury.getClassResolver().needToWriteRef(keyType));
+ boolean trackingValueRef = fury(fury ->
fury.getClassResolver().needToWriteRef(valueType));
Tuple2<Expression, Expression> mapKVSerializer =
getMapKVSerializer(keyTypeRawType, valueTypeRawType);
Expression keySerializer = mapKVSerializer.f0;
@@ -1131,25 +1161,50 @@ public abstract class BaseObjectCodecBuilder extends
CodecBuilder {
new While(
neqNull(entry),
() -> {
- String method = "writeJavaNullChunk";
- if (keyMonomorphic && valueMonomorphic) {
+ boolean hasGenerics =
+ keyTypeRawType != Object.class || valueTypeRawType !=
Object.class;
+ String method = hasGenerics ? "writeJavaNullChunkGeneric" :
"writeJavaNullChunk";
+ GenericType keyGenericType =
+ fury(f -> f.getClassResolver().buildGenericType(keyType));
+ GenericType valueGenericType =
+ fury(f -> f.getClassResolver().buildGenericType(valueType));
+ if (keyGenericType.hasGenericParameters()
+ || valueGenericType.hasGenericParameters()) {
+ method = "writeJavaNullChunkGeneric";
+ } else if (keyMonomorphic && valueMonomorphic) {
if (!trackingKeyRef && !trackingValueRef) {
method = "writeNullChunkKVFinalNoRef";
+ } else {
+ method = "writeJavaNullChunk";
}
}
+ Expression writeNullChunk;
+ if (method.equals("writeJavaNullChunkGeneric")) {
+ writeNullChunk =
+ inlineInvoke(
+ serializer,
+ method,
+ MAP_ENTRY_TYPE,
+ buffer,
+ entry,
+ iterator,
+ getGenericTypeField(keyType),
+ getGenericTypeField(valueType));
+ } else {
+ writeNullChunk =
+ inlineInvoke(
+ serializer,
+ method,
+ MAP_ENTRY_TYPE,
+ buffer,
+ entry,
+ iterator,
+ keySerializer,
+ valueSerializer);
+ }
Expression writeChunk = writeChunk(buffer, entry, iterator,
keyType, valueType);
return new ListExpression(
- new Assign(
- entry,
- inlineInvoke(
- serializer,
- method,
- MAP_ENTRY_TYPE,
- buffer,
- entry,
- iterator,
- keySerializer,
- valueSerializer)),
+ new Assign(entry, writeNullChunk),
new If(
neqNull(entry), inline ? writeChunk : new Assign(entry,
inline(writeChunk))));
});
@@ -1214,8 +1269,8 @@ public abstract class BaseObjectCodecBuilder extends
CodecBuilder {
Expression chunkHeader;
Expression keySerializer, valueSerializer;
- boolean trackingKeyRef = visitFury(fury ->
fury.getClassResolver().needToWriteRef(keyType));
- boolean trackingValueRef = visitFury(fury ->
fury.getClassResolver().needToWriteRef(valueType));
+ boolean trackingKeyRef = fury(fury ->
fury.getClassResolver().needToWriteRef(keyType));
+ boolean trackingValueRef = fury(fury ->
fury.getClassResolver().needToWriteRef(valueType));
Expression keyWriteRef = Literal.ofBoolean(trackingKeyRef);
Expression valueWriteRef = Literal.ofBoolean(trackingValueRef);
boolean inline = keyMonomorphic && valueMonomorphic;
@@ -1420,7 +1475,7 @@ public abstract class BaseObjectCodecBuilder extends
CodecBuilder {
TypeRef<?> typeRef,
Function<Expression, Expression> callback,
InvokeHint invokeHint) {
- if (visitFury(f -> f.getClassResolver().needToWriteRef(typeRef))) {
+ if (fury(f -> f.getClassResolver().needToWriteRef(typeRef))) {
return readRef(buffer, callback, () -> deserializeForNotNull(buffer,
typeRef, invokeHint));
} else {
if (typeRef.isPrimitive()) {
@@ -1438,7 +1493,7 @@ public abstract class BaseObjectCodecBuilder extends
CodecBuilder {
TypeRef<?> typeRef,
Function<Expression, Expression> callback,
boolean nullable) {
- if (visitFury(f -> f.getClassResolver().needToWriteRef(typeRef))) {
+ if (fury(f -> f.getClassResolver().needToWriteRef(typeRef))) {
return readRef(buffer, callback, () -> deserializeForNotNull(buffer,
typeRef, null));
} else {
if (typeRef.isPrimitive()) {
@@ -1631,7 +1686,7 @@ public abstract class BaseObjectCodecBuilder extends
CodecBuilder {
Class<?> elemClass = TypeUtils.getRawType(elementType);
walkPath.add(elementType.toString());
boolean finalType = isMonomorphic(elemClass);
- boolean trackingRef = visitFury(fury ->
fury.getClassResolver().needToWriteRef(elementType));
+ boolean trackingRef = fury(fury ->
fury.getClassResolver().needToWriteRef(elementType));
if (finalType) {
if (trackingRef) {
builder.add(readContainerElements(elementType, true, null, null,
buffer, collection, size));
@@ -1653,7 +1708,7 @@ public abstract class BaseObjectCodecBuilder extends
CodecBuilder {
inlineInvoke(readClassInfo(elemClass, buffer), "getSerializer",
SERIALIZER_TYPE);
TypeRef<?> serializerType = getSerializerType(elementType);
Expression elemSerializer; // make it in scope of `if(sameElementClass)`
- boolean maybeDecl = visitFury(f ->
f.getClassResolver().isSerializable(elemClass));
+ boolean maybeDecl = fury(f ->
f.getClassResolver().isSerializable(elemClass));
if (maybeDecl) {
elemSerializer =
new If(
diff --git
a/java/fury-core/src/main/java/org/apache/fury/builder/CodecBuilder.java
b/java/fury-core/src/main/java/org/apache/fury/builder/CodecBuilder.java
index bbda1ff0..4002865c 100644
--- a/java/fury-core/src/main/java/org/apache/fury/builder/CodecBuilder.java
+++ b/java/fury-core/src/main/java/org/apache/fury/builder/CodecBuilder.java
@@ -449,7 +449,7 @@ public abstract class CodecBuilder {
});
}
- private Reference getOrCreateField(
+ protected Reference getOrCreateField(
boolean isStatic, Class<?> type, String fieldName, Supplier<Expression>
value) {
Reference fieldRef = fieldMap.get(fieldName);
if (fieldRef == null) {
diff --git
a/java/fury-core/src/main/java/org/apache/fury/builder/CompatibleCodecBuilder.java
b/java/fury-core/src/main/java/org/apache/fury/builder/CompatibleCodecBuilder.java
index 33b8bcc0..5bd35579 100644
---
a/java/fury-core/src/main/java/org/apache/fury/builder/CompatibleCodecBuilder.java
+++
b/java/fury-core/src/main/java/org/apache/fury/builder/CompatibleCodecBuilder.java
@@ -878,9 +878,9 @@ public class CompatibleCodecBuilder extends
BaseObjectCodecBuilder {
protected Expression writeFinalClassInfo(Expression buffer, Class<?> cls) {
Preconditions.checkArgument(ReflectionUtils.isMonomorphic(cls));
- ClassInfo classInfo = visitFury(f ->
f.getClassResolver().getClassInfo(cls, false));
+ ClassInfo classInfo = fury(f -> f.getClassResolver().getClassInfo(cls,
false));
if (classInfo != null && classInfo.getClassId() !=
ClassResolver.NO_CLASS_ID) {
- return classResolver.writeClassExpr(buffer, classInfo.getClassId());
+ return fury(f -> f.getClassResolver().writeClassExpr(buffer,
classInfo.getClassId()));
}
Expression classInfoExpr = getFinalClassInfo(cls);
return new Invoke(classResolverRef, "writeClassInfo", buffer,
classInfoExpr);
@@ -888,9 +888,9 @@ public class CompatibleCodecBuilder extends
BaseObjectCodecBuilder {
protected Expression skipFinalClassInfo(Class<?> cls, Expression buffer) {
Preconditions.checkArgument(ReflectionUtils.isMonomorphic(cls));
- ClassInfo classInfo = visitFury(f ->
f.getClassResolver().getClassInfo(cls, false));
+ ClassInfo classInfo = fury(f -> f.getClassResolver().getClassInfo(cls,
false));
if (classInfo != null && classInfo.getClassId() !=
ClassResolver.NO_CLASS_ID) {
- return classResolver.skipRegisteredClassExpr(buffer);
+ return fury(f -> f.getClassResolver().skipRegisteredClassExpr(buffer));
}
// read `ClassInfo` is not used, set `inlineReadClassInfo` false,
// to avoid read doesn't happen in generated code.
diff --git
a/java/fury-core/src/main/java/org/apache/fury/builder/MetaSharedCodecBuilder.java
b/java/fury-core/src/main/java/org/apache/fury/builder/MetaSharedCodecBuilder.java
index 91a6413e..424cff77 100644
---
a/java/fury-core/src/main/java/org/apache/fury/builder/MetaSharedCodecBuilder.java
+++
b/java/fury-core/src/main/java/org/apache/fury/builder/MetaSharedCodecBuilder.java
@@ -75,7 +75,7 @@ public class MetaSharedCodecBuilder extends
ObjectCodecBuilder {
"Class version check should be disabled when compatible mode is
enabled.");
this.classDef = classDef;
Collection<Descriptor> descriptors =
- visitFury(
+ fury(
f -> MetaSharedSerializer.consolidateFields(f.getClassResolver(),
beanClass, classDef));
DescriptorGrouper grouper =
fury.getClassResolver().createDescriptorGrouper(descriptors, false);
objectCodecOptimizer =
diff --git
a/java/fury-core/src/main/java/org/apache/fury/builder/ObjectCodecBuilder.java
b/java/fury-core/src/main/java/org/apache/fury/builder/ObjectCodecBuilder.java
index eb0a837f..54672e85 100644
---
a/java/fury-core/src/main/java/org/apache/fury/builder/ObjectCodecBuilder.java
+++
b/java/fury-core/src/main/java/org/apache/fury/builder/ObjectCodecBuilder.java
@@ -90,15 +90,16 @@ public class ObjectCodecBuilder extends
BaseObjectCodecBuilder {
boolean shareMeta = fury.getConfig().isMetaShareEnabled();
if (shareMeta) {
descriptors =
- visitFury(
+ fury(
f ->
f.getClassResolver()
.getClassDef(beanClass, true)
- .getDescriptors(classResolver, beanClass));
+ .getDescriptors(f.getClassResolver(), beanClass));
} else {
descriptors = fury.getClassResolver().getFieldDescriptors(beanClass,
true);
}
- DescriptorGrouper grouper =
classResolver.createDescriptorGrouper(descriptors, false);
+ Collection<Descriptor> p = descriptors;
+ DescriptorGrouper grouper = fury(f ->
f.getClassResolver().createDescriptorGrouper(p, false));
descriptors = grouper.getSortedDescriptors();
classVersionHash =
new Literal(ObjectSerializer.computeStructHash(fury, descriptors),
PRIMITIVE_INT_TYPE);
@@ -137,7 +138,7 @@ public class ObjectCodecBuilder extends
BaseObjectCodecBuilder {
/** Mark non-inner registered final types as non-final to write class def
for those types. */
@Override
protected boolean isMonomorphic(Class<?> clz) {
- return visitFury(f -> f.getClassResolver().isMonomorphic(clz));
+ return fury(f -> f.getClassResolver().isMonomorphic(clz));
}
/**
diff --git
a/java/fury-core/src/main/java/org/apache/fury/resolver/ClassResolver.java
b/java/fury-core/src/main/java/org/apache/fury/resolver/ClassResolver.java
index f4661179..63902c2d 100644
--- a/java/fury-core/src/main/java/org/apache/fury/resolver/ClassResolver.java
+++ b/java/fury-core/src/main/java/org/apache/fury/resolver/ClassResolver.java
@@ -268,6 +268,7 @@ public class ClassResolver implements TypeResolver {
private ClassChecker classChecker = (classResolver, className) -> true;
private GenericType objectGenericType;
private final IdentityMap<Type, GenericType> genericTypes = new
IdentityMap<>();
+ private final Map<Class, Map<String, GenericType>> classGenericTypes = new
HashMap<>();
private final Map<List<ClassLoader>, CodeGenerator> codeGeneratorMap = new
HashMap<>();
}
@@ -2004,6 +2005,22 @@ public class ClassResolver implements TypeResolver {
public void resetWrite() {}
+ @CodegenInvoke
+ public GenericType getGenericTypeInStruct(Class<?> cls, String
genericTypeStr) {
+ Map<String, GenericType> map =
+ extRegistry.classGenericTypes.computeIfAbsent(cls, k -> new
HashMap<>());
+ GenericType genericType = map.get(genericTypeStr);
+ if (genericType == null) {
+ for (Field field : ReflectionUtils.getFields(cls, true)) {
+ Type type = field.getGenericType();
+ TypeRef<Object> typeRef = TypeRef.of(type);
+ genericType = buildGenericType(typeRef);
+ map.put(type.getTypeName(), genericType);
+ }
+ }
+ return genericType;
+ }
+
@Override
public GenericType buildGenericType(TypeRef<?> typeRef) {
return GenericType.build(
diff --git
a/java/fury-core/src/main/java/org/apache/fury/serializer/collection/AbstractMapSerializer.java
b/java/fury-core/src/main/java/org/apache/fury/serializer/collection/AbstractMapSerializer.java
index f392fe3a..5fa74580 100644
---
a/java/fury-core/src/main/java/org/apache/fury/serializer/collection/AbstractMapSerializer.java
+++
b/java/fury-core/src/main/java/org/apache/fury/serializer/collection/AbstractMapSerializer.java
@@ -138,18 +138,30 @@ public abstract class AbstractMapSerializer<T> extends
Serializer<T> {
Iterator<Entry<Object, Object>> iterator = map.entrySet().iterator();
Entry<Object, Object> entry = iterator.next();
while (entry != null) {
- entry = writeJavaNullChunk(buffer, entry, iterator, keySerializer,
valueSerializer);
- if (entry != null) {
- if (keySerializer != null || valueSerializer != null) {
+ if (keySerializer != null || valueSerializer != null) {
+ entry = writeJavaNullChunk(buffer, entry, iterator, keySerializer,
valueSerializer);
+ if (entry != null) {
entry =
writeJavaChunk(
classResolver, buffer, entry, iterator, keySerializer,
valueSerializer);
- } else {
- Generics generics = fury.getGenerics();
- GenericType genericType = generics.nextGenericType();
- if (genericType == null) {
+ }
+ } else {
+ Generics generics = fury.getGenerics();
+ GenericType genericType = generics.nextGenericType();
+ if (genericType == null) {
+ entry = writeJavaNullChunk(buffer, entry, iterator, null, null);
+ if (entry != null) {
entry = writeJavaChunk(classResolver, buffer, entry, iterator,
null, null);
- } else {
+ }
+ } else {
+ if (genericType.getTypeParametersCount() < 2) {
+ genericType = getKVGenericType(genericType);
+ }
+ GenericType keyGenericType = genericType.getTypeParameter0();
+ GenericType valueGenericType = genericType.getTypeParameter1();
+ entry =
+ writeJavaNullChunkGeneric(buffer, entry, iterator,
keyGenericType, valueGenericType);
+ if (entry != null) {
entry =
writeJavaChunkGeneric(
classResolver, generics, genericType, buffer, entry,
iterator);
@@ -257,6 +269,81 @@ public abstract class AbstractMapSerializer<T> extends
Serializer<T> {
}
}
+ public final Entry writeJavaNullChunkGeneric(
+ MemoryBuffer buffer,
+ Entry entry,
+ Iterator<Entry<Object, Object>> iterator,
+ GenericType keyType,
+ GenericType valueType) {
+ while (true) {
+ Object key = entry.getKey();
+ Object value = entry.getValue();
+ if (key != null) {
+ if (value != null) {
+ return entry;
+ }
+ writeKeyForNullValueChunkGeneric(buffer, key, keyType);
+ } else {
+ writeValueForNullKeyChunkGeneric(buffer, value, valueType);
+ }
+ if (iterator.hasNext()) {
+ entry = iterator.next();
+ } else {
+ return null;
+ }
+ }
+ }
+
+ private void writeKeyForNullValueChunkGeneric(
+ MemoryBuffer buffer, Object key, GenericType keyType) {
+ if (!keyType.isMonomorphic()) {
+ buffer.writeByte(VALUE_HAS_NULL | TRACKING_KEY_REF);
+ binding.writeRef(buffer, key, keyClassInfoWriteCache);
+ return;
+ }
+ Serializer serializer = keyType.getSerializer(typeResolver);
+ if (keyType.hasGenericParameters()) {
+ fury.getGenerics().pushGenericType(keyType);
+ fury.incDepth(1);
+ }
+ if (serializer.needToWriteRef()) {
+ buffer.writeByte(NULL_VALUE_KEY_DECL_TYPE_TRACKING_REF);
+ binding.writeRef(buffer, key, serializer);
+ } else {
+ buffer.writeByte(NULL_VALUE_KEY_DECL_TYPE);
+ binding.write(buffer, serializer, key);
+ }
+ if (keyType.hasGenericParameters()) {
+ fury.incDepth(-1);
+ fury.getGenerics().popGenericType();
+ }
+ }
+
+ private void writeValueForNullKeyChunkGeneric(
+ MemoryBuffer buffer, Object value, GenericType valueType) {
+ if (!valueType.isMonomorphic()) {
+ buffer.writeByte(KEY_HAS_NULL | TRACKING_VALUE_REF);
+ binding.writeRef(buffer, value, valueClassInfoWriteCache);
+ return;
+ }
+ Serializer serializer = valueType.getSerializer(typeResolver);
+ if (valueType.hasGenericParameters()) {
+ fury.getGenerics().pushGenericType(valueType);
+ fury.incDepth(1);
+ }
+ if (serializer.needToWriteRef()) {
+ buffer.writeByte(NULL_KEY_VALUE_DECL_TYPE_TRACKING_REF);
+ binding.writeRef(buffer, value, serializer);
+ } else {
+ buffer.writeByte(NULL_KEY_VALUE_DECL_TYPE);
+ binding.write(buffer, serializer, value);
+ }
+ if (valueType.hasGenericParameters()) {
+ fury.incDepth(-1);
+ fury.getGenerics().popGenericType();
+ }
+ }
+
// Make byte code of this method smaller than 325 for better jit inline
private Entry writeJavaChunk(
TypeResolver classResolver,
@@ -341,7 +428,8 @@ public abstract class AbstractMapSerializer<T> extends
Serializer<T> {
return classInfo.getSerializer();
}
- private Entry writeJavaChunkGeneric(
+ @CodegenInvoke
+ public Entry writeJavaChunkGeneric(
TypeResolver classResolver,
Generics generics,
GenericType genericType,
@@ -599,10 +687,14 @@ public abstract class AbstractMapSerializer<T> extends
Serializer<T> {
boolean trackKeyRef = (chunkHeader & TRACKING_KEY_REF) != 0;
Object key;
if ((chunkHeader & KEY_DECL_TYPE) != 0) {
- if (trackKeyRef) {
- key = binding.readRef(buffer, keySerializer);
+ if (keySerializer == null) {
+ key = readNonEmptyValueFromNullChunk(buffer, trackKeyRef, true);
} else {
- key = binding.read(buffer, keySerializer);
+ if (trackKeyRef) {
+ key = binding.readRef(buffer, keySerializer);
+ } else {
+ key = binding.read(buffer, keySerializer);
+ }
}
} else {
key = binding.readRef(buffer, keyClassInfoReadCache);
@@ -635,10 +727,14 @@ public abstract class AbstractMapSerializer<T> extends
Serializer<T> {
Object value;
boolean trackValueRef = (chunkHeader & TRACKING_VALUE_REF) != 0;
if ((chunkHeader & VALUE_DECL_TYPE) != 0) {
- if (trackValueRef) {
- value = binding.readRef(buffer, valueSerializer);
+ if (valueSerializer == null) {
+ value = readNonEmptyValueFromNullChunk(buffer, trackValueRef, false);
} else {
- value = binding.read(buffer, valueSerializer);
+ if (trackValueRef) {
+ value = binding.readRef(buffer, valueSerializer);
+ } else {
+ value = binding.read(buffer, valueSerializer);
+ }
}
} else {
value = binding.readRef(buffer, valueClassInfoReadCache);
@@ -649,6 +745,28 @@ public abstract class AbstractMapSerializer<T> extends
Serializer<T> {
}
}
+ private Object readNonEmptyValueFromNullChunk(
+ MemoryBuffer buffer, boolean trackRef, boolean isKey) {
+ Generics generics = fury.getGenerics();
+ GenericType genericType = generics.nextGenericType();
+ if (genericType.getTypeParametersCount() < 2) {
+ genericType = getKVGenericType(genericType);
+ }
+ GenericType type = isKey ? genericType.getTypeParameter0() :
genericType.getTypeParameter1();
+ generics.pushGenericType(type);
+ fury.incDepth(1);
+ Serializer<?> serializer = type.getSerializer(typeResolver);
+ Object v;
+ if (trackRef) {
+ v = binding.readRef(buffer, serializer);
+ } else {
+ v = binding.read(buffer, serializer);
+ }
+ fury.incDepth(-1);
+ generics.popGenericType();
+ return v;
+ }
+
@CodegenInvoke
public long readNullChunkKVFinalNoRef(
MemoryBuffer buffer,
diff --git a/java/fury-core/src/main/java/org/apache/fury/type/GenericType.java
b/java/fury-core/src/main/java/org/apache/fury/type/GenericType.java
index 8a6bb6f6..eb045109 100644
--- a/java/fury-core/src/main/java/org/apache/fury/type/GenericType.java
+++ b/java/fury-core/src/main/java/org/apache/fury/type/GenericType.java
@@ -34,7 +34,11 @@ import org.apache.fury.reflect.TypeRef;
import org.apache.fury.resolver.TypeResolver;
import org.apache.fury.serializer.Serializer;
-/** GenericType for building java generics as a tree and binding with fury
serializers. */
+/**
+ * GenericType for building java generics as a tree and binding with fury
serializers. Note
+ * GenericType for specific types such as Object.class can't be singleton,
because GenericType has
+ * some mutable fields
+ */
// TODO(chaokunyang) refine generics which can be inspired by spring
ResolvableType.
@SuppressWarnings("rawtypes")
public class GenericType {
diff --git
a/java/fury-core/src/test/java/org/apache/fury/serializer/collection/MapSerializersTest.java
b/java/fury-core/src/test/java/org/apache/fury/serializer/collection/MapSerializersTest.java
index 2e38f728..ff4e4704 100644
---
a/java/fury-core/src/test/java/org/apache/fury/serializer/collection/MapSerializersTest.java
+++
b/java/fury-core/src/test/java/org/apache/fury/serializer/collection/MapSerializersTest.java
@@ -1008,4 +1008,24 @@ public class MapSerializersTest extends FuryTestBase {
serDeCheck(fury, pojo);
fury.serialize(pojo);
}
+
+ @Data
+ @AllArgsConstructor
+ public static class NullChunkGeneric {
+ public Map<String, Integer> map;
+ public Map<String, List<Integer>> map1;
+ }
+
+ @Test
+ public void testNullChunkGeneric() {
+ Fury fury1 = builder().withCodegen(true).build();
+ Map<String, Integer> map = ofHashMap(null, 1, "k1", null, "k2", 2);
+ Map<String, List<Integer>> map1 =
+ ofHashMap(null, ofArrayList(1), "k1", null, "k2", ofArrayList(2));
+ NullChunkGeneric o = new NullChunkGeneric(map, map1);
+ byte[] bytes = fury1.serialize(o);
+ Fury fury2 = builder().withCodegen(false).build();
+ Object object = fury2.deserialize(bytes);
+ assertEquals(object, o);
+ }
}
diff --git
a/java/fury-core/src/test/java/org/apache/fury/serializer/compatible/DifferentPOJOCompatibleSerializerTest.java
b/java/fury-core/src/test/java/org/apache/fury/serializer/compatible/DifferentPOJOCompatibleSerializerTest.java
index 59dd15d4..463113f3 100644
---
a/java/fury-core/src/test/java/org/apache/fury/serializer/compatible/DifferentPOJOCompatibleSerializerTest.java
+++
b/java/fury-core/src/test/java/org/apache/fury/serializer/compatible/DifferentPOJOCompatibleSerializerTest.java
@@ -19,6 +19,7 @@
package org.apache.fury.serializer.compatible;
+import java.util.Arrays;
import org.apache.fury.Fury;
import org.apache.fury.config.CompatibleMode;
import org.apache.fury.config.Language;
@@ -44,13 +45,13 @@ public class DifferentPOJOCompatibleSerializerTest extends
Assert {
.requireClassRegistration(false)
.withAsyncCompilation(true)
.serializeEnumByName(true)
+ .withName(getClass() + Arrays.toString(classes))
.build();
if (classes != null) {
for (Class<?> clazz : classes) {
instance.register(clazz);
}
}
- ;
return instance;
}
diff --git
a/java/fury-core/src/test/java/org/apache/fury/serializer/compatible/DifferentPOJOCompatibleSerializerWithRegistrationTest.java
b/java/fury-core/src/test/java/org/apache/fury/serializer/compatible/DifferentPOJOCompatibleSerializerWithRegistrationTest.java
index c5c8d15e..aacd6d0e 100644
---
a/java/fury-core/src/test/java/org/apache/fury/serializer/compatible/DifferentPOJOCompatibleSerializerWithRegistrationTest.java
+++
b/java/fury-core/src/test/java/org/apache/fury/serializer/compatible/DifferentPOJOCompatibleSerializerWithRegistrationTest.java
@@ -19,6 +19,7 @@
package org.apache.fury.serializer.compatible;
+import java.util.Arrays;
import org.apache.fury.Fury;
import org.apache.fury.config.CompatibleMode;
import org.apache.fury.config.Language;
@@ -44,13 +45,13 @@ public class
DifferentPOJOCompatibleSerializerWithRegistrationTest extends Asser
.requireClassRegistration(false)
.withAsyncCompilation(true)
.serializeEnumByName(true)
+ .withName(getClass() + Arrays.toString(classes))
.build();
if (classes != null) {
for (Class<?> clazz : classes) {
instance.register(clazz);
}
}
- ;
return instance;
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]